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:
Joshua Strobl 2021-05-27 12:56:36 +03:00
parent 9f0e8dfbc8
commit fa19fd23b1
45 changed files with 1004 additions and 282 deletions

View file

@ -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')

View file

@ -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);

View file

@ -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
View 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, &section_name, &section_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
View 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

View file

@ -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(

View file

@ -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;
}

View file

@ -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.

View file

@ -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);

View file

@ -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);

View file

@ -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"

View file

@ -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
View 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
View 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();

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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 .

View file

@ -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);

View file

@ -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

View file

@ -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(

View file

@ -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);

View file

@ -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')

View file

@ -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
}

View file

@ -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,

View file

@ -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;

View file

@ -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"

View file

@ -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);

View file

@ -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", &currently_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() {

View file

@ -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(

View file

@ -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(

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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,
)

View file

@ -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 {

View file

@ -58,10 +58,6 @@
& > .track-list-columned-item { // Track rows
font-size: x-large;
}
&:nth-child(odd):not(:selected) {
background-color: $midnight;
}
}
}

View file

@ -1,3 +1,5 @@
@import '../../vars';
.cover-art-button {
& > revealer > box { // Inner controls
background-color: rgba(0,0,0,0.75);

View 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;
}
}
}
}
}

View 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';
}
}

View file

@ -0,0 +1,7 @@
.page-music-local {
& > .artist-list {
&, & > viewport, & > viewport > list {
background-color: $midnight;
}
}
}

View file

@ -0,0 +1,15 @@
@import '../../vars';
.player-bar {
background-color: $midnight;
.koto-button {
&:not(.toggled) {
color: $darkgrey;
}
&.toggled {
color: white;
}
}
}

View 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;
}
}
}
}
}