Implement TOML-based configuration support leveraging tomlc99 and custom file writer.
Fix code related to changing from temporary playlists to permanent ones. Implement new centralized Koto Paths for defining our variables and revdns name for consistency. Updated Album View to leverage KotoCoverArtButton component. Started refactoring our theming to support multiple variants. It isn't where I want it yet but we'll get there.
This commit is contained in:
parent
9f0e8dfbc8
commit
fa19fd23b1
45 changed files with 1004 additions and 282 deletions
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
505
src/config/config.c
Normal file
505
src/config/config.c
Normal file
|
@ -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 <glib-2.0/glib.h>
|
||||
#include <glib-2.0/gio/gio.h>
|
||||
#include <errno.h>
|
||||
#include <toml.h>
|
||||
|
||||
#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);
|
||||
}
|
38
src/config/config.h
Normal file
38
src/config/config.h
Normal file
|
@ -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 <glib-2.0/glib.h>
|
||||
#include <glib-2.0/gio/gio.h>
|
||||
|
||||
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
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
#include <glib-2.0/glib.h>
|
||||
#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(
|
||||
|
|
28
src/db/db.c
28
src/db/db.c
|
@ -21,6 +21,9 @@
|
|||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#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);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "koto-config.h"
|
||||
#include "config/config.h"
|
||||
#include "koto-button.h"
|
||||
#include "koto-expander.h"
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
50
src/koto-paths.c
Normal file
50
src/koto-paths.c
Normal file
|
@ -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 <glib-2.0/glib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#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());
|
||||
}
|
20
src/koto-paths.h
Normal file
20
src/koto-paths.h
Normal file
|
@ -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 <glib-2.0/glib.h>
|
||||
|
||||
void koto_paths_setup();
|
|
@ -17,18 +17,20 @@
|
|||
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 .
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#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(
|
||||
|
|
27
src/main.c
27
src/main.c
|
@ -14,18 +14,22 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <glib.h>
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#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);
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -19,9 +19,11 @@
|
|||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include <gstreamer-1.0/gst/player/player.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#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() {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <glib-2.0/glib-object.h>
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include <gstreamer-1.0/gst/player/player.h>
|
||||
#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(
|
||||
|
|
|
@ -20,9 +20,11 @@
|
|||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -58,10 +58,6 @@
|
|||
& > .track-list-columned-item { // Track rows
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
&:nth-child(odd):not(:selected) {
|
||||
background-color: $midnight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../vars';
|
||||
|
||||
.cover-art-button {
|
||||
& > revealer > box { // Inner controls
|
||||
background-color: rgba(0,0,0,0.75);
|
21
theme/variants/dark/_disc-view.scss
Normal file
21
theme/variants/dark/_disc-view.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
theme/variants/dark/_main.scss
Normal file
21
theme/variants/dark/_main.scss
Normal file
|
@ -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';
|
||||
}
|
||||
}
|
7
theme/variants/dark/_music-local.scss
Normal file
7
theme/variants/dark/_music-local.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
.page-music-local {
|
||||
& > .artist-list {
|
||||
&, & > viewport, & > viewport > list {
|
||||
background-color: $midnight;
|
||||
}
|
||||
}
|
||||
}
|
15
theme/variants/dark/_player-bar.scss
Normal file
15
theme/variants/dark/_player-bar.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
@import '../../vars';
|
||||
|
||||
.player-bar {
|
||||
background-color: $midnight;
|
||||
|
||||
.koto-button {
|
||||
&:not(.toggled) {
|
||||
color: $darkgrey;
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
13
theme/variants/dark/_playlist-page.scss
Normal file
13
theme/variants/dark/_playlist-page.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue