diff --git a/meson.build b/meson.build index bc52839..6a7169c 100644 --- a/meson.build +++ b/meson.build @@ -1,8 +1,9 @@ project('koto', 'c', version: '0.1.0', meson_version: '>= 0.57.0', - default_options: [ 'warning_level=2', + default_options: [ 'c_std=gnu11', + 'warning_level=2', 'werror=true', ], ) @@ -18,9 +19,9 @@ configure_file( output: 'koto-config.h', configuration: config_h, ) -add_project_arguments([ - '-I' + meson.current_build_dir(), -], language: 'c') + +c = meson.get_compiler('c') +toml_dep = c.find_library('toml', required: true) subdir('theme') subdir('data') diff --git a/src/components/koto-action-bar.c b/src/components/koto-action-bar.c index 592b52b..e8edb1e 100644 --- a/src/components/koto-action-bar.c +++ b/src/components/koto-action-bar.c @@ -21,6 +21,7 @@ #include "../db/cartographer.h" #include "../pages/music/music-local.h" #include "../playlist/add-remove-track-popover.h" +#include "../playlist/current.h" #include "../playback/engine.h" #include "../koto-button.h" #include "../koto-utils.h" @@ -28,6 +29,7 @@ extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup; extern KotoCartographer * koto_maps; +extern KotoCurrentPlaylist * current_playlist; extern KotoPageMusicLocal * music_local_page; extern KotoPlaybackEngine * playback_engine; extern KotoWindow * main_window; @@ -241,7 +243,6 @@ void koto_action_bar_handle_play_track_button_clicked( (void) button; KotoActionBar * self = data; - if (!KOTO_IS_ACTION_BAR(self)) { return; } @@ -252,12 +253,20 @@ void koto_action_bar_handle_play_track_button_clicked( KotoTrack * track = g_list_nth_data(self->current_list, 0); // Get the first track - if (!KOTO_IS_TRACK(track)) { // Not a track goto doclose; } - koto_playback_engine_set_track_by_uuid(playback_engine, koto_track_get_uuid(track)); // Set the track to play + if (self->relative == KOTO_ACTION_BAR_IS_PLAYLIST_RELATIVE) { // Relative to a playlist + KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->current_playlist_uuid); + + if (KOTO_IS_PLAYLIST(playlist)) { // Is a playlist + koto_current_playlist_set_playlist(current_playlist, playlist); // Update our playlist to the one associated with the track we are playing + koto_playlist_set_track_as_current(playlist, koto_track_get_uuid(track)); // Get this track as the current track in the position + } + } + + koto_playback_engine_set_track_by_uuid(playback_engine, koto_track_get_uuid(track), TRUE); // Set the track to play doclose: koto_action_bar_close(self); diff --git a/src/components/koto-cover-art-button.c b/src/components/koto-cover-art-button.c index f1e43ec..e48d1c5 100644 --- a/src/components/koto-cover-art-button.c +++ b/src/components/koto-cover-art-button.c @@ -63,7 +63,6 @@ static void koto_cover_art_button_set_property( static void koto_cover_art_button_class_init(KotoCoverArtButtonClass * c) { GObjectClass * gobject_class; - gobject_class = G_OBJECT_CLASS(c); gobject_class->get_property = koto_cover_art_button_get_property; gobject_class->set_property = koto_cover_art_button_set_property; @@ -201,7 +200,6 @@ void koto_cover_art_button_set_art_path( gboolean defined_artwork = koto_utils_is_string_valid(art_path); - if (GTK_IS_IMAGE(self->art)) { // Already have an image if (!defined_artwork) { // No art path or empty string gtk_image_set_from_icon_name(GTK_IMAGE(self->art), "audio-x-generic-symbolic"); diff --git a/src/config/config.c b/src/config/config.c new file mode 100644 index 0000000..c972b6f --- /dev/null +++ b/src/config/config.c @@ -0,0 +1,505 @@ +/* config.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "../playback/engine.h" +#include "../koto-paths.h" +#include "../koto-utils.h" + +#include "config.h" + +extern int errno; +extern const gchar * koto_config_template; +extern KotoPlaybackEngine * playback_engine; + +enum { + PROP_0, + PROP_PLAYBACK_CONTINUE_ON_PLAYLIST, + PROP_PLAYBACK_LAST_USED_VOLUME, + PROP_PLAYBACK_MAINTAIN_SHUFFLE, + PROP_UI_THEME_DESIRED, + PROP_UI_THEME_OVERRIDE, + N_PROPS, +}; + +static GParamSpec * config_props[N_PROPS] = { + 0 +}; + +struct _KotoConfig { + GObject parent_instance; + + GFile * config_file; + GFileMonitor * config_file_monitor; + + gchar * path; + gboolean finalized; + + /* Playback Settings */ + + gboolean playback_continue_on_playlist; + gdouble playback_last_used_volume; + gboolean playback_maintain_shuffle; + + /* UI Settings */ + + gchar * ui_theme_desired; + gboolean ui_theme_override; +}; + +struct _KotoConfigClass { + GObjectClass parent_class; +}; + +G_DEFINE_TYPE(KotoConfig, koto_config, G_TYPE_OBJECT); + +KotoConfig * config; + +static void koto_config_constructed(GObject *obj); + +static void koto_config_get_property( + GObject * obj, + guint prop_id, + GValue * val, + GParamSpec * spec +); + +static void koto_config_set_property( + GObject * obj, + guint prop_id, + const GValue * val, + GParamSpec * spec +); + +static void koto_config_class_init(KotoConfigClass *c) { + GObjectClass * gobject_class; + gobject_class = G_OBJECT_CLASS(c); + gobject_class->constructed = koto_config_constructed; + gobject_class->get_property = koto_config_get_property; + gobject_class->set_property = koto_config_set_property; + + config_props[PROP_PLAYBACK_CONTINUE_ON_PLAYLIST] = g_param_spec_boolean( + "playback-continue-on-playlist", + "Continue Playback of Playlist", + "Continue playback of a Playlist after playing a specific track in the playlist", + FALSE, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE + ); + + config_props[PROP_PLAYBACK_LAST_USED_VOLUME] = g_param_spec_double( + "playback-last-used-volume", + "Last Used Volume", + "Last Used Volume", + 0, + 1, + 0.5, // 50% + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE + ); + + config_props[PROP_PLAYBACK_MAINTAIN_SHUFFLE] = g_param_spec_boolean( + "playback-maintain-shuffle", + "Maintain Shuffle on Playlist Change", + "Maintain shuffle setting when changing playlists", + TRUE, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE + ); + + config_props[PROP_UI_THEME_DESIRED] = g_param_spec_string( + "ui-theme-desired", + "Desired Theme", + "Desired Theme", + "dark", // Like my soul + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE + ); + + config_props[PROP_UI_THEME_OVERRIDE] = g_param_spec_boolean( + "ui-theme-override", + "Override built-in theming", + "Override built-in theming", + FALSE, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE + ); + + g_object_class_install_properties(gobject_class, N_PROPS, config_props); +} + +static void koto_config_init(KotoConfig * self) { + self->finalized = FALSE; +} + +static void koto_config_constructed(GObject *obj) { + KotoConfig * self = KOTO_CONFIG(obj); + self->finalized = TRUE; +} + +static void koto_config_get_property( + GObject * obj, + guint prop_id, + GValue * val, + GParamSpec * spec +) { + KotoConfig * self = KOTO_CONFIG(obj); + + switch (prop_id) { + case PROP_PLAYBACK_CONTINUE_ON_PLAYLIST: + g_value_set_boolean(val, self->playback_continue_on_playlist); + break; + case PROP_PLAYBACK_LAST_USED_VOLUME: + g_value_set_double(val, self->playback_last_used_volume); + break; + case PROP_PLAYBACK_MAINTAIN_SHUFFLE: + g_value_set_boolean(val, self->playback_maintain_shuffle); + break; + case PROP_UI_THEME_DESIRED: + g_value_set_string(val, g_strdup(self->ui_theme_desired)); + break; + case PROP_UI_THEME_OVERRIDE: + g_value_set_boolean(val, self->ui_theme_override); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +static void koto_config_set_property( + GObject * obj, + guint prop_id, + const GValue * val, + GParamSpec * spec +) { + KotoConfig * self = KOTO_CONFIG(obj); + + switch (prop_id) { + case PROP_PLAYBACK_CONTINUE_ON_PLAYLIST: + self->playback_continue_on_playlist = g_value_get_boolean(val); + break; + case PROP_PLAYBACK_LAST_USED_VOLUME: + self->playback_last_used_volume = g_value_get_double(val); + break; + case PROP_PLAYBACK_MAINTAIN_SHUFFLE: + self->playback_maintain_shuffle = g_value_get_boolean(val); + break; + case PROP_UI_THEME_DESIRED: + self->ui_theme_desired = g_strdup(g_value_get_string(val)); + break; + case PROP_UI_THEME_OVERRIDE: + self->ui_theme_override = g_value_get_boolean(val); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } + + if (self->finalized) { // Loaded the config + g_object_notify_by_pspec(obj, config_props[prop_id]); // Notify that a change happened + } +} + +/** + * Load our TOML file from the specified path into our KotoConfig +**/ +void koto_config_load(KotoConfig * self, gchar *path) { + if (!koto_utils_is_string_valid(path)) { // Path is not valid + return; + } + + self->path = g_strdup(path); + + self->config_file = g_file_new_for_path(path); + gboolean config_file_exists = g_file_query_exists(self->config_file, NULL); + + if (!config_file_exists) { // File does not exist + GError * create_err; + GFileOutputStream * stream = g_file_create( + self->config_file, + G_FILE_CREATE_PRIVATE, + NULL, + &create_err + ); + + if (create_err != NULL) { + if (create_err->code != G_IO_ERROR_EXISTS) { // Not an error indicating the file already exists + g_message("Failed to create or open file: %s", create_err->message); + return; + } + } + + g_object_unref(stream); + } + + GError * file_info_query_err; + + GFileInfo * file_info = g_file_query_info( // Get the size of our TOML file + self->config_file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + NULL, + &file_info_query_err + ); + + if (file_info != NULL) { // Got info + goffset size = g_file_info_get_size(file_info); // Get the size from info + g_object_unref(file_info); // Unref immediately + + if (size == 0) { // If we don't have any file contents (new file), skip parsing + goto monitor; + } + } else { // Failed to get the info + g_warning("Failed to get size info of %s: %s", self->path, file_info_query_err->message); + } + + FILE * file; + file = fopen(self->path, "r"); // Open the file as read only + + if (file == NULL) { // Failed to get the file + /** Handle error checking here*/ + return; + } + + char errbuf[200]; + toml_table_t * conf = toml_parse_file(file, errbuf, sizeof(errbuf)); + fclose(file); // Close the file + + if (!conf) { + g_error("Failed to read our config file. %s", errbuf); + return; + } + + /** Supplemental Libraries (Excludes Built-in) */ + + toml_table_t * libraries_section = toml_table_in(conf, "libraries"); + + if (libraries_section) { // Have supplemental libraries + toml_array_t * library_uuids = toml_array_in(libraries_section, "uuids"); + + if (library_uuids && (toml_array_nelem(library_uuids) != 0)) { // Have UUIDs + for (int i = 0; i < toml_array_nelem(library_uuids); i++) { // Iterate over each UUID + toml_datum_t uuid = toml_string_at(library_uuids, i); // Get the UUID + + if (!uuid.ok) { // Not a UUID string + continue; // Skip this entry in the array + } + + g_message("UUID: %s", uuid.u.s); + // TODO: Implement Koto library creation + free(uuid.u.s); + toml_free(conf); + } + } + } + + /** Playback Section */ + + toml_table_t * playback_section = toml_table_in(conf, "playback"); + + if (playback_section) { // Have playback section + toml_datum_t continue_on_playlist = toml_bool_in(playback_section, "continue-on-playlist"); + toml_datum_t last_used_volume = toml_double_in(playback_section, "last-used-volume"); + toml_datum_t maintain_shuffle = toml_bool_in(playback_section, "maintain-shuffle"); + + if (continue_on_playlist.ok && (self->playback_continue_on_playlist != continue_on_playlist.u.b)) { // If we have a continue-on-playlist set and they are different + g_object_set(self, "playback-continue-on-playlist", continue_on_playlist.u.b, NULL); + } + + if (last_used_volume.ok && (self->playback_last_used_volume != last_used_volume.u.d)) { // If we have last-used-volume set and they are different + g_object_set(self, "playback-last-used-volume", last_used_volume.u.d, NULL); + } + + if (maintain_shuffle.ok && (self->playback_maintain_shuffle != maintain_shuffle.u.b)) { // If we have a "maintain shuffle set" and they are different + g_object_set(self, "playback-maintain-shuffle", maintain_shuffle.u.b, NULL); + } + } + + /* UI Section */ + + toml_table_t * ui_section = toml_table_in(conf, "ui"); + + if (ui_section) { // Have UI section + toml_datum_t name = toml_string_in(ui_section, "theme-desired"); + + if (name.ok && (g_strcmp0(name.u.s, self->ui_theme_desired) != 0)) { // Have a name specified and they are different + g_object_set(self, "ui-theme-desired", g_strdup(name.u.s), NULL); + free(name.u.s); + } + + toml_datum_t override_app = toml_bool_in(ui_section, "theme-override"); + + if (override_app.ok && (override_app.u.b != self->ui_theme_override)) { // Changed if we are overriding theme + g_object_set(self, "ui-theme-override", override_app.u.b, NULL); + } + } + +monitor: + if (self->config_file_monitor != NULL) { // If we already have a file monitor for the file + return; + } + + self->config_file_monitor = g_file_monitor_file( + self->config_file, + G_FILE_MONITOR_NONE, + NULL, + NULL + ); + + g_signal_connect(self->config_file_monitor, "changed", G_CALLBACK(koto_config_monitor_handle_changed), self); // Monitor changes to our config file + + if (!config_file_exists) { // File did not originally exist + koto_config_save(self); // Save immediately + } +} + +void koto_config_monitor_handle_changed(GFileMonitor * monitor, GFile * file, GFile *other_file, GFileMonitorEvent ev, gpointer user_data) { + (void) monitor; + (void) file; + (void) other_file; + KotoConfig * config = user_data; + + if ( + (ev == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) || // Attributes changed + (ev == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) // Changes done + ) { + koto_config_refresh(config); // Refresh the config + } +} + +/** + * Refresh will handle any FS notify change on our Koto config file and call load +**/ +void koto_config_refresh(KotoConfig * self) { + koto_config_load(self, self->path); +} + +/** + * Save will write our config back out +**/ +void koto_config_save(KotoConfig *self) { + GStrvBuilder * root_builder = g_strv_builder_new(); // Create a new strv builder + + GParamSpec ** props_list = g_object_class_list_properties(G_OBJECT_GET_CLASS(self), NULL); // Get the propreties associated with our settings + + GHashTable * sections_to_prop_keys = g_hash_table_new(g_str_hash, g_str_equal); // Create our section to hold our various sections based on props + + /* Section Hashes*/ + + gchar * playback_hash = g_strdup("playback"); + gchar * ui_hash = g_strdup("ui"); + + gdouble current_playback_volume = koto_playback_engine_get_volume(playback_engine); // Get the last used volume in the playback engine + self->playback_last_used_volume = current_playback_volume; // Update our value so we have it during save + + int i; + for (i = 0; i < N_PROPS; i++) { // For each property + GParamSpec *spec = props_list[i]; // Get the prop + + if (!G_IS_PARAM_SPEC(spec)) { // Not a spec + continue; // Skip + } + + const gchar * prop_name = g_param_spec_get_name(spec); + + gpointer respective_prop = NULL; + + if (g_str_has_prefix(prop_name, "playback")) { // Is playback + respective_prop = playback_hash; + } else if (g_str_has_prefix(prop_name, "ui")) { // Is UI + respective_prop = ui_hash; + } + + if (respective_prop == NULL) { // No property + continue; + } + + GList * keys; + + if (g_hash_table_contains(sections_to_prop_keys, respective_prop)) { // Already has list + keys = g_hash_table_lookup(sections_to_prop_keys, respective_prop); // Get the list + } else { // Don't have list + keys = NULL; + } + + keys = g_list_append(keys, g_strdup(prop_name)); // Add the name in full + g_hash_table_insert(sections_to_prop_keys, respective_prop, keys); // Replace list (or add it) + } + + GHashTableIter iter; + gpointer section_name, section_props; + + g_hash_table_iter_init(&iter, sections_to_prop_keys); + + while (g_hash_table_iter_next(&iter, §ion_name, §ion_props)) { + GStrvBuilder * section_builder = g_strv_builder_new(); // Make our string builder + g_strv_builder_add(section_builder, g_strdup_printf("[%s]", (gchar *) section_name)); // Add section as [section] + + GList * current_section_keyname; + for (current_section_keyname = section_props; current_section_keyname != NULL; current_section_keyname = current_section_keyname->next) { // Iterate over property names + GValue prop_val_raw = G_VALUE_INIT; // Initialize our GValue + g_object_get_property(G_OBJECT(self), current_section_keyname->data, &prop_val_raw); + gchar * prop_val = g_strdup_value_contents(&prop_val_raw); + + if ((g_strcmp0(prop_val, "TRUE") == 0) || (g_strcmp0(prop_val, "FALSE") == 0)) { // TRUE or FALSE from a boolean type + prop_val = g_utf8_strdown(prop_val, -1); // Change it to be lowercased + } + + gchar * key_name = g_strdup(current_section_keyname->data); + gchar * key_name_replaced = koto_utils_replace_string_all(key_name, g_strdup_printf("%s-", (gchar *) section_name), ""); // Remove SECTIONNAME- + + const gchar * line = g_strdup_printf("\t%s = %s", key_name_replaced, prop_val); + + g_strv_builder_add(section_builder, line); // Add the line + g_free(key_name_replaced); + g_free(key_name); + } + + GStrv lines = g_strv_builder_end(section_builder); // Get all the lines as a GStrv which is a gchar ** + gchar * content = g_strjoinv("\n", lines); // Separate all lines with newline + g_strfreev(lines); // Free our lines + + g_strv_builder_add(root_builder, content); // Add section content to root builder + g_strv_builder_unref(section_builder); // Unref our builder + } + + g_hash_table_unref(sections_to_prop_keys); // Free our hash table + + GStrv lines = g_strv_builder_end(root_builder); // Get all the lines as a GStrv which is a gchar ** + gchar * content = g_strjoinv("\n", lines); // Separate all lines with newline + g_strfreev(lines); // Free our lines + + g_strv_builder_unref(root_builder); // Unref our root builder + + ulong file_content_length = g_utf8_strlen(content, -1); + + g_file_replace_contents( + self->config_file, + content, + file_content_length, + NULL, + FALSE, + G_FILE_CREATE_PRIVATE, + NULL, + NULL, + NULL + ); +} + +KotoConfig * koto_config_new() { + return g_object_new (KOTO_TYPE_CONFIG, NULL); +} \ No newline at end of file diff --git a/src/config/config.h b/src/config/config.h new file mode 100644 index 0000000..7296439 --- /dev/null +++ b/src/config/config.h @@ -0,0 +1,38 @@ +/* config.h + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +G_BEGIN_DECLS + +/** + * Type Definition + **/ + +#define KOTO_TYPE_CONFIG (koto_config_get_type()) + +G_DECLARE_FINAL_TYPE(KotoConfig, koto_config, KOTO, CONFIG, GObject) + +KotoConfig* koto_config_new(); +void koto_config_load(KotoConfig * self, gchar *path); +void koto_config_monitor_handle_changed(GFileMonitor * monitor, GFile * file, GFile *other_file, GFileMonitorEvent ev, gpointer user_data); +void koto_config_refresh(KotoConfig * self); +void koto_config_save(KotoConfig * self); + +G_END_DECLS \ No newline at end of file diff --git a/src/db/cartographer.c b/src/db/cartographer.c index 606bcad..ced62c9 100644 --- a/src/db/cartographer.c +++ b/src/db/cartographer.c @@ -16,6 +16,7 @@ */ #include +#include "../koto-utils.h" #include "cartographer.h" enum { @@ -254,7 +255,6 @@ void koto_cartographer_add_playlist( ) { gchar * playlist_uuid = NULL; - g_object_get(playlist, "uuid", &playlist_uuid, NULL); if ((playlist_uuid == NULL) || koto_cartographer_has_playlist_by_uuid(self, playlist_uuid)) { // Have the playlist or invalid UUID @@ -494,16 +494,24 @@ void koto_cartographer_remove_playlist_by_uuid( KotoCartographer * self, gchar* playlist_uuid ) { - if (playlist_uuid != NULL) { - g_hash_table_remove(self->playlists, playlist_uuid); - - g_signal_emit( - self, - cartographer_signals[SIGNAL_PLAYLIST_REMOVED], - 0, - playlist_uuid - ); + if (!koto_utils_is_string_valid(playlist_uuid)) { // Not a valid playlist UUID string + return; } + + KotoPlaylist * possible_playlist = koto_cartographer_get_playlist_by_uuid(self, playlist_uuid); + + if (!KOTO_IS_PLAYLIST(possible_playlist)) { // If not a playlist + return; + } + + g_signal_emit( + self, + cartographer_signals[SIGNAL_PLAYLIST_REMOVED], + 0, + playlist_uuid + ); + + g_hash_table_remove(self->playlists, playlist_uuid); } void koto_cartographer_remove_track( diff --git a/src/db/db.c b/src/db/db.c index 1d4afa8..1bb7181 100644 --- a/src/db/db.c +++ b/src/db/db.c @@ -21,6 +21,9 @@ #include #include #include "db.h" +#include "../koto-paths.h" + +extern gchar * koto_path_to_db; int KOTO_DB_SUCCESS = 0; int KOTO_DB_NEW = 1; @@ -44,7 +47,6 @@ int create_db_tables() { gchar * create_tables_errmsg = NULL; int rc = sqlite3_exec(koto_db, tables_creation_queries, 0, 0, &create_tables_errmsg); - if (rc != SQLITE_OK) { g_critical("Failed to create required tables: %s", create_tables_errmsg); } @@ -56,7 +58,6 @@ int enable_foreign_keys() { gchar * enable_foreign_keys_err = NULL; int rc = sqlite3_exec(koto_db, "PRAGMA foreign_keys = ON;", 0, 0, &enable_foreign_keys_err); - if (rc != SQLITE_OK) { g_critical("Failed to enable foreign key support. Ensure your sqlite3 is compiled with neither SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER defined: %s", enable_foreign_keys_err); } @@ -65,39 +66,20 @@ int enable_foreign_keys() { return (rc == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL; } -gchar * get_db_path() { - if (db_filepath == NULL) { - const gchar * data_home = g_get_user_data_dir(); - gchar * data_dir = g_build_path(G_DIR_SEPARATOR_S, data_home, "com.github.joshstrobl.koto", NULL); - db_filepath = g_build_filename(g_strdup(data_dir), "db", NULL); // Build out our path using XDG_DATA_HOME (e.g. .local/share/) + our namespace + db as the file name - g_free(data_dir); - } - - return db_filepath; -} - int have_existing_db() { struct stat db_stat; - int success = stat(get_db_path(), &db_stat); - - + int success = stat(koto_path_to_db, &db_stat); return ((success == 0) && S_ISREG(db_stat.st_mode)) ? 0 : 1; } int open_db() { int ret = KOTO_DB_SUCCESS; // Default to last return being SUCCESS - if (have_existing_db() == 1) { // If we do not have an existing DB - const gchar * data_home = g_get_user_data_dir(); - const gchar * data_dir = g_path_get_dirname(db_filepath); - mkdir(data_home, 0755); - mkdir(data_dir, 0755); - chown(data_dir, getuid(), getgid()); ret = KOTO_DB_NEW; } - if (sqlite3_open(db_filepath, &koto_db) != KOTO_DB_SUCCESS) { // If we failed to open the database file + if (sqlite3_open(koto_path_to_db, &koto_db) != KOTO_DB_SUCCESS) { // If we failed to open the database file g_critical("Failed to open or create database: %s", sqlite3_errmsg(koto_db)); return KOTO_DB_FAIL; } diff --git a/src/indexer/album.c b/src/indexer/album.c index 11ab013..9ee2eae 100644 --- a/src/indexer/album.c +++ b/src/indexer/album.c @@ -543,7 +543,6 @@ void koto_album_set_as_current_playlist(KotoAlbum * self) { KotoPlaylist * new_album_playlist = koto_playlist_new(); // Create a new playlist - g_object_set(new_album_playlist, "ephemeral", TRUE, NULL); // Set as ephemeral / temporary // The following section effectively reverses our tracks, so the first is now last. diff --git a/src/indexer/file-indexer.c b/src/indexer/file-indexer.c index e7a8bd8..4050e67 100644 --- a/src/indexer/file-indexer.c +++ b/src/indexer/file-indexer.c @@ -232,7 +232,6 @@ int process_artists( KotoArtist * artist = koto_artist_new_with_uuid(artist_uuid); // Create our artist with the UUID - g_object_set( artist, "path", @@ -422,7 +421,6 @@ int process_tracks( void read_from_db(KotoLibrary * self) { int artists_rc = sqlite3_exec(koto_db, "SELECT * FROM artists", process_artists, self, NULL); // Process our artists - if (artists_rc != SQLITE_OK) { // Failed to get our artists g_critical("Failed to read our artists: %s", sqlite3_errmsg(koto_db)); return; @@ -432,7 +430,6 @@ void read_from_db(KotoLibrary * self) { int playlist_rc = sqlite3_exec(koto_db, "SELECT * FROM playlist_meta", process_playlists, self, NULL); // Process our playlists - if (playlist_rc != SQLITE_OK) { // Failed to get our playlists g_critical("Failed to read our playlists: %s", sqlite3_errmsg(koto_db)); return; @@ -551,27 +548,23 @@ void output_artists( (void) data; KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, (gchar*) artist_key); - if (artist == NULL) { return; } gchar * artist_name; - g_object_get(artist, "name", &artist_name, NULL); - g_debug("Artist: %s", artist_name); + g_message("Artist: %s", artist_name); GList * albums = koto_artist_get_albums(artist); // Get the albums for this artist - if (albums != NULL) { - g_debug("Length of Albums: %d", g_list_length(albums)); + g_message("Length of Albums: %d", g_list_length(albums)); } GList * a; - for (a = albums; a != NULL; a = a->next) { gchar * album_uuid = a->data; KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); diff --git a/src/koto-button.c b/src/koto-button.c index bae717f..ba036fb 100644 --- a/src/koto-button.c +++ b/src/koto-button.c @@ -17,7 +17,7 @@ #include #include "koto-button.h" -#include "koto-config.h" +#include "config/config.h" #include "koto-utils.h" struct _PixbufSize { @@ -113,7 +113,6 @@ static void koto_button_set_property( static void koto_button_class_init(KotoButtonClass * c) { GObjectClass * gobject_class; - gobject_class = G_OBJECT_CLASS(c); gobject_class->constructed = koto_button_constructed; gobject_class->set_property = koto_button_set_property; @@ -198,7 +197,6 @@ static void koto_button_constructed(GObject * obj) { KotoButton * self = KOTO_BUTTON(obj); GtkStyleContext * style = gtk_widget_get_style_context(GTK_WIDGET(self)); - gtk_style_context_add_class(style, "koto-button"); G_OBJECT_CLASS(koto_button_parent_class)->constructed(obj); diff --git a/src/koto-expander.c b/src/koto-expander.c index 192f183..ecb628f 100644 --- a/src/koto-expander.c +++ b/src/koto-expander.c @@ -17,7 +17,7 @@ #include #include -#include "koto-config.h" +#include "config/config.h" #include "koto-button.h" #include "koto-expander.h" diff --git a/src/koto-nav.c b/src/koto-nav.c index 94ca2b4..af1c4c0 100644 --- a/src/koto-nav.c +++ b/src/koto-nav.c @@ -19,7 +19,7 @@ #include "db/cartographer.h" #include "indexer/structs.h" #include "playlist/playlist.h" -#include "koto-config.h" +#include "config/config.h" #include "koto-button.h" #include "koto-expander.h" #include "koto-nav.h" @@ -327,7 +327,6 @@ void koto_nav_handle_playlist_removed( (void) carto; KotoNav * self = user_data; - if (!g_hash_table_contains(self->playlist_buttons, playlist_uuid)) { // Does not contain this return; } diff --git a/src/koto-paths.c b/src/koto-paths.c new file mode 100644 index 0000000..49561b9 --- /dev/null +++ b/src/koto-paths.c @@ -0,0 +1,50 @@ +/* koto-paths.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include "koto-paths.h" + +gchar * koto_rev_dns; +gchar * koto_path_cache; +gchar * koto_path_config; + +gchar * koto_path_to_conf; +gchar * koto_path_to_db; + +void koto_paths_setup() { + koto_rev_dns = "com.github.joshstrobl.koto"; + const gchar *user_cache_dir = g_get_user_data_dir(); + const gchar * user_config_dir = g_get_user_config_dir(); + + koto_path_cache = g_build_path(G_DIR_SEPARATOR_S, user_cache_dir, koto_rev_dns,NULL); + koto_path_config = g_build_path(G_DIR_SEPARATOR_S, user_config_dir, koto_rev_dns, NULL); + koto_path_to_conf = g_build_filename(koto_path_config, "config.toml", NULL); + koto_path_to_db = g_build_filename( koto_path_cache, "db", NULL); + + mkdir(user_cache_dir, 0755); + mkdir(user_config_dir, 0755); + mkdir(koto_path_cache, 0755); + mkdir(koto_path_config, 0755); + + chown(user_cache_dir, getuid(), getgid()); + chown(user_config_dir, getuid(), getgid()); + chown(koto_path_cache, getuid(), getgid()); + chown(koto_path_config, getuid(), getgid()); +} \ No newline at end of file diff --git a/src/koto-paths.h b/src/koto-paths.h new file mode 100644 index 0000000..a791c88 --- /dev/null +++ b/src/koto-paths.h @@ -0,0 +1,20 @@ +/* koto-paths.h + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +void koto_paths_setup(); \ No newline at end of file diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c index 580f1d9..432794a 100644 --- a/src/koto-playerbar.c +++ b/src/koto-playerbar.c @@ -17,18 +17,20 @@ #include #include +#include "config/config.h" #include "db/cartographer.h" #include "playlist/add-remove-track-popover.h" #include "playlist/current.h" #include "playlist/playlist.h" #include "playback/engine.h" #include "koto-button.h" -#include "koto-config.h" +#include "config/config.h" #include "koto-playerbar.h" extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup; -extern KotoCurrentPlaylist * current_playlist; extern KotoCartographer * koto_maps; +extern KotoConfig *config; +extern KotoCurrentPlaylist * current_playlist; extern KotoPlaybackEngine * playback_engine; struct _KotoPlayerBar { @@ -86,7 +88,6 @@ static void koto_playerbar_class_init(KotoPlayerBarClass * c) { static void koto_playerbar_constructed(GObject * obj) { KotoPlayerBar * self = KOTO_PLAYERBAR(obj); - self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_widget_add_css_class(self->main, "player-bar"); @@ -135,8 +136,13 @@ static void koto_playerbar_constructed(GObject * obj) { gtk_widget_set_hexpand(GTK_WIDGET(self->main), TRUE); + // Set up our volume and other playback state bits from our config + + koto_playerbar_apply_configuration_state(config, 0, self); + // Set up the bindings + g_signal_connect(config, "notify::playback-last-used-volume", G_CALLBACK(koto_playerbar_apply_configuration_state), self); // Bind onto the playback last used volume config option g_signal_connect(playback_engine, "is-playing", G_CALLBACK(koto_playerbar_handle_is_playing), self); g_signal_connect(playback_engine, "is-paused", G_CALLBACK(koto_playerbar_handle_is_paused), self); g_signal_connect(playback_engine, "tick-duration", G_CALLBACK(koto_playerbar_handle_tick_duration), self); @@ -155,6 +161,23 @@ KotoPlayerBar * koto_playerbar_new(void) { return g_object_new(KOTO_TYPE_PLAYERBAR, NULL); } +void koto_playerbar_apply_configuration_state( + KotoConfig * config, + guint prop_id, + KotoPlayerBar * self +) { + (void) prop_id; + gdouble config_last_used_volume = 0; + g_object_get( + config, + "playback-last-used-volume", + &config_last_used_volume, + NULL + ); + + gtk_scale_button_set_value(GTK_SCALE_BUTTON(self->volume_button), config_last_used_volume); +} + void koto_playerbar_create_playback_details(KotoPlayerBar* bar) { bar->playback_details_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); @@ -248,7 +271,6 @@ void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) { gtk_box_append(GTK_BOX(bar->secondary_controls_section), bar->volume_button); g_signal_connect(GTK_SCALE_BUTTON(bar->volume_button), "value-changed", G_CALLBACK(koto_playerbar_handle_volume_button_change), bar); - } } diff --git a/src/koto-playerbar.h b/src/koto-playerbar.h index 465e867..115be71 100644 --- a/src/koto-playerbar.h +++ b/src/koto-playerbar.h @@ -29,6 +29,12 @@ G_DECLARE_FINAL_TYPE(KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GObject) KotoPlayerBar * koto_playerbar_new(void); GtkWidget * koto_playerbar_get_main(KotoPlayerBar* bar); +void koto_playerbar_apply_configuration_state( + KotoConfig * config, + guint prop_id, + KotoPlayerBar * self +); + void koto_playerbar_create_playback_details(KotoPlayerBar* bar); void koto_playerbar_create_primary_controls(KotoPlayerBar* bar); diff --git a/src/koto-utils.c b/src/koto-utils.c index 6ff15f5..13de9fa 100644 --- a/src/koto-utils.c +++ b/src/koto-utils.c @@ -65,6 +65,10 @@ GtkWidget * koto_utils_create_image_from_filepath( return image; } +gchar * koto_utils_gboolean_to_string(gboolean b) { + return g_strdup(b ? "true" : "false"); +} + gchar * koto_utils_get_filename_without_extension(gchar * filename) { gchar * trimmed_file_name = g_strdup(filename); gchar ** split = g_strsplit(filename, ".", -1); // Split every time we see . diff --git a/src/koto-utils.h b/src/koto-utils.h index 1bc3a3e..d7f4630 100644 --- a/src/koto-utils.h +++ b/src/koto-utils.h @@ -30,6 +30,8 @@ GtkWidget * koto_utils_create_image_from_filepath( guint height ); +gchar * koto_utils_gboolean_to_string(gboolean b); + gchar * koto_utils_get_filename_without_extension(gchar * filename); gboolean koto_utils_is_string_valid(gchar * str); diff --git a/src/koto-window.c b/src/koto-window.c index 7a5b703..2f2bee1 100644 --- a/src/koto-window.c +++ b/src/koto-window.c @@ -25,20 +25,24 @@ #include "playlist/add-remove-track-popover.h" #include "playlist/current.h" #include "playlist/create-modify-dialog.h" -#include "koto-config.h" +#include "config/config.h" #include "koto-dialog-container.h" #include "koto-nav.h" #include "koto-playerbar.h" +#include "koto-paths.h" #include "koto-window.h" extern KotoActionBar * action_bar; extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup; extern KotoCartographer * koto_maps; extern KotoCreateModifyPlaylistDialog * playlist_create_modify_dialog; +extern KotoConfig * config; extern KotoCurrentPlaylist * current_playlist; extern KotoPageMusicLocal * music_local_page; extern KotoPlaybackEngine * playback_engine; +extern gchar * koto_rev_dns; + struct _KotoWindow { GtkApplicationWindow parent_instance; KotoLibrary * library; @@ -46,6 +50,8 @@ struct _KotoWindow { KotoDialogContainer * dialogs; + GtkCssProvider * provider; + GtkWidget * overlay; GtkWidget * header_bar; GtkWidget * menu_button; @@ -69,11 +75,11 @@ static void koto_window_init (KotoWindow * self) { current_playlist = koto_current_playlist_new(); playback_engine = koto_playback_engine_new(); - GtkCssProvider* provider = gtk_css_provider_new(); - - - gtk_css_provider_load_from_resource(provider, "/com/github/joshstrobl/koto/style.css"); - gtk_style_context_add_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + self->provider = gtk_css_provider_new(); + gtk_css_provider_load_from_resource(self->provider, "/com/github/joshstrobl/koto/style.css"); + koto_window_manage_style(config, 0, self); // Immediately apply the theme + g_signal_connect(config, "notify::ui-theme-desired", G_CALLBACK(koto_window_manage_style), self); // Handle changes to desired theme + g_signal_connect(config, "notify::ui-theme-override", G_CALLBACK(koto_window_manage_style), self); // Handle changes to theme overriding create_new_headerbar(self); // Create our headerbar @@ -137,9 +143,10 @@ static void koto_window_init (KotoWindow * self) { #endif gtk_window_set_title(GTK_WINDOW(self), "Koto"); gtk_window_set_icon_name(GTK_WINDOW(self), "audio-headphones"); - gtk_window_set_startup_id(GTK_WINDOW(self), "com.github.joshstrobl.koto"); + gtk_window_set_startup_id(GTK_WINDOW(self), koto_rev_dns); gtk_widget_queue_draw(self->content_layout); + g_thread_new("load-library", (void*) load_library, self); } @@ -151,6 +158,61 @@ void koto_window_add_page( gtk_stack_add_named(GTK_STACK(self->pages), page, page_name); } +void koto_window_manage_style(KotoConfig * c, guint prop_id, KotoWindow * self) { + (void) prop_id; + + if (!KOTO_IS_WINDOW(self)) { // Not a Koto Window + g_warning("Not a window"); + } + + gchar * desired_theme = NULL; + gboolean overriding_theme = FALSE; + g_object_get( + c, + "ui-theme-desired", + &desired_theme, + "ui-theme-override", + &overriding_theme, + NULL + ); + + if (!koto_utils_is_string_valid(desired_theme)) { // Theme not valid + desired_theme = "dark"; + } + + GtkStyleContext * context = gtk_widget_get_style_context(GTK_WIDGET(self)); + + if (!overriding_theme) { // If we are not overriding the theme + if (!gtk_style_context_has_class(context, "koto-theme-dark")) { // Don't have our css class for a theme + gtk_style_context_add_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(self->provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + } + + GList * themes = NULL; + themes = g_list_append(themes, "dark"); + themes = g_list_append(themes, "gruvbox"); + themes = g_list_append(themes, "light"); + + GList *themes_head; + + for (themes_head = themes; themes_head != NULL; themes_head = themes_head->next) { // For each theme + gchar * theme_class_name = g_strdup_printf("koto-theme-%s", (gchar *) themes->data); // Get the theme + + if (g_strcmp0((gchar *) themes->data, desired_theme) == 0) { // If we are using this theme + gtk_widget_add_css_class(GTK_WIDGET(self), theme_class_name); // Add the CSS class + } else { + gtk_widget_remove_css_class(GTK_WIDGET(self), theme_class_name); // Remove the CSS class + } + + g_free(theme_class_name); // Free the CSS class + } + + g_list_free(themes_head); + g_list_free(themes); + } else { // Overriding the built-in theme + gtk_style_context_remove_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(self->provider)); // Remove the provider + } +} + void koto_window_go_to_page( KotoWindow * self, gchar * page_name diff --git a/src/koto-window.h b/src/koto-window.h index 7043fc5..30896a2 100644 --- a/src/koto-window.h +++ b/src/koto-window.h @@ -18,6 +18,7 @@ #pragma once #include +#include "config/config.h" #include "db/cartographer.h" #include "playlist/playlist.h" @@ -44,6 +45,12 @@ void koto_window_handle_playlist_added( gpointer user_data ); +void koto_window_manage_style( + KotoConfig * config, + guint prop_id, + KotoWindow * self +); + void koto_window_hide_dialogs(KotoWindow * self); void koto_window_remove_page( diff --git a/src/main.c b/src/main.c index 687ec62..be06bad 100644 --- a/src/main.c +++ b/src/main.c @@ -14,18 +14,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#include +#include #include +#include "config/config.h" #include "db/cartographer.h" #include "db/db.h" #include "playback/media-keys.h" #include "playback/mimes.h" #include "playback/mpris.h" +#include "paths.h" -#include "koto-config.h" +#include "config/config.h" +#include "koto-paths.h" #include "koto-window.h" +extern KotoConfig * config; + extern guint mpris_bus_id; extern GDBusNodeInfo * introspection_data; @@ -35,6 +39,9 @@ extern sqlite3 * koto_db; extern GHashTable * supported_mimes_hash; extern GList * supported_mimes; +extern gchar * koto_path_to_conf; +extern gchar * koto_rev_dns; + GtkApplication * app = NULL; GtkWindow * main_window; @@ -53,6 +60,7 @@ static void on_activate (GtkApplication * app) { static void on_shutdown(GtkApplication * app) { (void) app; + koto_config_save(config); // Save our config close_db(); // Close the database g_bus_unown_name(mpris_bus_id); g_dbus_node_info_unref(introspection_data); @@ -64,23 +72,22 @@ int main ( ) { int ret; - - /* Set up gettext translations */ - bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); - bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); - textdomain(GETTEXT_PACKAGE); - gtk_init(); gst_init(&argc, &argv); + koto_paths_setup(); // Set up our required paths + supported_mimes_hash = g_hash_table_new(g_str_hash, g_str_equal); supported_mimes = NULL; // Ensure our mimes GList is initialized koto_playback_engine_get_supported_mimetypes(supported_mimes); koto_maps = koto_cartographer_new(); // Create our new cartographer and their collection of maps + + config = koto_config_new(); // Set our config + koto_config_load(config, koto_path_to_conf); open_db(); // Open our database - app = gtk_application_new("com.github.joshstrobl.koto", G_APPLICATION_FLAGS_NONE); + app = gtk_application_new(koto_rev_dns, G_APPLICATION_FLAGS_NONE); g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL); g_signal_connect(app, "shutdown", G_CALLBACK(on_shutdown), NULL); ret = g_application_run(G_APPLICATION(app), argc, argv); diff --git a/src/meson.build b/src/meson.build index 8e22d0b..514adc2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,8 +1,13 @@ -add_project_arguments('-Db_sanitize=address', language: 'c') +add_global_arguments([ + '-I' + meson.current_build_dir(), + '-Db_sanitize=address', + '-Dwerror=true', +], language: 'c') koto_sources = [ 'components/koto-action-bar.c', 'components/koto-cover-art-button.c', + 'config/config.c', 'db/cartographer.c', 'db/db.c', 'indexer/album.c', @@ -28,6 +33,7 @@ koto_sources = [ 'koto-expander.c', 'koto-nav.c', 'koto-playerbar.c', + 'koto-paths.c', 'koto-track-item.c', 'koto-utils.c', 'koto-window.c', @@ -42,6 +48,7 @@ koto_deps = [ dependency('libmagic', version: '>=5.39'), dependency('sqlite3', version: '>=3.34'), dependency('taglib_c', version: '>=1.11'), + toml_dep, ] gnome = import('gnome') diff --git a/src/pages/music/album-view.c b/src/pages/music/album-view.c index bcd17e1..05718ac 100644 --- a/src/pages/music/album-view.c +++ b/src/pages/music/album-view.c @@ -17,12 +17,13 @@ #include #include +#include "../../components/koto-cover-art-button.h" #include "../../db/cartographer.h" #include "../../indexer/structs.h" #include "../../koto-button.h" #include "album-view.h" #include "disc-view.h" -#include "koto-config.h" +#include "config/config.h" #include "koto-utils.h" extern KotoCartographer * koto_maps; @@ -34,11 +35,7 @@ struct _KotoAlbumView { GtkWidget * album_tracks_box; GtkWidget * discs; - GtkWidget * album_overlay_art; - GtkWidget * album_overlay_container; - GtkWidget * album_overlay_controls; - GtkWidget * album_overlay_revealer; - KotoButton * play_pause_button; + KotoCoverArtButton *album_cover; GtkWidget * album_label; GHashTable * cd_to_track_listbox; @@ -107,35 +104,10 @@ static void koto_album_view_init(KotoAlbumView * self) { gtk_box_append(GTK_BOX(self->main), self->album_tracks_box); // Add the tracks box to the art info combo box gtk_box_append(GTK_BOX(self->album_tracks_box), self->discs); // Add the discs list box to the albums tracks box - self->album_overlay_container = gtk_overlay_new(); // Create our overlay container - gtk_widget_set_valign(self->album_overlay_container, GTK_ALIGN_START); // Align to top of list for album - - self->album_overlay_art = koto_utils_create_image_from_filepath(NULL, "audio-x-generic-symbolic", 220, 220); - gtk_overlay_set_child(GTK_OVERLAY(self->album_overlay_container), self->album_overlay_art); // Add our art as the "child" for the overlay - - self->album_overlay_revealer = gtk_revealer_new(); // Create a new revealer - gtk_revealer_set_transition_type(GTK_REVEALER(self->album_overlay_revealer), GTK_REVEALER_TRANSITION_TYPE_CROSSFADE); - gtk_revealer_set_transition_duration(GTK_REVEALER(self->album_overlay_revealer), 400); - - self->album_overlay_controls = gtk_center_box_new(); // Create a center box for the controls - - self->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->album_overlay_controls), GTK_WIDGET(self->play_pause_button)); - - gtk_revealer_set_child(GTK_REVEALER(self->album_overlay_revealer), self->album_overlay_controls); - koto_album_view_hide_overlay_controls(NULL, self); // Hide by default - - gtk_overlay_add_overlay(GTK_OVERLAY(self->album_overlay_container), self->album_overlay_revealer); // Add our revealer as the overlay - gtk_box_prepend(GTK_BOX(self->main), self->album_overlay_container); // Add our album overlay container - - GtkEventController * motion_controller = gtk_event_controller_motion_new(); // Create our new motion event controller to track mouse leave and enter - - - g_signal_connect(motion_controller, "enter", G_CALLBACK(koto_album_view_show_overlay_controls), self); - g_signal_connect(motion_controller, "leave", G_CALLBACK(koto_album_view_hide_overlay_controls), self); - gtk_widget_add_controller(self->album_overlay_container, motion_controller); - - koto_button_add_click_handler(self->play_pause_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_album_view_toggle_album_playback), self); + self->album_cover = koto_cover_art_button_new(220, 220, NULL); + gtk_box_prepend(GTK_BOX(self->main), koto_cover_art_button_get_main(self->album_cover)); + KotoButton * cover_art_button = koto_cover_art_button_get_button(self->album_cover); // Get the button for the cover art + koto_button_add_click_handler(cover_art_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_album_view_toggle_album_playback), self); } GtkWidget * koto_album_view_get_main(KotoAlbumView * self) { @@ -169,7 +141,6 @@ static void koto_album_view_set_property( ) { KotoAlbumView * self = KOTO_ALBUM_VIEW(obj); - switch (prop_id) { case PROP_ALBUM: koto_album_view_set_album(self, (KotoAlbum*) g_value_get_object(val)); @@ -188,17 +159,6 @@ void koto_album_view_add_track_to_listbox( (void) track; } -void koto_album_view_hide_overlay_controls( - GtkEventControllerFocus * controller, - gpointer data -) { - (void) controller; - KotoAlbumView* self = data; - - - gtk_revealer_set_reveal_child(GTK_REVEALER(self->album_overlay_revealer), FALSE); -} - void koto_album_view_set_album( KotoAlbumView * self, KotoAlbum * album @@ -210,13 +170,9 @@ void koto_album_view_set_album( self->album = album; gchar * album_art = koto_album_get_album_art(self->album); // Get the art for the album - - - gtk_image_set_from_file(GTK_IMAGE(self->album_overlay_art), album_art); + koto_cover_art_button_set_art_path(self->album_cover, album_art); gchar * album_name; - - g_object_get(album, "name", &album_name, NULL); // Get the album name self->album_label = gtk_label_new(album_name); @@ -226,7 +182,6 @@ void koto_album_view_set_album( GHashTable * discs = g_hash_table_new(g_str_hash, g_str_equal); GList * tracks = koto_album_get_tracks(album); // Get the tracks for this album - for (guint i = 0; i < g_list_length(tracks); i++) { KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) g_list_nth_data(tracks, i)); // Get the track by its UUID @@ -258,17 +213,6 @@ void koto_album_view_set_album( g_hash_table_destroy(discs); } -void koto_album_view_show_overlay_controls( - GtkEventControllerFocus * controller, - gpointer data -) { - (void) controller; - KotoAlbumView* self = data; - - - gtk_revealer_set_reveal_child(GTK_REVEALER(self->album_overlay_revealer), TRUE); -} - int koto_album_view_sort_discs( GtkListBoxRow * disc1, GtkListBoxRow * disc2, @@ -307,8 +251,6 @@ void koto_album_view_toggle_album_playback( (void) y; KotoAlbumView* self = data; - - koto_button_show_image(KOTO_BUTTON(self->play_pause_button), TRUE); koto_album_set_as_current_playlist(self->album); // Set as the current playlist } diff --git a/src/pages/music/album-view.h b/src/pages/music/album-view.h index 0d8610f..bf1fe68 100644 --- a/src/pages/music/album-view.h +++ b/src/pages/music/album-view.h @@ -35,21 +35,11 @@ void koto_album_view_add_track_to_listbox( KotoTrack * track ); -void koto_album_view_hide_overlay_controls( - GtkEventControllerFocus * controller, - gpointer data -); - void koto_album_view_set_album( KotoAlbumView * self, KotoAlbum * album ); -void koto_album_view_show_overlay_controls( - GtkEventControllerFocus * controller, - gpointer data -); - void koto_album_view_toggle_album_playback( GtkGestureClick * gesture, int n_press, diff --git a/src/pages/music/artist-view.c b/src/pages/music/artist-view.c index b693bb4..84f64c3 100644 --- a/src/pages/music/artist-view.c +++ b/src/pages/music/artist-view.c @@ -21,7 +21,7 @@ #include "../../indexer/structs.h" #include "album-view.h" #include "artist-view.h" -#include "koto-config.h" +#include "config/config.h" #include "koto-utils.h" extern KotoCartographer * koto_maps; diff --git a/src/pages/music/music-local.c b/src/pages/music/music-local.c index e732c46..91688a9 100644 --- a/src/pages/music/music-local.c +++ b/src/pages/music/music-local.c @@ -20,7 +20,7 @@ #include "../../db/cartographer.h" #include "../../indexer/structs.h" #include "koto-button.h" -#include "koto-config.h" +#include "config/config.h" #include "../../koto-utils.h" #include "music-local.h" diff --git a/src/pages/playlist/list.c b/src/pages/playlist/list.c index 76b7360..007732d 100644 --- a/src/pages/playlist/list.c +++ b/src/pages/playlist/list.c @@ -136,12 +136,10 @@ static void koto_playlist_page_init(KotoPlaylistPage * self) { self->playlist_image = koto_cover_art_button_new(220, 220, NULL); // Create our Cover Art Button with no art by default KotoButton * cover_art_button = koto_cover_art_button_get_button(self->playlist_image); // Get the button for the cover art - koto_button_add_click_handler(cover_art_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_cover_art_clicked), self); GtkWidget * info_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_set_size_request(info_box, -1, 220); gtk_widget_add_css_class(info_box, "playlist-page-header-info"); gtk_widget_set_hexpand(info_box, TRUE); diff --git a/src/playback/engine.c b/src/playback/engine.c index cefda87..fcb6ff7 100644 --- a/src/playback/engine.c +++ b/src/playback/engine.c @@ -19,9 +19,11 @@ #include #include #include +#include "../config/config.h" #include "../db/cartographer.h" #include "../playlist/current.h" #include "../indexer/structs.h" +#include "../koto-paths.h" #include "../koto-utils.h" #include "engine.h" #include "mpris.h" @@ -38,11 +40,14 @@ enum { N_SIGNALS }; +extern gchar * koto_rev_dns; + static guint playback_engine_signals[N_SIGNALS] = { 0 }; extern KotoCartographer * koto_maps; +extern KotoConfig * config; extern KotoCurrentPlaylist * current_playlist; KotoPlaybackEngine * playback_engine; @@ -64,10 +69,14 @@ struct _KotoPlaybackEngine { gboolean is_playing; gboolean is_playing_specific_track; + gboolean is_shuffle_enabled; gboolean tick_duration_timer_running; gboolean tick_track_timer_running; + gboolean via_config_continue_on_playlist; // Pulls from our Koto Config + gboolean via_config_maintain_shuffle; // Pulls from our Koto Config + guint playback_position; gdouble volume; }; @@ -89,10 +98,7 @@ G_DEFINE_TYPE(KotoPlaybackEngine, koto_playback_engine, G_TYPE_OBJECT); static void koto_playback_engine_class_init(KotoPlaybackEngineClass * c) { c->play_state_changed = NULL; - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); playback_engine_signals[SIGNAL_IS_PLAYING] = g_signal_new( @@ -218,11 +224,51 @@ static void koto_playback_engine_init(KotoPlaybackEngine * self) { self->is_playing = FALSE; self->is_playing_specific_track = FALSE; self->is_repeat_enabled = FALSE; + self->is_shuffle_enabled = FALSE; self->tick_duration_timer_running = FALSE; self->tick_track_timer_running = FALSE; + self->via_config_continue_on_playlist = FALSE; + self->via_config_maintain_shuffle = TRUE; + + koto_playback_engine_apply_configuration_state(NULL, 0, self); // Apply configuration immediately + + g_signal_connect(config, "notify::playback-continue-on-playlist", G_CALLBACK(koto_playback_engine_apply_configuration_state), self); // Handle changes to our config + g_signal_connect(config, "notify::last-used-volume", G_CALLBACK(koto_playback_engine_apply_configuration_state), self); // Handle changes to our config + g_signal_connect(config, "notify::maintain-shuffle", G_CALLBACK(koto_playback_engine_apply_configuration_state), self); // Handle changes to our config if (KOTO_IS_CURRENT_PLAYLIST(current_playlist)) { - g_signal_connect(current_playlist, "notify::current-playlist", G_CALLBACK(koto_playback_engine_current_playlist_changed), NULL); + g_signal_connect(current_playlist, "notify::current-playlist", G_CALLBACK(koto_playback_engine_current_playlist_changed), self); + } +} + +void koto_playback_engine_apply_configuration_state( + KotoConfig * c, + guint prop_id, + KotoPlaybackEngine * self +) { + (void) c; + (void) prop_id; + + gboolean config_continue_on_playlist = self->via_config_continue_on_playlist; + gdouble config_last_used_volume = 0.5; + gboolean config_maintain_shuffle = self->via_config_maintain_shuffle; + + g_object_get( + config, + "playback-continue-on-playlist", + &config_continue_on_playlist, + "playback-last-used-volume", + &config_last_used_volume, + "playback-maintain-shuffle", + &config_maintain_shuffle, + NULL + ); + + self->via_config_continue_on_playlist = config_continue_on_playlist; + self->via_config_maintain_shuffle = config_maintain_shuffle; + + if (self->volume != config_last_used_volume) { // Not the same volume + koto_playback_engine_set_volume(self, config_last_used_volume); } } @@ -238,36 +284,46 @@ void koto_playback_engine_backwards(KotoPlaybackEngine * self) { return; } - koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_previous(playlist)); + koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_previous(playlist), FALSE); } -void koto_playback_engine_current_playlist_changed() { - if (!KOTO_IS_PLAYBACK_ENGINE(playback_engine)) { +void koto_playback_engine_current_playlist_changed(KotoCurrentPlaylist * current_pl, guint prop_id, KotoPlaybackEngine *self) { + (void) current_pl; + (void) prop_id; + + if (!KOTO_IS_PLAYBACK_ENGINE(self)) { return; } KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist - if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently return; } - koto_playback_engine_set_track_by_uuid(playback_engine, koto_playlist_go_to_next(playlist)); // Go to "next" which is the first track + if (self->is_shuffle_enabled) { // If shuffle is enabled + koto_playback_engine_set_track_shuffle(self, self->via_config_maintain_shuffle); // Set to our maintain shuffle value + } + + koto_playback_engine_set_track_by_uuid(playback_engine, koto_playlist_go_to_next(playlist), FALSE); // Go to "next" which is the first track } void koto_playback_engine_forwards(KotoPlaybackEngine * self) { KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist - if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently return; } if (self->is_repeat_enabled) { // Is repeat enabled koto_playback_engine_set_position(self, 0); // Set position back to 0 to repeat the track - } else if (!self->is_repeat_enabled && !self->is_playing_specific_track) { // Repeat not enabled and we are not playing a specific track - koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist)); + } else { // If repeat is not enabled + if ( + (self->via_config_continue_on_playlist && self->is_playing_specific_track) || // Playing a specific track and wanting to continue on the playlist + (!self->is_playing_specific_track) // Not playing a specific track + ) { + koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist), FALSE); + } } } @@ -314,20 +370,11 @@ gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine * self) { } gboolean koto_playback_engine_get_track_shuffle(KotoPlaybackEngine * self) { - (void) self; - KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); + return self->is_shuffle_enabled; +} - - if (!KOTO_IS_PLAYLIST(playlist)) { // Don't have a playlist currently - return FALSE; - } - - gboolean currently_shuffling = FALSE; - - - g_object_get(playlist, "is-shuffle-enabled", ¤tly_shuffling, NULL); // Get the current is-shuffle-enabled - - return currently_shuffling; +gdouble koto_playback_engine_get_volume(KotoPlaybackEngine * self) { + return self->volume; } gboolean koto_playback_engine_monitor_changed( @@ -419,18 +466,19 @@ void koto_playback_engine_set_track_shuffle( ) { KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); - if (!KOTO_IS_PLAYLIST(playlist)) { // Don't have a playlist currently return; } - g_object_set(playlist, "is-shuffle-enabled", enable_shuffle, NULL); // Set the is-shuffle-enabled on any existing playlist + self->is_shuffle_enabled = enable_shuffle; + g_object_set(playlist, "is-shuffle-enabled", self->is_shuffle_enabled, NULL); // Set the is-shuffle-enabled on any existing playlist g_signal_emit(self, playback_engine_signals[SIGNAL_TRACK_SHUFFLE_CHANGE], 0); // Emit our track shuffle changed event } void koto_playback_engine_set_track_by_uuid( KotoPlaybackEngine * self, - gchar * track_uuid + gchar * track_uuid, + gboolean playing_specific_track ) { if (track_uuid == NULL) { return; @@ -438,7 +486,6 @@ void koto_playback_engine_set_track_by_uuid( KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track from cartographer - if (!KOTO_IS_TRACK(track)) { // Not a track return; } @@ -447,13 +494,13 @@ void koto_playback_engine_set_track_by_uuid( gchar * track_file_path = NULL; - g_object_get(track, "path", &track_file_path, NULL); // Get the path to the track koto_playback_engine_stop(self); // Stop current track - gchar * gst_filename = gst_filename_to_uri(track_file_path, NULL); // Get the gst supported file naem + self->is_playing_specific_track = playing_specific_track; + gchar * gst_filename = gst_filename_to_uri(track_file_path, NULL); // Get the gst supported file naem g_object_set(self->playbin, "uri", gst_filename, NULL); g_free(gst_filename); // Free the filename @@ -467,7 +514,6 @@ void koto_playback_engine_set_track_by_uuid( GVariant * metadata = koto_track_get_metadata_vardict(track); // Get the GVariantBuilder variable dict for the metadata GVariantDict * metadata_dict = g_variant_dict_new(metadata); - g_signal_emit(self, playback_engine_signals[SIGNAL_TRACK_CHANGE], 0); // Emit our track change signal koto_update_mpris_info_for_track(self->current_track); @@ -484,7 +530,6 @@ void koto_playback_engine_set_track_by_uuid( gchar * icon_name = "audio-x-generic-symbolic"; - if (g_variant_dict_contains(metadata_dict, "mpris:artUrl")) { // If we have artwork specified GVariant * art_url_var = g_variant_dict_lookup_value(metadata_dict, "mpris:artUrl", NULL); // Get the GVariant for the art URL const gchar * art_uri = g_variant_get_string(art_url_var, NULL); // Get the string for the artwork @@ -493,19 +538,22 @@ void koto_playback_engine_set_track_by_uuid( // Super important note: We are not using libnotify directly because the synchronous nature of notify_notification_send seems to result in dbus timeouts if (g_find_program_in_path("notify-send") != NULL) { // Have notify-send - char * argv[12]; - argv[0] = "notify-send"; - argv[1] = "-a"; - argv[2] = "Koto"; - argv[3] = "-i"; - argv[4] = icon_name; - argv[5] = "-c"; - argv[6] = "x-gnome.music"; - argv[7] = "-h"; - argv[8] = "string:desktop-entry:com.github.joshstrobl.koto"; - argv[9] = g_strdup(track_name); - argv[10] = artist_album_combo; - argv[11] = NULL; + char * argv[14] = { + "notify-send", + "-a", + "Koto", + "-i", + icon_name, + "-c", + "x-gnome.music", + "-t", + "5000", + "-h", + g_strdup_printf("string:desktop-entry:%s", koto_rev_dns), + g_strdup(track_name), + artist_album_combo, + NULL + }; g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); } @@ -556,7 +604,6 @@ gboolean koto_playback_engine_tick_duration(gpointer user_data) { gboolean koto_playback_engine_tick_track(gpointer user_data) { KotoPlaybackEngine * self = user_data; - if (self->is_playing) { // Is playing g_signal_emit(self, playback_engine_signals[SIGNAL_TICK_TRACK], 0); // Emit our 100ms track tick } else { @@ -571,7 +618,7 @@ void koto_playback_engine_toggle_track_repeat(KotoPlaybackEngine * self) { } void koto_playback_engine_toggle_track_shuffle(KotoPlaybackEngine * self) { - koto_playback_engine_set_track_shuffle(self, !koto_playback_engine_get_track_shuffle(self)); // Invert the currently shuffling vale + koto_playback_engine_set_track_shuffle(self, !self->is_shuffle_enabled); // Invert the currently shuffling vale } KotoPlaybackEngine * koto_playback_engine_new() { diff --git a/src/playback/engine.h b/src/playback/engine.h index 4f99c3b..4a84515 100644 --- a/src/playback/engine.h +++ b/src/playback/engine.h @@ -19,6 +19,7 @@ #include #include #include +#include "../config/config.h" #include "../playlist/current.h" G_BEGIN_DECLS @@ -43,9 +44,15 @@ GType koto_playback_engine_get_type(void) G_GNUC_CONST; KotoPlaybackEngine * koto_playback_engine_new(); +void koto_playback_engine_apply_configuration_state( + KotoConfig * config, + guint prop_id, + KotoPlaybackEngine * self +); + void koto_playback_engine_backwards(KotoPlaybackEngine * self); -void koto_playback_engine_current_playlist_changed(); +void koto_playback_engine_current_playlist_changed(KotoCurrentPlaylist * current_pl, guint prop_id, KotoPlaybackEngine *self); void koto_playback_engine_forwards(KotoPlaybackEngine * self); @@ -61,6 +68,8 @@ gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine * self); gboolean koto_playback_engine_get_track_shuffle(KotoPlaybackEngine * self); +gdouble koto_playback_engine_get_volume(KotoPlaybackEngine * self); + void koto_playback_engine_mute(KotoPlaybackEngine * self); gboolean koto_playback_engine_monitor_changed( @@ -92,7 +101,8 @@ void koto_playback_engine_set_track_shuffle( void koto_playback_engine_set_track_by_uuid( KotoPlaybackEngine * self, - gchar * track_uuid + gchar * track_uuid, + gboolean playing_specific_track ); void koto_playback_engine_set_volume( diff --git a/src/playback/media-keys.c b/src/playback/media-keys.c index 8578a35..6a1c8d8 100644 --- a/src/playback/media-keys.c +++ b/src/playback/media-keys.c @@ -20,9 +20,11 @@ #include #include "engine.h" #include "media-keys.h" +#include "../koto-paths.h" extern GtkWindow * main_window; extern KotoPlaybackEngine * playback_engine; +extern gchar * koto_rev_dns; GDBusConnection * media_keys_dbus_conn = NULL; GDBusProxy * media_keys_proxy = NULL; @@ -54,7 +56,7 @@ void grab_media_keys() { g_dbus_proxy_call( media_keys_proxy, "GrabMediaPlayerKeys", - g_variant_new("(su)", "com.github.joshstrobl.koto", 0), + g_variant_new("(su)", koto_rev_dns, 0), G_DBUS_CALL_FLAGS_NONE, -1, NULL, @@ -92,7 +94,7 @@ void handle_media_keys_signal( g_variant_get(parameters, "(ss)", &application_name, &key); - if (g_strcmp0(application_name, "com.github.joshstrobl.koto") != 0) { // Not for Koto + if (g_strcmp0(application_name, koto_rev_dns) != 0) { // Not for Koto return; } @@ -136,7 +138,7 @@ void release_media_keys() { return; } - GVariant * params = g_variant_new_string(g_strdup("com.github.joshstrobl.koto")); + GVariant * params = g_variant_new_string(g_strdup(koto_rev_dns)); g_dbus_proxy_call( diff --git a/src/playback/mpris.c b/src/playback/mpris.c index 21c1a09..98c647d 100644 --- a/src/playback/mpris.c +++ b/src/playback/mpris.c @@ -23,11 +23,13 @@ #include "../db/cartographer.h" #include "../playlist/current.h" #include "../playlist/playlist.h" +#include "../koto-paths.h" #include "../koto-utils.h" #include "engine.h" #include "mimes.h" #include "mpris.h" +extern gchar * koto_rev_dns; extern KotoCartographer * koto_maps; extern KotoCurrentPlaylist * current_playlist; extern GtkApplication * app; @@ -187,7 +189,7 @@ GVariant * handle_get_property( } if (g_strcmp0(property_name, "DesktopEntry") == 0) { // Desktop Entry - ret = g_variant_new_string("com.github.joshstrobl.koto"); + ret = g_variant_new_string(koto_rev_dns); } if (g_strcmp0(property_name, "SupportedUriSchemas") == 0) { // Supported URI Schemas diff --git a/src/playlist/add-remove-track-popover.c b/src/playlist/add-remove-track-popover.c index 1c3c4c8..68c284d 100644 --- a/src/playlist/add-remove-track-popover.c +++ b/src/playlist/add-remove-track-popover.c @@ -193,7 +193,6 @@ void koto_add_remove_track_popover_handle_playlist_removed( (void) carto; KotoAddRemoveTrackPopover * self = user_data; - if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) { return; } diff --git a/src/playlist/playlist.c b/src/playlist/playlist.c index a666ef1..5109106 100644 --- a/src/playlist/playlist.c +++ b/src/playlist/playlist.c @@ -309,14 +309,12 @@ void koto_playlist_add_track_by_uuid( ) { KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, uuid); // Get the track - if (!KOTO_IS_TRACK(track)) { return; } GList * found_tracks_uuids = g_queue_find_custom(self->tracks, uuid, koto_playlist_compare_track_uuids); - if (found_tracks_uuids != NULL) { // Is somewhere in the tracks already g_list_free(found_tracks_uuids); return; @@ -354,7 +352,6 @@ void koto_playlist_apply_model( ) { GList * sort_user_data = NULL; - sort_user_data = g_list_prepend(sort_user_data, GUINT_TO_POINTER(preferred_model)); // Prepend our preferred model first sort_user_data = g_list_prepend(sort_user_data, self); // Prepend ourself @@ -362,10 +359,6 @@ void koto_playlist_apply_model( g_list_store_sort(self->store, koto_playlist_model_sort_by_track, sort_user_data); // Sort tracks by indexed tracks self->model = preferred_model; // Update our preferred model - - /*if (self->current_position != -1) { // Have a position set - koto_playlist_set_track_as_current(self, self->current_uuid); // Update the position based on the new model just by setting it as current again - }*/ } void koto_playlist_commit(KotoPlaylist * self) { @@ -476,7 +469,6 @@ gchar * koto_playlist_get_random_track(KotoPlaylist * self) { gchar * track_uuid = NULL; guint tracks_len = g_queue_get_length(self->sorted_tracks); - if (tracks_len == g_queue_get_length(self->played_tracks)) { // Played all tracks track_uuid = g_list_nth_data(self->sorted_tracks->head, 0); // Get the first g_queue_clear(self->played_tracks); // Clear our played tracks @@ -757,14 +749,12 @@ void koto_playlist_remove_track_by_uuid( gint file_index = g_queue_index(self->tracks, uuid); // Get the position of this uuid - if (file_index != -1) { // Have in tracks g_queue_pop_nth(self->tracks, file_index); // Remove nth where it is the file index } gint file_index_in_sorted = g_queue_index(self->sorted_tracks, uuid); // Get position in sorted tracks - if (file_index_in_sorted != -1) { // Have in sorted tracks g_queue_pop_nth(self->sorted_tracks, file_index_in_sorted); // Remove nth where it is the index in sorted tracks } @@ -873,8 +863,8 @@ void koto_playlist_set_track_as_current( ) { gint position_of_track = g_queue_index(self->sorted_tracks, track_uuid); // Get the position of the UUID in our tracks - if (position_of_track != -1) { // In tracks + self->current_uuid = track_uuid; self->current_position = position_of_track; } } diff --git a/theme/_disc-view.scss b/theme/_disc-view.scss index 624c83c..f1b18ae 100644 --- a/theme/_disc-view.scss +++ b/theme/_disc-view.scss @@ -1,22 +1,8 @@ @import "vars"; .disc-view { - & > box { // Horizontal box with image and disc labe + & > box { // Horizontal box with image and disc label color: #ccc; margin: 10px 0; } - - & .track-list { - & > row { - &:not(:active):not(:selected) { // Neither active nor selected, see gtk overrides - &:nth-child(odd):not(:hover) { - background-color: $midnight; - } - - &:nth-child(even), &:hover { - background-color: $grey; - } - } - } - } } diff --git a/theme/_player-bar.scss b/theme/_player-bar.scss index 4c4a26b..a41b4f1 100644 --- a/theme/_player-bar.scss +++ b/theme/_player-bar.scss @@ -1,20 +1,9 @@ @import 'vars'; .player-bar { - background-color: $midnight; background-image: none; padding: $halvedpadding; - .koto-button { - &:not(.toggled) { - color: $darkgrey; - } - - &.toggled { - color: white; - } - } - .playerbar-info { // Central info section & > box { // Info labels margin-left: 2ex; diff --git a/theme/main.scss b/theme/main.scss index e624686..6265502 100644 --- a/theme/main.scss +++ b/theme/main.scss @@ -1,7 +1,7 @@ -@import 'components/cover-art-button'; @import 'components/gtk-overrides'; @import 'pages/music-local'; @import 'pages/playlist-page'; +@import 'variants/dark/main'; @import 'button'; @import 'disc-view'; @@ -12,15 +12,11 @@ @import 'vars'; window { - background-color: $grey; - & > headerbar, & > headerbar:active { - background-color: $midnight; background-image: none; } .koto-dialog-container { - background-color: transparentize($midnight, 0.5); padding: 20px; } diff --git a/theme/meson.build b/theme/meson.build index 143c5f3..134c5db 100644 --- a/theme/meson.build +++ b/theme/meson.build @@ -8,18 +8,5 @@ theme = custom_target('Theme generation', [ '-a', '-M', '-t', 'compact' ], '@INPUT@', '@OUTPUT@', ], - depend_files: files([ - 'components/_cover-art-button.scss', - 'components/_gtk-overrides.scss', - 'pages/_music-local.scss', - 'pages/_playlist-page.scss', - '_button.scss', - '_disc-view.scss', - '_expander.scss', - '_player-bar.scss', - '_primary-nav.scss', - '_track-item.scss', - '_vars.scss', - ]), build_by_default: true, ) diff --git a/theme/pages/_music-local.scss b/theme/pages/_music-local.scss index cc0754f..5e37821 100644 --- a/theme/pages/_music-local.scss +++ b/theme/pages/_music-local.scss @@ -2,10 +2,6 @@ .page-music-local { & > .artist-list { - &, & > viewport, & > viewport > list { - background-color: $midnight; - } - & > viewport > list { & > row { padding: $halvedpadding; @@ -27,10 +23,6 @@ & > flowboxchild > .album-view { & > overlay { margin-right: $itempadding; - - & > revealer > box { // Inner controls - background-color: rgba(0,0,0,0.75); - } } & > box { diff --git a/theme/pages/_playlist-page.scss b/theme/pages/_playlist-page.scss index cb18ec3..dbd6924 100644 --- a/theme/pages/_playlist-page.scss +++ b/theme/pages/_playlist-page.scss @@ -58,10 +58,6 @@ & > .track-list-columned-item { // Track rows font-size: x-large; } - - &:nth-child(odd):not(:selected) { - background-color: $midnight; - } } } diff --git a/theme/components/_cover-art-button.scss b/theme/variants/dark/_cover-art-button.scss similarity index 81% rename from theme/components/_cover-art-button.scss rename to theme/variants/dark/_cover-art-button.scss index 6af84db..6ad7e75 100644 --- a/theme/components/_cover-art-button.scss +++ b/theme/variants/dark/_cover-art-button.scss @@ -1,3 +1,5 @@ +@import '../../vars'; + .cover-art-button { & > revealer > box { // Inner controls background-color: rgba(0,0,0,0.75); diff --git a/theme/variants/dark/_disc-view.scss b/theme/variants/dark/_disc-view.scss new file mode 100644 index 0000000..96e88c3 --- /dev/null +++ b/theme/variants/dark/_disc-view.scss @@ -0,0 +1,21 @@ +@import '../../vars'; + +.disc-view { + & > box { // Horizontal box with image and disc label + color: #ccc; + } + + & .track-list { + & > row { + &:not(:active):not(:selected) { // Neither active nor selected, see gtk overrides + &:nth-child(odd):not(:hover) { + background-color: $midnight; + } + + &:nth-child(even), &:hover { + background-color: $grey; + } + } + } + } +} \ No newline at end of file diff --git a/theme/variants/dark/_main.scss b/theme/variants/dark/_main.scss new file mode 100644 index 0000000..8b03705 --- /dev/null +++ b/theme/variants/dark/_main.scss @@ -0,0 +1,21 @@ +@import '../../vars'; + +window { + &.koto-theme-dark { + background-color: $grey; + + & > headerbar, & > headerbar:active { + background-color: $midnight; + } + + .koto-dialog-container { + background-color: transparentize($midnight, 0.75); + } + + @import 'cover-art-button'; + @import 'disc-view'; + @import 'music-local'; + @import 'player-bar'; + @import 'playlist-page'; + } +} \ No newline at end of file diff --git a/theme/variants/dark/_music-local.scss b/theme/variants/dark/_music-local.scss new file mode 100644 index 0000000..0d1346c --- /dev/null +++ b/theme/variants/dark/_music-local.scss @@ -0,0 +1,7 @@ +.page-music-local { + & > .artist-list { + &, & > viewport, & > viewport > list { + background-color: $midnight; + } + } +} \ No newline at end of file diff --git a/theme/variants/dark/_player-bar.scss b/theme/variants/dark/_player-bar.scss new file mode 100644 index 0000000..0e84278 --- /dev/null +++ b/theme/variants/dark/_player-bar.scss @@ -0,0 +1,15 @@ +@import '../../vars'; + +.player-bar { + background-color: $midnight; + + .koto-button { + &:not(.toggled) { + color: $darkgrey; + } + + &.toggled { + color: white; + } + } +} \ No newline at end of file diff --git a/theme/variants/dark/_playlist-page.scss b/theme/variants/dark/_playlist-page.scss new file mode 100644 index 0000000..fca60d8 --- /dev/null +++ b/theme/variants/dark/_playlist-page.scss @@ -0,0 +1,13 @@ +@import '../../vars'; + +.playlist-page { + .track-list-content { // Our Track List + & > .track-list-columned { // Column content + & > row { + &:nth-child(odd):not(:selected) { + background-color: $midnight; + } + } + } + } +} \ No newline at end of file