├── po ├── LINGUAS ├── meson.build └── POTFILES ├── theme ├── _track-item.scss ├── koto-builtin-dark.scss ├── koto-builtin-light.scss ├── koto-builtin-gruvbox.scss ├── _vars.scss ├── components │ ├── _cover-art-button.scss │ ├── _badge.scss │ ├── _writer-page.scss │ ├── _track-list.scss │ ├── _album-info.scss │ ├── _audiobook-view.scss │ ├── _track-table.scss │ └── _gtk-overrides.scss ├── _expander.scss ├── _button.scss ├── _disc-view.scss ├── pages │ ├── _artist-view.scss │ ├── _music-local.scss │ ├── _playlist-page.scss │ └── _audiobook-library.scss ├── meson.build ├── variants │ ├── dark │ │ └── _vars.scss │ ├── gruvbox │ │ └── _vars.scss │ └── light │ │ └── _vars.scss ├── _primary-nav.scss ├── _player-bar.scss └── _main.scss ├── .gitignore ├── data ├── genres │ ├── sci-fi.png │ ├── travel.png │ ├── foreign-languages.png │ ├── mystery-and-thriller.png │ └── business-and-personal-finance.png ├── com.github.joshstrobl.koto.gschema.xml ├── com.github.joshstrobl.koto.desktop.in ├── vectors │ ├── multimedia-backwards-jump.svg │ ├── multimedia-forwards-jump.svg │ ├── business-and-personal-finance.svg │ └── foreign-languages.svg ├── meson.build └── com.github.joshstrobl.koto.appdata.xml.in ├── .github ├── koto-alpha-image.png └── FUNDING.yml ├── .vscode ├── c_cpp_properties.json ├── settings.json ├── launch.json └── tasks.json ├── src ├── koto-paths.h ├── indexer │ ├── album-playlist-funcs.h │ ├── artist-playlist-funcs.h │ ├── misc-types.h │ └── track-helpers.h ├── koto.gresource.xml ├── db │ ├── db.h │ ├── loaders.h │ ├── db.c │ └── cartographer.h ├── playback │ ├── mimes.h │ ├── media-keys.h │ ├── mpris.h │ ├── mimes.c │ ├── engine.h │ └── media-keys.c ├── pages │ ├── audiobooks │ │ ├── writer-page.h │ │ ├── genres-banner.h │ │ ├── audiobook-view.h │ │ ├── genre-button.h │ │ ├── library.h │ │ ├── writer-page.c │ │ ├── genre-button.c │ │ ├── genres-banner.c │ │ └── library.c │ ├── music │ │ ├── disc-view.h │ │ ├── album-view.h │ │ ├── artist-view.h │ │ └── music-local.h │ └── playlist │ │ └── list.h ├── components │ ├── track-item.h │ ├── album-info.h │ ├── cover-art-button.h │ ├── action-bar.h │ ├── track-table.h │ ├── track-item.c │ ├── button.h │ └── cover-art-button.c ├── config │ └── config.h ├── koto-paths.c ├── koto-dialog-container.h ├── playlist │ ├── current.h │ ├── create-modify-dialog.h │ ├── add-remove-track-popover.h │ ├── current.c │ └── playlist.h ├── koto-nav.h ├── koto-headerbar.ui ├── koto-expander.h ├── koto-window.h ├── koto-utils.h ├── meson.build ├── koto-dialog-container.c ├── main.c ├── koto-playerbar.h └── koto-utils.c ├── meson.build ├── com.github.joshstrobl.koto.json └── README.md /po/LINGUAS: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext('koto', preset: 'glib') 2 | -------------------------------------------------------------------------------- /theme/_track-item.scss: -------------------------------------------------------------------------------- 1 | .track-item { 2 | padding: 10px; 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | DesiredSettings.md 2 | builddir 3 | .buildconfig 4 | src/theme/style.css 5 | -------------------------------------------------------------------------------- /data/genres/sci-fi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshStrobl/koto/HEAD/data/genres/sci-fi.png -------------------------------------------------------------------------------- /data/genres/travel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshStrobl/koto/HEAD/data/genres/travel.png -------------------------------------------------------------------------------- /.github/koto-alpha-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshStrobl/koto/HEAD/.github/koto-alpha-image.png -------------------------------------------------------------------------------- /data/genres/foreign-languages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshStrobl/koto/HEAD/data/genres/foreign-languages.png -------------------------------------------------------------------------------- /data/genres/mystery-and-thriller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshStrobl/koto/HEAD/data/genres/mystery-and-thriller.png -------------------------------------------------------------------------------- /data/genres/business-and-personal-finance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshStrobl/koto/HEAD/data/genres/business-and-personal-finance.png -------------------------------------------------------------------------------- /theme/koto-builtin-dark.scss: -------------------------------------------------------------------------------- 1 | $variant : 'dark'; 2 | 3 | @import 'vars'; // Root vars 4 | @import 'variants/dark/vars'; // Dark variant vars 5 | @import 'main'; -------------------------------------------------------------------------------- /theme/koto-builtin-light.scss: -------------------------------------------------------------------------------- 1 | $variant : 'light'; 2 | 3 | @import 'vars'; // Root vars 4 | @import 'variants/light/vars'; // Light variant vars 5 | @import 'main'; -------------------------------------------------------------------------------- /theme/koto-builtin-gruvbox.scss: -------------------------------------------------------------------------------- 1 | $variant : 'gruvbox'; 2 | 3 | @import 'vars'; // Root vars 4 | @import 'variants/gruvbox/vars'; // Grubox variant vars 5 | @import 'main'; -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | data/com.github.joshstrobl.koto.desktop.in 2 | data/com.github.joshstrobl.koto.appdata.xml.in 3 | data/com.github.joshstrobl.koto.gschema.xml 4 | src/koto-window.ui 5 | src/main.c 6 | src/koto-window.c 7 | 8 | -------------------------------------------------------------------------------- /theme/_vars.scss: -------------------------------------------------------------------------------- 1 | $midnight: #1d1d1d; 2 | $darkgrey: #666666; 3 | $green: #60E078; 4 | $palewhite: #cccccc; 5 | $red : #FF4652; 6 | 7 | $padding: 40px; 8 | $halvedpadding: $padding / 2; 9 | $quarterpadding: $padding / 4; -------------------------------------------------------------------------------- /data/com.github.joshstrobl.koto.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /data/com.github.joshstrobl.koto.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Koto 3 | Exec=com.github.joshstrobl.koto 4 | Icon=audio-headphones 5 | Terminal=false 6 | Type=Application 7 | Categories=AudioVideo;Audio;GTK;Music;Player; 8 | StartupNotify=true 9 | -------------------------------------------------------------------------------- /theme/components/_cover-art-button.scss: -------------------------------------------------------------------------------- 1 | .cover-art-button { 2 | &:hover { 3 | & > image { 4 | opacity: 0.75; 5 | } 6 | 7 | & > revealer > box { // Inner controls 8 | background-color: transparentize($bg-secondary, 0.25); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /theme/_expander.scss: -------------------------------------------------------------------------------- 1 | @import 'vars'; 2 | 3 | .expander { 4 | & > .expander-header { 5 | & > label { 6 | font-size: large; 7 | } 8 | } 9 | 10 | & > revealer > box { 11 | & > .koto-button { 12 | &, & > box { 13 | min-height: 40px; 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /theme/_button.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | 3 | .koto-button { 4 | border-width: 0; 5 | 6 | & > .button-label { 7 | margin-left: 10px; 8 | } 9 | 10 | &:not(.active) { 11 | color: $text-color-bright; 12 | } 13 | 14 | &.active > image { 15 | color: $koto-primary-color; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /theme/components/_badge.scss: -------------------------------------------------------------------------------- 1 | // This file contains the styling for our label badge 2 | 3 | @import '../vars'; 4 | 5 | .label-badge { 6 | color: $text-color-faded; 7 | font-size: large; 8 | font-weight: 900; 9 | background-color: $bg-secondary; 10 | border-radius: 10px; 11 | padding: 5px 20px; 12 | } -------------------------------------------------------------------------------- /theme/_disc-view.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | 3 | .discs-list { 4 | background-color: transparent; 5 | border-color: transparent; 6 | border-width: 0; 7 | 8 | .disc-view { 9 | & > box { // Horizontal box with image and disc label 10 | color: $text-color-faded; 11 | margin: 10px 0; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /theme/components/_writer-page.scss: -------------------------------------------------------------------------------- 1 | // This is the styling for the writer page 2 | 3 | @import '../vars'; 4 | 5 | .writer-page { 6 | .writer-header { // Our writer / artist header label 7 | color: $text-color-faded; 8 | font-size: 4em; 9 | font-weight: bold; 10 | padding-bottom: $padding; 11 | } 12 | } -------------------------------------------------------------------------------- /theme/pages/_artist-view.scss: -------------------------------------------------------------------------------- 1 | .artist-view { 2 | .no-albums-view { // No Albums 3 | .no-albums-view-header { // Header for artist when we have no albums 4 | .cover-art-button { // Button to play all artist tracks 5 | margin-right: 40px; 6 | } 7 | 8 | & > label { // Artist Name 9 | font-weight: 800; 10 | font-size: 10ex; 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /theme/components/_track-list.scss: -------------------------------------------------------------------------------- 1 | // Track List styling 2 | 3 | @import '../vars'; 4 | 5 | .track-list { 6 | & > row { 7 | &:not(:active):not(:selected) { // Neither active nor selected, see gtk overrides 8 | color: $text-color-bright; 9 | 10 | &:nth-child(odd):not(:hover) { 11 | background-color: $bg-primary; 12 | } 13 | 14 | &:nth-child(even), &:hover { 15 | background-color: $bg-secondary; 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/usr/include/**" 8 | ], 9 | "defines": [], 10 | "compilerPath": "/usr/bin/gcc", 11 | "cStandard": "gnu17", 12 | "cppStandard": "c++20", 13 | "intelliSenseMode": "linux-gcc-x64" 14 | } 15 | ], 16 | "version": 4 17 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "glib.h": "c", 4 | "ios": "c", 5 | "__node_handle": "c", 6 | "gtk.h": "c", 7 | "gtktreeview.h": "c", 8 | "cartographer.h": "c", 9 | "structs.h": "c", 10 | "gst.h": "c", 11 | "player.h": "c", 12 | "config.h": "c", 13 | "toml.h": "c", 14 | "chrono": "c", 15 | "sqlite3.h": "c", 16 | "unistd.h": "c", 17 | "ui.h": "c", 18 | "koto-utils.h": "c", 19 | "random": "c", 20 | "add-remove-track-popover.h": "c" 21 | } 22 | } -------------------------------------------------------------------------------- /theme/meson.build: -------------------------------------------------------------------------------- 1 | sassc = find_program('sassc', required: true) 2 | 3 | builtin_variants = [ 4 | 'dark', 5 | 'gruvbox', 6 | 'light' 7 | ] 8 | 9 | themes = [] 10 | 11 | foreach variant: builtin_variants 12 | themes += custom_target('@0@ theme generation'.format(variant), 13 | input: 'koto-builtin-@0@.scss'.format(variant), 14 | output: 'koto-builtin-@0@.css'.format(variant), 15 | command: [ 16 | sassc, 17 | [ '-a', '-M', '-t', 'compact' ], 18 | '@INPUT@', '@OUTPUT@', 19 | ], 20 | build_by_default: true, 21 | ) 22 | endforeach -------------------------------------------------------------------------------- /theme/pages/_music-local.scss: -------------------------------------------------------------------------------- 1 | @import '../vars'; 2 | 3 | .page-music-local { 4 | & > .artist-list { 5 | &, & > viewport, & > viewport > list { 6 | background-color: $artist-list-bg; 7 | } 8 | 9 | & > viewport > list { 10 | & > row { 11 | padding: $halvedpadding; 12 | } 13 | } 14 | } 15 | 16 | & > stack { 17 | & > .artist-view { 18 | & > viewport > .artist-view-content { 19 | & > .album-list { 20 | & > flowboxchild > .album-view { 21 | & > overlay { 22 | margin-right: $padding; 23 | } 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: JoshStrobl 4 | patreon: joshuastrobl 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: joshuastrobl 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /theme/components/_album-info.scss: -------------------------------------------------------------------------------- 1 | // This file contain the styling for the Album Info section 2 | 3 | .album-info { 4 | .album-description, 5 | .album-narrator, 6 | .album-title-year-combo, 7 | .genres-tag-list { 8 | margin-bottom: $quarterpadding 9 | } 10 | 11 | .album-title-year-combo { 12 | padding-top: $halvedpadding; 13 | } 14 | 15 | .album-title { // Title of album 16 | color: $text-color-faded; 17 | font-size: 2.5em; 18 | font-weight: bold; 19 | } 20 | 21 | .album-year { 22 | margin-left: $halvedpadding; 23 | } 24 | 25 | .genres-tag-list { // Genres Tag List 26 | & .label-badge:not(:last-child) { 27 | margin-right: $halvedpadding; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/koto-paths.h: -------------------------------------------------------------------------------- 1 | /* koto-paths.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | 20 | void koto_paths_setup(); -------------------------------------------------------------------------------- /data/vectors/multimedia-backwards-jump.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /theme/components/_audiobook-view.scss: -------------------------------------------------------------------------------- 1 | // This is the styling for the Audiobook VIew 2 | 3 | @import '../vars'; 4 | 5 | .audiobook-view { 6 | .side-info { // Side Info 7 | margin-right: $halvedpadding; 8 | 9 | button, 10 | image { 11 | margin-bottom: $halvedpadding; 12 | } 13 | 14 | button { // Play / Continue Playback button 15 | font-size: large; 16 | font-weight: bold; 17 | } 18 | 19 | & > label { 20 | font-size: large; 21 | 22 | &:last-child { 23 | margin-bottom: $halvedpadding; 24 | } 25 | } 26 | } 27 | 28 | .chapters-label { // Chapters label after album info 29 | color: $text-color-faded; 30 | font-size: x-large; 31 | font-weight: bold; 32 | padding: $halvedpadding 0; // Top / bottom padding 33 | } 34 | } -------------------------------------------------------------------------------- /data/vectors/multimedia-forwards-jump.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /theme/variants/dark/_vars.scss: -------------------------------------------------------------------------------- 1 | $koto-primary-color: $green; 2 | 3 | $bg-primary : #2e2e2e; 4 | $bg-secondary : $midnight; 5 | 6 | $border-color: black; 7 | 8 | $text-color-bright: white; 9 | $text-color-faded: $palewhite; 10 | 11 | $button-destructive-color-bg: $red; 12 | $button-destructive-color-text: white; 13 | $button-suggested-color-bg: $koto-primary-color; 14 | $button-suggested-color-text: $midnight; 15 | $button-normal-color-bg: $midnight; 16 | $button-normal-color-text: $text-color-bright; 17 | $button-normal-color-border: black; 18 | 19 | $input-background: $bg-primary; 20 | 21 | $primary-nav-button-active-color: $bg-secondary; 22 | 23 | $artist-list-bg: $bg-secondary; 24 | $player-bar-icon-color: $darkgrey; 25 | 26 | $selected-row-color-bg : $koto-primary-color; 27 | $selected-row-color-text : $midnight; -------------------------------------------------------------------------------- /theme/variants/gruvbox/_vars.scss: -------------------------------------------------------------------------------- 1 | $koto-primary-color: #ba5923; 2 | 3 | $bg-primary : #2C2826; 4 | $bg-secondary : #211e1c; 5 | 6 | $border-color: black; 7 | 8 | $text-color-bright: white; 9 | $text-color-faded: $palewhite; 10 | 11 | $button-destructive-color-bg: $red; 12 | $button-destructive-color-text: white; 13 | $button-suggested-color-bg: $koto-primary-color; 14 | $button-suggested-color-text: $midnight; 15 | $button-normal-color-bg: $midnight; 16 | $button-normal-color-text: $text-color-bright; 17 | $button-normal-color-border: black; 18 | 19 | $input-background: $bg-primary; 20 | 21 | $primary-nav-button-active-color: $bg-secondary; 22 | 23 | $artist-list-bg: $bg-secondary; 24 | $player-bar-icon-color: #423C3A; 25 | 26 | $selected-row-color-bg : $koto-primary-color; 27 | $selected-row-color-text : $midnight; -------------------------------------------------------------------------------- /theme/variants/light/_vars.scss: -------------------------------------------------------------------------------- 1 | $koto-primary-color: $green; 2 | 3 | $bg-primary : #fafafa; 4 | $bg-secondary : $palewhite; 5 | 6 | $border-color: $darkgrey; 7 | 8 | $text-color-bright: $midnight; 9 | $text-color-faded: $darkgrey; 10 | 11 | $button-destructive-color-bg: $red; 12 | $button-destructive-color-text: white; 13 | $button-suggested-color-bg: $koto-primary-color; 14 | $button-suggested-color-text: $midnight; 15 | $button-normal-color-bg: white; 16 | $button-normal-color-text: $text-color-bright; 17 | $button-normal-color-border: $darkgrey; 18 | 19 | $input-background: $bg-primary; 20 | 21 | $primary-nav-button-active-color: $bg-secondary; 22 | 23 | $artist-list-bg: $bg-secondary; 24 | $player-bar-icon-color: $darkgrey; 25 | 26 | $selected-row-color-bg : $koto-primary-color; 27 | $selected-row-color-text : $midnight; -------------------------------------------------------------------------------- /theme/pages/_playlist-page.scss: -------------------------------------------------------------------------------- 1 | @import '../vars'; 2 | 3 | .playlist-page { 4 | .playlist-page-header { // Our header 5 | & > .playlist-page-header-info { // Our info centerbox 6 | margin-left: 40px; 7 | 8 | & > label { // All labels 9 | color: $text-color-faded; 10 | } 11 | 12 | & > label:nth-child(1) { // First item (type of playlist) 13 | font-size: 3ex; 14 | font-weight: 700; 15 | margin-top: 30px; 16 | margin-bottom: 10px; 17 | } 18 | 19 | & > label:nth-child(2), 20 | & > label:nth-child(3) { 21 | font-weight: 800; 22 | } 23 | 24 | & > label:nth-child(2) { // Second item (playlist name) 25 | font-size: 10ex; 26 | } 27 | 28 | & > label:nth-child(3) { // Third item (number of tracks) 29 | font-size: 4ex; 30 | margin-top: 40px; 31 | } 32 | } 33 | 34 | & > .koto-button { 35 | 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/indexer/album-playlist-funcs.h: -------------------------------------------------------------------------------- 1 | /* album-playlist-funcs.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "../playlist/playlist.h" 19 | #include "structs.h" 20 | 21 | G_BEGIN_DECLS 22 | 23 | KotoPlaylist * koto_album_get_playlist(KotoAlbum * self); 24 | 25 | G_END_DECLS -------------------------------------------------------------------------------- /src/indexer/artist-playlist-funcs.h: -------------------------------------------------------------------------------- 1 | /* artist-playlist-funcs.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "../playlist/playlist.h" 19 | #include "structs.h" 20 | 21 | G_BEGIN_DECLS 22 | 23 | KotoPlaylist * koto_artist_get_playlist(KotoArtist * self); 24 | 25 | G_END_DECLS -------------------------------------------------------------------------------- /theme/pages/_audiobook-library.scss: -------------------------------------------------------------------------------- 1 | // This file contains the styling for our Audiobook Library 2 | 3 | @import '../vars'; 4 | 5 | .audiobook-library { // Library page 6 | .genres-banner { // Banner for genres list 7 | .large-banner { // Large banner with art for each genre 8 | padding: $padding; 9 | 10 | .audiobook-genre-button { // Genre buttons 11 | .koto-button { 12 | font-size: 2em; 13 | margin: 0.5em; 14 | } 15 | } 16 | } 17 | } 18 | 19 | .writers-button-flow { // Flowbox of buttons for writers 20 | padding: 0 $padding; // Horizontal padding of our standard item padding 21 | 22 | flowboxchild { 23 | padding: 0; 24 | 25 | &:nth-child(even) { 26 | margin: 0 0.5em; 27 | } 28 | 29 | .writer-button { // Writer button 30 | color: $text-color-bright; 31 | font-size: 1.4em; 32 | background-color: $bg-secondary; 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/indexer/misc-types.h: -------------------------------------------------------------------------------- 1 | /* misc-types.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | typedef enum { 20 | KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT, // Chronological is considered default 21 | KOTO_PREFERRED_ALBUM_ALWAYS_ALPHABETICAL, // Prefer sorting alphabetically 22 | } KotoPreferredAlbumSortType; -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('koto', 'c', 2 | version: '0.1.0', 3 | meson_version: '>= 0.57.0', 4 | default_options: [ 5 | 'c_std=gnu11', 6 | 'warning_level=2', 7 | 'werror=true', 8 | ], 9 | ) 10 | 11 | i18n = import('i18n') 12 | gnome = import('gnome') 13 | 14 | config_h = configuration_data() 15 | config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) 16 | config_h.set_quoted('GETTEXT_PACKAGE', 'koto') 17 | config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) 18 | configure_file( 19 | output: 'koto-config.h', 20 | configuration: config_h, 21 | ) 22 | 23 | c = meson.get_compiler('c') 24 | toml_dep = c.find_library('toml', required: true) 25 | 26 | subdir('theme') 27 | subdir('data') 28 | subdir('src') 29 | subdir('po') 30 | 31 | gnome.post_install( 32 | glib_compile_schemas: true, 33 | gtk_update_icon_cache: false 34 | ) 35 | meson.add_install_script('build-aux/meson/postinstall.py') 36 | -------------------------------------------------------------------------------- /src/koto.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ../data/genres/business-and-personal-finance.png 5 | ../data/genres/foreign-languages.png 6 | ../data/vectors/multimedia-backwards-jump.svg 7 | ../data/vectors/multimedia-forwards-jump.svg 8 | ../data/genres/mystery-and-thriller.png 9 | ../data/genres/sci-fi.png 10 | ../data/genres/travel.png 11 | ../theme/koto-builtin-dark.css 12 | ../theme/koto-builtin-gruvbox.css 13 | ../theme/koto-builtin-light.css 14 | 15 | 16 | -------------------------------------------------------------------------------- /theme/_primary-nav.scss: -------------------------------------------------------------------------------- 1 | .primary-nav { 2 | padding: 10px 0; 3 | 4 | & > viewport > box { 5 | & > .koto-button, // Direct buttons like Home 6 | & > .expander > .expander-header { // Expander Headers 7 | font-size: large; 8 | padding-top: 10px; 9 | padding-bottom: 10px; 10 | } 11 | 12 | & > .koto-button, // Direct buttons like Home 13 | & > .expander > .expander-header, // Expander Headers 14 | & > .expander revealer .koto-button { // Expander revealer Buttons 15 | margin-left: 10px; 16 | margin-right: 10px; 17 | padding-left: 10px; 18 | padding-right: 10px; 19 | } 20 | 21 | & > .koto-button, // Direct buttons like Home 22 | & > .expander revealer .koto-button { // Expander revealer Buttons 23 | &.pseudoactive { // When hovering or explicit request to show as active 24 | background-color: $primary-nav-button-active-color; 25 | border-radius: 10px; 26 | } 27 | } 28 | 29 | & > .expander { 30 | & > .expander-header { 31 | color: $text-color-faded; 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /com.github.joshstrobl.koto.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id" : "com.github.joshstrobl.koto", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "40", 5 | "sdk" : "org.gnome.Sdk", 6 | "command" : "com.github.joshstrobl.koto", 7 | "finish-args" : [ 8 | "--share=network", 9 | "--share=ipc", 10 | "--socket=fallback-x11", 11 | "--socket=wayland" 12 | ], 13 | "cleanup" : [ 14 | "/include", 15 | "/lib/pkgconfig", 16 | "/man", 17 | "/share/doc", 18 | "/share/gtk-doc", 19 | "/share/man", 20 | "/share/pkgconfig", 21 | "*.la", 22 | "*.a" 23 | ], 24 | "modules" : [ 25 | { 26 | "name" : "koto", 27 | "builddir" : true, 28 | "buildsystem" : "meson", 29 | "sources" : [ 30 | { 31 | "type" : "git", 32 | "url" : "https://github.com/JoshStrobl/koto.git" 33 | } 34 | ] 35 | } 36 | ], 37 | "build-options" : { 38 | "env" : { } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /theme/components/_track-table.scss: -------------------------------------------------------------------------------- 1 | .track-list-content { // Our Track List 2 | & > .track-list-header, 3 | .track-list-columned-item { 4 | font-size: x-large; 5 | padding: 3ex 2ex; 6 | } 7 | 8 | & > .track-list-header { // Headers 9 | font-weight: bold; 10 | 11 | .koto-button { // All Koto buttons in our header 12 | &.active { // Is active 13 | color: $green; 14 | } 15 | } 16 | } 17 | 18 | & > .track-list-columned { // Column content 19 | & > row { 20 | &:not(:selected) { 21 | color: $text-color-bright; 22 | } 23 | 24 | &:nth-child(odd):not(:selected) { 25 | background-color: $bg-secondary; 26 | } 27 | 28 | & > .track-list-columned-item { // Track rows 29 | font-size: x-large; 30 | } 31 | } 32 | } 33 | 34 | .track-column-number { // Column section within header and track items 35 | 36 | } 37 | 38 | .track-column-name { // Name section within header and track items 39 | 40 | } 41 | 42 | .track-column-album { // Album section within headers and track items 43 | 44 | } 45 | 46 | .track-column-artist { // Artist section within headers and track items 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /src/db/db.h: -------------------------------------------------------------------------------- 1 | /* db.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | extern int KOTO_DB_SUCCESS; 22 | extern int KOTO_DB_NEW; 23 | extern int KOTO_DB_FAIL; 24 | 25 | void close_db(); 26 | 27 | int create_db_tables(); 28 | 29 | gchar * get_db_path(); 30 | 31 | int enable_foreign_keys(); 32 | 33 | int have_existing_db(); 34 | 35 | int new_transaction( 36 | gchar * operation, 37 | const gchar * transaction_err_msg, 38 | gboolean fatal 39 | ); 40 | 41 | int open_db(); 42 | -------------------------------------------------------------------------------- /src/playback/mimes.h: -------------------------------------------------------------------------------- 1 | /* mimes.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | 22 | G_BEGIN_DECLS 23 | 24 | gboolean koto_bplayback_engine_gst_caps_iter( 25 | GstCapsFeatures * features, 26 | GstStructure * structure, 27 | gpointer user_data 28 | ); 29 | 30 | void koto_playback_engine_gst_pad_iter( 31 | gpointer list_data, 32 | gpointer user_data 33 | ); 34 | 35 | void koto_playback_engine_get_supported_mimetypes(GList * mimes); 36 | 37 | G_END_DECLS 38 | -------------------------------------------------------------------------------- /src/pages/audiobooks/writer-page.h: -------------------------------------------------------------------------------- 1 | /* writers-page.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | 22 | G_BEGIN_DECLS 23 | 24 | #define KOTO_TYPE_WRITER_PAGE (koto_writer_page_get_type()) 25 | G_DECLARE_FINAL_TYPE(KotoWriterPage, koto_writer_page, KOTO, WRITER_PAGE, GObject) 26 | 27 | GtkWidget* koto_writer_page_create_item( 28 | gpointer item, 29 | gpointer user_data 30 | ); 31 | 32 | GtkWidget * koto_writer_page_get_main(KotoWriterPage * self); 33 | 34 | void koto_writer_page_set_artist( 35 | KotoWriterPage * self, 36 | KotoArtist * artist 37 | ); 38 | 39 | KotoWriterPage * koto_writer_page_new(KotoArtist * artist); 40 | 41 | G_END_DECLS -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | desktop_file = i18n.merge_file( 2 | input: 'com.github.joshstrobl.koto.desktop.in', 3 | output: 'com.github.joshstrobl.koto.desktop', 4 | type: 'desktop', 5 | po_dir: '../po', 6 | install: true, 7 | install_dir: join_paths(get_option('datadir'), 'applications') 8 | ) 9 | 10 | desktop_utils = find_program('desktop-file-validate', required: false) 11 | if desktop_utils.found() 12 | test('Validate desktop file', desktop_utils, 13 | args: [desktop_file] 14 | ) 15 | endif 16 | 17 | appstream_file = i18n.merge_file( 18 | input: 'com.github.joshstrobl.koto.appdata.xml.in', 19 | output: 'com.github.joshstrobl.koto.appdata.xml', 20 | po_dir: '../po', 21 | install: true, 22 | install_dir: join_paths(get_option('datadir'), 'appdata') 23 | ) 24 | 25 | appstream_util = find_program('appstream-util', required: false) 26 | if appstream_util.found() 27 | test('Validate appstream file (relaxed)', appstream_util, 28 | args: ['validate-relax', appstream_file] 29 | ) 30 | endif 31 | 32 | install_data('com.github.joshstrobl.koto.gschema.xml', 33 | install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') 34 | ) 35 | 36 | compile_schemas = find_program('glib-compile-schemas', required: false) 37 | if compile_schemas.found() 38 | test('Validate schema file', compile_schemas, 39 | args: ['--strict', '--dry-run', meson.current_source_dir()] 40 | ) 41 | endif 42 | -------------------------------------------------------------------------------- /src/components/track-item.h: -------------------------------------------------------------------------------- 1 | /* track-item.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "indexer/structs.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define KOTO_TYPE_TRACK_ITEM (koto_track_item_get_type()) 27 | 28 | G_DECLARE_FINAL_TYPE(KotoTrackItem, koto_track_item, KOTO, TRACK_ITEM, GtkBox) 29 | 30 | KotoTrackItem* koto_track_item_new(KotoTrack * track); 31 | void koto_track_item_handle_add_to_playlist_button_click( 32 | GtkGestureClick * gesture, 33 | int n_press, 34 | double x, 35 | double y, 36 | gpointer user_data 37 | ); 38 | 39 | KotoTrack * koto_track_item_get_track(KotoTrackItem * self); 40 | 41 | void koto_track_item_set_track( 42 | KotoTrackItem * self, 43 | KotoTrack * track 44 | ); 45 | 46 | G_END_DECLS 47 | -------------------------------------------------------------------------------- /src/playback/media-keys.h: -------------------------------------------------------------------------------- 1 | /* media-keys.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | G_BEGIN_DECLS 23 | 24 | void grab_media_keys(); 25 | 26 | void handle_media_keys_async_done( 27 | GObject * source_object, 28 | GAsyncResult * res, 29 | gpointer user_data 30 | ); 31 | 32 | void handle_media_keys_signal( 33 | GDBusProxy * proxy, 34 | const gchar * sender_name, 35 | const gchar * signal_name, 36 | GVariant * parameters, 37 | gpointer user_data 38 | ); 39 | 40 | void handle_window_enter( 41 | GtkEventControllerFocus * controller, 42 | gpointer user_data 43 | ); 44 | 45 | void handle_window_leave( 46 | GtkEventControllerFocus * controller, 47 | gpointer user_data 48 | ); 49 | 50 | void release_media_keys(); 51 | 52 | void setup_mediakeys_interface(); 53 | 54 | G_END_DECLS 55 | -------------------------------------------------------------------------------- /data/com.github.joshstrobl.koto.appdata.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.github.joshstrobl.koto.desktop 4 | CC0-1.0 5 | Apache-2.0 6 | Koto 7 | Koto is an in-development audiobook, music, and podcast manager. 8 | 9 | Koto is an in-development audiobook, music, and podcast manager that is designed for and caters to a modern desktop Linux experience. 10 | 11 | com.github.joshstrobl.koto.desktop 12 | https://github.com/JoshStrobl/koto 13 | https://github.com/JoshStrobl/koto/issues 14 | https://patreon.com/joshuastrobl 15 | https://liberapay.com/joshuastrobl 16 | Joshua Strobl 17 | joshua.strobl_AT_outlook.com 18 | 19 | mild 20 | 21 | 22 | com.github.joshstrobl.koto 23 | 24 | 25 | pointing 26 | touch 27 | 1600 28 | 29 | 30 | keyboard 31 | 1366 32 | 33 | 34 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch (GDB)", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/builddir/src/com.github.joshstrobl.koto", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [ 16 | {"name" : "G_MESSAGES_DEBUG", "value": "all" }, 17 | { "name": "GTK_THEME", "value": "Adwaita:dark" } 18 | ], 19 | "externalConsole": false, 20 | "MIMode": "gdb", 21 | "setupCommands": [ 22 | { 23 | "description": "Enable pretty-printing for gdb", 24 | "text": "-enable-pretty-printing", 25 | "ignoreFailures": true 26 | } 27 | ], 28 | "preLaunchTask": "Meson Configure and Build" 29 | }, 30 | { 31 | "name": "Launch", 32 | "type": "cppdbg", 33 | "request": "launch", 34 | "program": "${workspaceFolder}/builddir/src/com.github.joshstrobl.koto", 35 | "args": [], 36 | "stopAtEntry": false, 37 | "cwd": "${workspaceFolder}", 38 | "environment": [ 39 | {"name" : "G_MESSAGES_DEBUG", "value": "all" }, 40 | { "name": "GTK_THEME", "value": "Adwaita:dark" } 41 | ], 42 | "externalConsole": false, 43 | "linux": { 44 | "MIMode": "gdb", 45 | "miDebuggerPath": "" 46 | }, 47 | "preLaunchTask": "Meson Configure and Build" 48 | }, 49 | ] 50 | } -------------------------------------------------------------------------------- /theme/_player-bar.scss: -------------------------------------------------------------------------------- 1 | @import 'vars'; 2 | 3 | .player-bar { 4 | background-color: $bg-secondary; 5 | background-image: none; 6 | padding: $halvedpadding; 7 | 8 | .koto-button { 9 | &:not(.toggled) { 10 | color: $player-bar-icon-color; 11 | } 12 | 13 | &.toggled { 14 | color: $text-color-bright; 15 | } 16 | } 17 | 18 | .playerbar-primary-controls, // Primary 19 | .playerbar-secondary-controls { // Secondary 20 | & > .koto-button { // Direct descendents 21 | margin: 0 $quarterpadding; 22 | } 23 | } 24 | 25 | .playerbar-primary-controls { // Primary Controls 26 | .playerbar-advanced-controls { // Advanced controls 27 | margin-left: $padding; 28 | 29 | & > entry { // Inner GtkEntry 30 | margin: 0 $halvedpadding; 31 | } 32 | } 33 | } 34 | 35 | .playerbar-info { // Central info section 36 | & > box { // Info labels 37 | margin-left: 2ex; 38 | 39 | & > image { 40 | color: $text-color-faded; 41 | } 42 | 43 | & > label { 44 | margin-top: 6px; 45 | margin-bottom: 6px; 46 | 47 | &:nth-child(1) { // Title 48 | font-size: x-large; 49 | font-weight: bold; 50 | } 51 | 52 | &:not(:nth-child(1)) { // Album and Artist 53 | font-size: large; 54 | } 55 | 56 | &:nth-child(2) { // Album 57 | 58 | } 59 | 60 | &:nth-child(3) { // Artist 61 | 62 | } 63 | } 64 | } 65 | } 66 | 67 | .playerbar-secondary-controls { // Secondary controls 68 | label { // Inner playback position label 69 | font-size: large; 70 | margin-right: $halvedpadding; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/indexer/track-helpers.h: -------------------------------------------------------------------------------- 1 | /* track-helpers.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | 20 | void koto_track_helpers_init(); 21 | 22 | guint64 koto_track_helpers_get_cd_based_on_file_name(const gchar * file_name); 23 | 24 | gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre); 25 | 26 | gchar * koto_track_helpers_get_name_for_file( 27 | const gchar * path, 28 | gchar * optional_artist_name 29 | ); 30 | 31 | guint64 koto_track_helpers_get_position_based_on_file_name(const gchar * file_name); 32 | 33 | gint koto_track_helpers_sort_track_items( 34 | gconstpointer track1_item, 35 | gconstpointer track2_item, 36 | gpointer user_data 37 | ); 38 | 39 | gint koto_track_helpers_sort_tracks( 40 | gconstpointer track1, 41 | gconstpointer track2, 42 | gpointer user_data 43 | ); 44 | 45 | gint koto_track_helpers_sort_tracks_by_uuid( 46 | gconstpointer track1_uuid, 47 | gconstpointer track2_uuid, 48 | gpointer user_data 49 | ); -------------------------------------------------------------------------------- /src/config/config.h: -------------------------------------------------------------------------------- 1 | /* config.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | #include "../indexer/misc-types.h" 22 | 23 | G_BEGIN_DECLS 24 | 25 | /** 26 | * Type Definition 27 | **/ 28 | 29 | #define KOTO_TYPE_CONFIG (koto_config_get_type()) 30 | 31 | G_DECLARE_FINAL_TYPE(KotoConfig, koto_config, KOTO, CONFIG, GObject) 32 | 33 | KotoConfig* koto_config_new(); 34 | void koto_config_load( 35 | KotoConfig * self, 36 | gchar * path 37 | ); 38 | 39 | void koto_config_load_libs(KotoConfig * self); 40 | 41 | void koto_config_monitor_handle_changed( 42 | GFileMonitor * monitor, 43 | GFile * file, 44 | GFile * other_file, 45 | GFileMonitorEvent ev, 46 | gpointer user_data 47 | ); 48 | 49 | KotoPreferredAlbumSortType koto_config_get_preferred_album_sort_type(KotoConfig * self); 50 | 51 | void koto_config_refresh(KotoConfig * self); 52 | 53 | void koto_config_save(KotoConfig * self); 54 | 55 | G_END_DECLS -------------------------------------------------------------------------------- /src/db/loaders.h: -------------------------------------------------------------------------------- 1 | /* loaders.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | int process_artists( 19 | void * data, 20 | int num_columns, 21 | char ** fields, 22 | char ** column_names 23 | ); 24 | 25 | int process_artist_paths( 26 | void * data, 27 | int num_columns, 28 | char ** fields, 29 | char ** column_names 30 | ); 31 | 32 | int process_albums( 33 | void * data, 34 | int num_columns, 35 | char ** fields, 36 | char ** column_names 37 | ); 38 | 39 | int process_playlists( 40 | void * data, 41 | int num_columns, 42 | char ** fields, 43 | char ** column_names 44 | ); 45 | 46 | int process_playlists_tracks( 47 | void * data, 48 | int num_columns, 49 | char ** fields, 50 | char ** column_names 51 | ); 52 | 53 | int process_tracks( 54 | void * data, 55 | int num_columns, 56 | char ** fields, 57 | char ** column_names 58 | ); 59 | 60 | int process_track_paths( 61 | void * data, 62 | int num_columns, 63 | char ** fields, 64 | char ** column_names 65 | ); 66 | 67 | void read_from_db(); -------------------------------------------------------------------------------- /src/components/album-info.h: -------------------------------------------------------------------------------- 1 | /* album-info.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../config/config.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | typedef enum { 27 | KOTO_ALBUM_INFO_TYPE_ALBUM, 28 | KOTO_ALBUM_INFO_TYPE_AUDIOBOOK, 29 | KOTO_ALBUM_INFO_TYPE_PODCAST 30 | } KotoAlbumInfoType; 31 | 32 | #define KOTO_TYPE_ALBUM_INFO (koto_album_info_get_type()) 33 | G_DECLARE_FINAL_TYPE(KotoAlbumInfo, koto_album_info, KOTO, ALBUM_INFO, GtkBox); 34 | #define KOTO_IS_ALBUM_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ALBUM_INFO)) 35 | 36 | void koto_album_info_apply_configuration_state( 37 | KotoConfig * c, 38 | guint prop_id, 39 | KotoAlbumInfo * self 40 | ); 41 | 42 | void koto_album_info_set_album_uuid( 43 | KotoAlbumInfo * self, 44 | gchar * album_uuid 45 | ); 46 | 47 | void koto_album_info_set_type( 48 | KotoAlbumInfo * self, 49 | const gchar * type 50 | ); 51 | 52 | KotoAlbumInfo * koto_album_info_new(gchar * type); -------------------------------------------------------------------------------- /theme/_main.scss: -------------------------------------------------------------------------------- 1 | @import 'components/album-info'; 2 | @import 'components/audiobook-view'; 3 | @import 'components/badge'; 4 | @import 'components/cover-art-button'; 5 | @import 'components/gtk-overrides'; 6 | @import 'components/track-list'; 7 | @import 'components/track-table'; 8 | @import 'components/writer-page'; 9 | @import 'pages/audiobook-library'; 10 | @import 'pages/artist-view'; 11 | @import 'pages/music-local'; 12 | @import 'pages/playlist-page'; 13 | 14 | @import 'button'; 15 | @import 'disc-view'; 16 | @import 'expander'; 17 | @import 'player-bar'; 18 | @import 'primary-nav'; 19 | @import 'track-item'; 20 | 21 | window { 22 | color: $text-color-bright; 23 | background-color: $bg-primary; 24 | 25 | & > headerbar, & > headerbar:active { 26 | background-color: $bg-secondary; 27 | background-image: none; 28 | border-bottom-width: 0; // Unset default styling for headerbar 29 | 30 | &:active windowcontrols button { // Minimize, Maximize, Close buttons when window is active 31 | color: $text-color-bright; 32 | text-decoration-color: $text-color-bright; 33 | } 34 | 35 | &:not(:active) windowcontrols button { // Minimize, Maximize, Close buttons when window is inactive 36 | color: $text-color-faded; 37 | text-decoration-color: $text-color-faded; 38 | } 39 | } 40 | 41 | .koto-dialog-container { 42 | background-color: transparentize($bg-secondary, 0.25); 43 | padding: 20px; 44 | } 45 | 46 | // All the classes we want consistent padding applied to for its primary content 47 | .artist-view-content, // Has the albums 48 | .playlist-page, // Individual playlists 49 | .writer-page { // Writer page in Audiobook 50 | padding: $padding; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/koto-paths.c: -------------------------------------------------------------------------------- 1 | /* koto-paths.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "koto-paths.h" 23 | #include "koto-utils.h" 24 | 25 | gchar * koto_rev_dns; 26 | gchar * koto_path_cache; 27 | gchar * koto_path_config; 28 | 29 | gchar * koto_path_to_conf; 30 | gchar * koto_path_to_db; 31 | 32 | void koto_paths_setup() { 33 | koto_rev_dns = "com.github.joshstrobl.koto"; 34 | gchar * user_cache_dir = g_strdup(g_get_user_data_dir()); 35 | gchar * user_config_dir = g_strdup(g_get_user_config_dir()); 36 | 37 | koto_path_cache = g_build_path(G_DIR_SEPARATOR_S, user_cache_dir, koto_rev_dns, NULL); 38 | koto_path_config = g_build_path(G_DIR_SEPARATOR_S, user_config_dir, koto_rev_dns, NULL); 39 | koto_path_to_conf = g_build_filename(koto_path_config, "config.toml", NULL); 40 | koto_path_to_db = g_build_filename( koto_path_cache, "db", NULL); 41 | 42 | koto_utils_mkdir(user_cache_dir); 43 | koto_utils_mkdir(user_config_dir); 44 | koto_utils_mkdir(koto_path_cache); 45 | koto_utils_mkdir(koto_path_config); 46 | } -------------------------------------------------------------------------------- /src/pages/audiobooks/genres-banner.h: -------------------------------------------------------------------------------- 1 | /* genres-banner.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | 23 | G_BEGIN_DECLS 24 | 25 | #define KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER koto_audiobooks_genres_banner_get_type() 26 | #define KOTO_AUDIOBOOKS_GENRES_BANNER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER, KotoAudiobooksGenresBanner)) 27 | typedef struct _KotoAudiobooksGenresBanner KotoAudiobooksGenresBanner; 28 | typedef struct _KotoAudiobooksGenresBannerClass KotoAudiobooksGenresBannerClass; 29 | 30 | GLIB_AVAILABLE_IN_ALL 31 | GType koto_audiobooks_genres_banner_get_type(void) G_GNUC_CONST; 32 | 33 | #define KOTO_IS_AUDIOBOOKS_GENRES_BANNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER)) 34 | 35 | void koto_audiobooks_genres_banner_add_genre( 36 | KotoAudiobooksGenresBanner * self, 37 | gchar * genre_id 38 | ); 39 | 40 | GtkWidget * koto_audiobooks_genres_banner_get_main(KotoAudiobooksGenresBanner * self); 41 | 42 | KotoAudiobooksGenresBanner * koto_audiobooks_genres_banner_new(); 43 | 44 | G_END_DECLS -------------------------------------------------------------------------------- /src/koto-dialog-container.h: -------------------------------------------------------------------------------- 1 | /* koto-dialog-container.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | 22 | G_BEGIN_DECLS 23 | 24 | /** 25 | * Type Definition 26 | **/ 27 | 28 | #define KOTO_TYPE_DIALOG_CONTAINER koto_dialog_container_get_type() 29 | G_DECLARE_FINAL_TYPE(KotoDialogContainer, koto_dialog_container, KOTO, DIALOG_CONTAINER, GtkBox); 30 | #define KOTO_IS_DIALOG_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_DIALOG_CONTAINER)) 31 | 32 | /** 33 | * Functions 34 | **/ 35 | 36 | KotoDialogContainer * koto_dialog_container_new(); 37 | 38 | void koto_dialog_container_add_dialog( 39 | KotoDialogContainer * self, 40 | gchar * dialog_name, 41 | GtkWidget * dialog 42 | ); 43 | 44 | void koto_dialog_container_handle_close_click( 45 | GtkGestureClick * gesture, 46 | int n_press, 47 | double x, 48 | double y, 49 | gpointer user_data 50 | ); 51 | 52 | void koto_dialog_container_hide(KotoDialogContainer * self); 53 | 54 | void koto_dialog_container_show_dialog( 55 | KotoDialogContainer * self, 56 | gchar * dialog_name 57 | ); 58 | 59 | G_END_DECLS 60 | -------------------------------------------------------------------------------- /src/pages/music/disc-view.h: -------------------------------------------------------------------------------- 1 | /* disc-view.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../../indexer/structs.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define KOTO_TYPE_DISC_VIEW (koto_disc_view_get_type()) 27 | 28 | G_DECLARE_FINAL_TYPE(KotoDiscView, koto_disc_view, KOTO, DISC_VIEW, GtkBox) 29 | 30 | KotoDiscView* koto_disc_view_new( 31 | KotoAlbum * album, 32 | guint disc 33 | ); 34 | 35 | void koto_disc_view_add_track( 36 | KotoDiscView * self, 37 | KotoTrack * track 38 | ); 39 | 40 | void koto_disc_view_handle_selected_rows_changed( 41 | GtkListBox * box, 42 | gpointer user_data 43 | ); 44 | 45 | void koto_disc_view_set_album( 46 | KotoDiscView * self, 47 | KotoAlbum * album 48 | ); 49 | 50 | void koto_disc_view_set_disc_label_visible( 51 | KotoDiscView * self, 52 | gboolean visible 53 | ); 54 | 55 | void koto_disc_view_set_disc_number( 56 | KotoDiscView * self, 57 | guint disc_number 58 | ); 59 | 60 | gint koto_disc_view_sort_list_box_rows( 61 | GtkListBoxRow * row1, 62 | GtkListBoxRow * row2, 63 | gpointer user_data 64 | ); 65 | 66 | G_END_DECLS 67 | -------------------------------------------------------------------------------- /src/pages/audiobooks/audiobook-view.h: -------------------------------------------------------------------------------- 1 | /* audiobook-view.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../../indexer/structs.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define KOTO_TYPE_AUDIOBOOK_VIEW (koto_audiobook_view_get_type()) 27 | G_DECLARE_FINAL_TYPE(KotoAudiobookView, koto_audiobook_view, KOTO, AUDIOBOOK_VIEW, GtkBox); 28 | #define KOTO_IS_AUDIOBOOK_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOK_VIEW)) 29 | 30 | void koto_audiobook_view_set_album( 31 | KotoAudiobookView * self, 32 | KotoAlbum * album 33 | ); 34 | 35 | GtkWidget * koto_audiobook_view_create_track_item( 36 | gpointer item, 37 | gpointer user_data 38 | ); 39 | 40 | void koto_audiobook_view_handle_play_clicked( 41 | GtkButton * button, 42 | gpointer user_data 43 | ); 44 | 45 | void koto_audiobook_view_handle_playlist_updated( 46 | KotoPlaylist * playlist, 47 | gpointer user_data 48 | ); 49 | 50 | void koto_audiobook_view_update_side_info(KotoAudiobookView * self); 51 | 52 | void koto_audiobook_view_destroy_associated_user_data(gpointer user_data); 53 | 54 | KotoAudiobookView * koto_audiobook_view_new(); 55 | 56 | G_END_DECLS -------------------------------------------------------------------------------- /src/pages/audiobooks/genre-button.h: -------------------------------------------------------------------------------- 1 | /* genres-button.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "../../components/button.h" 21 | 22 | G_BEGIN_DECLS 23 | 24 | #define KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON koto_audiobooks_genre_button_get_type() 25 | G_DECLARE_FINAL_TYPE(KotoAudiobooksGenreButton, koto_audiobooks_genre_button, KOTO, AUDIOBOOKS_GENRE_BUTTON, GtkBox) 26 | #define KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON)) 27 | 28 | KotoButton * koto_audiobooks_genre_button_get_button(KotoAudiobooksGenreButton * self); 29 | 30 | gchar * koto_audiobooks_genre_button_get_id(KotoAudiobooksGenreButton * self); 31 | 32 | gchar * koto_audiobooks_genre_button_get_name(KotoAudiobooksGenreButton * self); 33 | 34 | void koto_audiobooks_genre_button_set_id( 35 | KotoAudiobooksGenreButton * self, 36 | gchar * genre_id 37 | ); 38 | 39 | void koto_audiobooks_genre_button_set_name( 40 | KotoAudiobooksGenreButton * self, 41 | gchar * genre_name 42 | ); 43 | 44 | KotoAudiobooksGenreButton * koto_audiobooks_genre_button_new( 45 | gchar * genre_id, 46 | gchar * genre_name 47 | ); 48 | 49 | G_END_DECLS -------------------------------------------------------------------------------- /src/pages/music/album-view.h: -------------------------------------------------------------------------------- 1 | /* album-view.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../../indexer/structs.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define KOTO_TYPE_ALBUM_VIEW (koto_album_view_get_type()) 27 | 28 | G_DECLARE_FINAL_TYPE(KotoAlbumView, koto_album_view, KOTO, ALBUM_VIEW, GObject) 29 | 30 | KotoAlbumView* koto_album_view_new(KotoAlbum * album); 31 | GtkWidget * koto_album_view_get_main(KotoAlbumView * self); 32 | 33 | void koto_album_view_add_track( 34 | KotoAlbumView * self, 35 | KotoTrack * track 36 | ); 37 | 38 | void koto_album_view_handle_track_added( 39 | KotoAlbum * album, 40 | KotoTrack * track, 41 | gpointer user_data 42 | ); 43 | 44 | void koto_album_view_set_album( 45 | KotoAlbumView * self, 46 | KotoAlbum * album 47 | ); 48 | 49 | int koto_album_view_sort_discs( 50 | GtkListBoxRow * track1, 51 | GtkListBoxRow * track2, 52 | gpointer user_data 53 | ); 54 | 55 | void koto_album_view_toggle_album_playback( 56 | GtkGestureClick * gesture, 57 | int n_press, 58 | double x, 59 | double y, 60 | gpointer data 61 | ); 62 | 63 | void koto_album_view_update_disc_labels(KotoAlbumView * self); 64 | 65 | G_END_DECLS 66 | -------------------------------------------------------------------------------- /src/playlist/current.h: -------------------------------------------------------------------------------- 1 | /* current.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include "playlist.h" 21 | 22 | G_BEGIN_DECLS 23 | 24 | /** 25 | * Type Definition 26 | **/ 27 | 28 | #define KOTO_TYPE_CURRENT_PLAYLIST koto_current_playlist_get_type() 29 | #define KOTO_CURRENT_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_CURRENT_PLAYLIST, KotoCurrentPlaylist)) 30 | #define KOTO_IS_CURRENT_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_CURRENT_PLAYLIST)) 31 | 32 | typedef struct _KotoCurrentPlaylist KotoCurrentPlaylist; 33 | typedef struct _KotoCurrentPlaylistClass KotoCurrentPlaylistClass; 34 | 35 | GLIB_AVAILABLE_IN_ALL 36 | GType koto_current_playlist_get_type(void) G_GNUC_CONST; 37 | 38 | /** 39 | * Current Playlist Functions 40 | **/ 41 | 42 | KotoCurrentPlaylist * koto_current_playlist_new(); 43 | 44 | KotoPlaylist * koto_current_playlist_get_playlist(KotoCurrentPlaylist * self); 45 | 46 | void koto_current_playlist_save_playlist_state(KotoCurrentPlaylist * self); 47 | 48 | void koto_current_playlist_set_playlist( 49 | KotoCurrentPlaylist * self, 50 | KotoPlaylist * playlist, 51 | gboolean play_immediately, 52 | gboolean play_current 53 | ); 54 | 55 | G_END_DECLS 56 | -------------------------------------------------------------------------------- /src/koto-nav.h: -------------------------------------------------------------------------------- 1 | /* koto-nav.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include "db/cartographer.h" 21 | #include "indexer/structs.h" 22 | 23 | G_BEGIN_DECLS 24 | 25 | #define KOTO_TYPE_NAV (koto_nav_get_type()) 26 | 27 | G_DECLARE_FINAL_TYPE(KotoNav, koto_nav, KOTO, NAV, GObject) 28 | 29 | KotoNav* koto_nav_new(void); 30 | void koto_nav_create_audiobooks_section(KotoNav * self); 31 | 32 | void koto_nav_create_music_section(KotoNav * self); 33 | 34 | void koto_nav_create_playlist_section(KotoNav * self); 35 | 36 | void koto_nav_create_podcasts_section(KotoNav * self); 37 | 38 | void koto_nav_handle_playlist_add_click( 39 | GtkGestureClick * gesture, 40 | int n_press, 41 | double x, 42 | double y, 43 | gpointer user_data 44 | ); 45 | 46 | void koto_nav_handle_playlist_added( 47 | KotoCartographer * carto, 48 | KotoPlaylist * playlist, 49 | gpointer user_data 50 | ); 51 | 52 | void koto_nav_handle_playlist_modified( 53 | KotoPlaylist * playlist, 54 | gpointer user_data 55 | ); 56 | 57 | void koto_nav_handle_playlist_removed( 58 | KotoCartographer * carto, 59 | gchar * playlist_uuid, 60 | gpointer user_data 61 | ); 62 | 63 | GtkWidget * koto_nav_get_nav(KotoNav * self); 64 | 65 | G_END_DECLS 66 | -------------------------------------------------------------------------------- /src/koto-headerbar.ui: -------------------------------------------------------------------------------- 1 | ?xml version="1.0" encoding="UTF-8"?> 2 | 3 | 4 | 5 | 6 | False 7 | audio-headphones 8 | 3 9 | 10 | 11 | True 12 | False 13 | True 14 | appmenu:minimize,maximize,close 15 | 16 | 17 | 400 18 | True 19 | True 20 | edit-find-symbolic 21 | False 22 | False 23 | Search... 24 | GTK_INPUT_HINT_NO_EMOJI | GTK_INPUT_HINT_NONE 25 | 26 | 27 | 28 | 29 | menu_button 30 | True 31 | True 32 | True 33 | audioHeadphonesMenuImage 34 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Clean builddir", 8 | "type": "shell", 9 | "command": "rm", 10 | "args": [ 11 | "-rf", 12 | "builddir" 13 | ], 14 | "problemMatcher": [] 15 | }, 16 | { 17 | "label": "Format", 18 | "type": "shell", 19 | "command": "uncrustify", 20 | "args": [ 21 | "-c", 22 | "jsc.cfg", 23 | "--no-backup", 24 | "**/*.c", 25 | "**/*.h" 26 | ] 27 | }, 28 | { 29 | "label": "Meson Configure and Build", 30 | "type": "shell", 31 | "command": "", 32 | "dependsOrder": "sequence", 33 | "dependsOn": ["Meson Configure", "Meson Compile"] 34 | }, 35 | { 36 | "label": "Meson Configure", 37 | "type": "shell", 38 | "command": "meson", 39 | "args": [ 40 | "--prefix=/usr", 41 | "--libdir=\"libdir\"", 42 | "--sysconfdir=/etc", 43 | "builddir" 44 | ], 45 | "problemMatcher": [] 46 | }, 47 | { 48 | "label": "Meson Compile", 49 | "type": "shell", 50 | "command": "meson", 51 | "args": [ 52 | "compile", 53 | "-C", 54 | "builddir", 55 | ], 56 | "problemMatcher": [], 57 | "group": { 58 | "kind": "build", 59 | "isDefault": true 60 | } 61 | }, 62 | { 63 | "label": "Meson Dist", 64 | "type": "shell", 65 | "command": "meson", 66 | "args": [ 67 | "dist", 68 | "-C", 69 | "builddir", 70 | "--formats", 71 | "xztar", 72 | "--include-subprojects" 73 | ], 74 | "problemMatcher": [] 75 | }, 76 | { 77 | "label": "Meson Install", 78 | "type": "shell", 79 | "command": "sudo", 80 | "args": [ 81 | "meson", 82 | "install", 83 | "-C", 84 | "builddir", 85 | "--destdir", 86 | "/", 87 | "--no-rebuild" 88 | ], 89 | "problemMatcher": [] 90 | } 91 | ] 92 | } -------------------------------------------------------------------------------- /src/koto-expander.h: -------------------------------------------------------------------------------- 1 | /* koto-expander.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include "components/button.h" 22 | 23 | G_BEGIN_DECLS 24 | 25 | #define KOTO_TYPE_EXPANDER (koto_expander_get_type()) 26 | G_DECLARE_FINAL_TYPE(KotoExpander, koto_expander, KOTO, EXPANDER, GtkBox) 27 | #define KOTO_IS_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_EXPANDER)) 28 | 29 | KotoExpander * koto_expander_new(gchar * primary_icon_name, gchar * primary_label_text); 30 | KotoExpander * koto_expander_new_with_button( 31 | gchar * primary_icon_name, 32 | gchar * primary_label_text, 33 | KotoButton * secondary_button 34 | ); 35 | 36 | GtkWidget * koto_expander_get_content(KotoExpander * self); 37 | 38 | void koto_expander_set_icon_name( 39 | KotoExpander * self, 40 | gchar * icon 41 | ); 42 | 43 | void koto_expander_set_label( 44 | KotoExpander * self, 45 | const gchar * label 46 | ); 47 | 48 | void koto_expander_set_secondary_button( 49 | KotoExpander * self, 50 | KotoButton * new_button 51 | ); 52 | 53 | void koto_expander_set_content( 54 | KotoExpander * self, 55 | GtkWidget * new_content 56 | ); 57 | 58 | void koto_expander_toggle_content( 59 | GtkGestureClick * gesture, 60 | int n_press, 61 | double x, 62 | double y, 63 | gpointer data 64 | ); 65 | 66 | G_END_DECLS 67 | -------------------------------------------------------------------------------- /src/components/cover-art-button.h: -------------------------------------------------------------------------------- 1 | /* cover-art-button.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "button.h" 21 | 22 | G_BEGIN_DECLS 23 | 24 | #define KOTO_TYPE_COVER_ART_BUTTON (koto_cover_art_button_get_type()) 25 | G_DECLARE_FINAL_TYPE(KotoCoverArtButton, koto_cover_art_button, KOTO, COVER_ART_BUTTON, GObject); 26 | #define KOTO_IS_COVER_ART_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_COVER_ART_BUTTON)) 27 | 28 | /** 29 | * Cover Art Functions 30 | **/ 31 | 32 | KotoCoverArtButton * koto_cover_art_button_new( 33 | guint height, 34 | guint width, 35 | gchar * art_path 36 | ); 37 | 38 | KotoButton * koto_cover_art_button_get_button(KotoCoverArtButton * self); 39 | 40 | GtkWidget * koto_cover_art_button_get_main(KotoCoverArtButton * self); 41 | 42 | void koto_cover_art_button_hide_overlay_controls( 43 | GtkEventControllerFocus * controller, 44 | gpointer data 45 | ); 46 | 47 | void koto_cover_art_button_set_art_path( 48 | KotoCoverArtButton * self, 49 | gchar * art_path 50 | ); 51 | 52 | void koto_cover_art_button_set_dimensions( 53 | KotoCoverArtButton * self, 54 | guint height, 55 | guint width 56 | ); 57 | 58 | void koto_cover_art_button_show_overlay_controls( 59 | GtkEventControllerFocus * controller, 60 | gpointer data 61 | ); 62 | 63 | G_END_DECLS -------------------------------------------------------------------------------- /src/koto-window.h: -------------------------------------------------------------------------------- 1 | /* koto-window.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include "config/config.h" 22 | #include "db/cartographer.h" 23 | #include "playlist/playlist.h" 24 | 25 | G_BEGIN_DECLS 26 | 27 | #define KOTO_TYPE_WINDOW (koto_window_get_type()) 28 | 29 | G_DECLARE_FINAL_TYPE(KotoWindow, koto_window, KOTO, WINDOW, GtkApplicationWindow) 30 | 31 | void koto_window_add_page( 32 | KotoWindow * self, 33 | gchar * page_name, 34 | GtkWidget * page 35 | ); 36 | 37 | void koto_window_go_to_page( 38 | KotoWindow * self, 39 | gchar * page_name 40 | ); 41 | 42 | void koto_window_handle_playlist_added( 43 | KotoCartographer * carto, 44 | KotoPlaylist * playlist, 45 | gpointer user_data 46 | ); 47 | 48 | void koto_window_manage_style( 49 | KotoConfig * config, 50 | guint prop_id, 51 | KotoWindow * self 52 | ); 53 | 54 | void koto_window_hide_dialogs(KotoWindow * self); 55 | 56 | void koto_window_remove_page( 57 | KotoWindow * self, 58 | gchar * page_name 59 | ); 60 | 61 | void koto_window_show_dialog( 62 | KotoWindow * self, 63 | gchar * dialog_name 64 | ); 65 | 66 | void create_new_headerbar(KotoWindow * self); 67 | 68 | void handle_album_added(); 69 | 70 | void load_library(KotoWindow * self); 71 | 72 | void set_optimal_default_window_size(KotoWindow * self); 73 | 74 | G_END_DECLS 75 | -------------------------------------------------------------------------------- /src/koto-utils.h: -------------------------------------------------------------------------------- 1 | /* koto-utils.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | 22 | G_BEGIN_DECLS 23 | 24 | GtkFileChooserNative * koto_utils_create_image_file_chooser(gchar * file_chooser_label); 25 | 26 | GtkWidget * koto_utils_create_image_from_filepath( 27 | gchar * filepath, 28 | gchar * fallback_icon, 29 | guint width, 30 | guint height 31 | ); 32 | 33 | gchar * koto_utils_gboolean_to_string(gboolean b); 34 | 35 | gchar * koto_utils_get_filename_without_extension(gchar * filename); 36 | 37 | gchar * koto_utils_join_string_list( 38 | GList * list, 39 | gchar * sep 40 | ); 41 | 42 | void koto_utils_mkdir(gchar * path); 43 | 44 | void koto_utils_push_queue_element_to_store( 45 | gpointer data, 46 | gpointer user_data 47 | ); 48 | 49 | gchar * koto_utils_seconds_to_time_format(guint64 seconds); 50 | 51 | gboolean koto_utils_string_contains_substring( 52 | gchar * s, 53 | gchar * sub 54 | ); 55 | 56 | gchar * koto_utils_string_get_valid(gchar * str); 57 | 58 | gboolean koto_utils_string_is_valid(const gchar * str); 59 | 60 | gchar * koto_utils_string_replace_all( 61 | gchar * str, 62 | gchar * find, 63 | gchar * repl 64 | ); 65 | 66 | GList * koto_utils_string_to_string_list( 67 | gchar * s, 68 | gchar * sep 69 | ); 70 | 71 | gchar * koto_utils_string_title(const gchar * s); 72 | 73 | gchar * koto_utils_string_unquote(gchar * s); 74 | 75 | G_END_DECLS 76 | -------------------------------------------------------------------------------- /src/pages/music/artist-view.h: -------------------------------------------------------------------------------- 1 | /* artist-view.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../../indexer/structs.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define KOTO_TYPE_ARTIST_VIEW (koto_artist_view_get_type()) 27 | 28 | G_DECLARE_FINAL_TYPE(KotoArtistView, koto_artist_view, KOTO, ARTIST_VIEW, GObject) 29 | 30 | KotoArtistView* koto_artist_view_new(KotoArtist * artist); 31 | 32 | void koto_artist_view_add_album( 33 | KotoArtistView * self, 34 | KotoAlbum * album 35 | ); 36 | 37 | GtkWidget * koto_artist_view_get_main(KotoArtistView * self); 38 | 39 | void koto_artist_view_handle_album_added( 40 | KotoArtist * artist, 41 | KotoAlbum * album, 42 | gpointer user_data 43 | ); 44 | 45 | void koto_artist_view_handle_album_removed( 46 | KotoArtist * artist, 47 | gchar * album_uuid, 48 | gpointer user_data 49 | ); 50 | 51 | void koto_artist_view_handle_artist_name_changed( 52 | KotoArtist * artist, 53 | guint prop_id, 54 | KotoArtistView * self 55 | ); 56 | 57 | void koto_artist_view_handle_has_no_albums( 58 | KotoArtist * artist, 59 | gpointer user_data 60 | ); 61 | 62 | void koto_artist_view_set_artist( 63 | KotoArtistView * self, 64 | KotoArtist * artist 65 | ); 66 | 67 | void koto_artist_view_toggle_playback( 68 | GtkGestureClick * gesture, 69 | int n_press, 70 | double x, 71 | double y, 72 | gpointer data 73 | ); 74 | 75 | G_END_DECLS 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Koto 2 | 3 |  4 | 5 | Koto is an in-development audiobook, music, and podcast manager that is designed *for* and caters *to* a modern desktop 6 | Linux experience. **Nothing to see here yet**. 7 | 8 | ## Blog 9 | 10 | - [Dev Diary 13: Koto - The Resurrection](https://joshuastrobl.com/2024/11/01/dev-diary-13-koto-the-resurrection) 11 | - [Dev Diary 12: Koto August Progress Report](https://joshuastrobl.com/2021/09/06/dev-diary-12-koto-august-progress-report) 12 | - [Dev Diary 11: Koto July Progress Report](https://joshuastrobl.com/2021/08/08/dev-diary-11-koto-july-progress-report/) 13 | - [Dev Diary 10: Koto June Progress Report](https://joshuastrobl.com/2021/07/08/dev-diary-10-koto-june-progress-report/) 14 | - [Dev Diary 9: Koto May Progress Report (B-side)](https://joshuastrobl.com/2021/06/10/dev-diary-9-koto-may-progress-report-b-side/) 15 | - [Dev Diary 8: Koto May Progress Report (A-side)](https://joshuastrobl.com/2021/05/27/dev-diary-8-koto-may-progress-report-a-side/) 16 | - [Dev Diary 7: Koto April Progress Report (B-side)](https://joshuastrobl.com/2021/05/07/dev-diary-7-koto-april-progress-report-b-side/) 17 | - [Dev Diary 6: Koto April Progress Report (A-side)](https://joshuastrobl.com/2021/04/26/dev-diary-6-koto-april-progress-report-a-side/) 18 | - [Dev Diary 5: Koto March Progress Report (B-side)](https://joshuastrobl.com/2021/04/08/dev-diary-5-koto-march-progress-report-b-side/) 19 | - [Dev Diary 4: Koto March Progress Report (A-side)](https://joshuastrobl.com/2021/03/26/dev-diary-4-koto-march-progress-report-a-side/) 20 | - [Dev Diary 3: Koto February Progress Report (B-side)](https://joshuastrobl.com/2021/03/05/dev-diary-3-koto-february-progress-report-b-side/) 21 | - [Dev Diary 2: Koto February Progress Report (A-side)](https://joshuastrobl.com/2021/02/17/dev-diary-2-koto-february-progress-report-a-side/) 22 | - [Dev Diary 1: Koto - Foundations](https://joshuastrobl.com/2021/01/25/dev-diary-1-koto-foundations/) 23 | 24 | ## License 25 | 26 | Koto is licensed under the Apache 2.0 license. 27 | -------------------------------------------------------------------------------- /src/pages/music/music-local.h: -------------------------------------------------------------------------------- 1 | /* music-local.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../../indexer/structs.h" 23 | #include "artist-view.h" 24 | 25 | G_BEGIN_DECLS 26 | 27 | #define KOTO_TYPE_PAGE_MUSIC_LOCAL (koto_page_music_local_get_type()) 28 | 29 | G_DECLARE_FINAL_TYPE(KotoPageMusicLocal, koto_page_music_local, KOTO, PAGE_MUSIC_LOCAL, GtkBox) 30 | 31 | KotoPageMusicLocal* koto_page_music_local_new(); 32 | void koto_page_music_local_add_artist( 33 | KotoPageMusicLocal * self, 34 | KotoArtist * artist 35 | ); 36 | 37 | void koto_page_music_local_handle_artist_click( 38 | GtkListBox * box, 39 | GtkListBoxRow * row, 40 | gpointer data 41 | ); 42 | 43 | void koto_page_music_local_handle_artist_added( 44 | KotoCartographer * carto, 45 | KotoArtist * artist, 46 | gpointer user_data 47 | ); 48 | 49 | void koto_page_music_local_handle_artist_removed( 50 | KotoCartographer * carto, 51 | gchar * artist_uuid, 52 | gchar * artist_name, 53 | gpointer user_data 54 | ); 55 | 56 | void koto_page_music_local_go_to_artist_by_name( 57 | KotoPageMusicLocal * self, 58 | gchar * artist_name 59 | ); 60 | 61 | void koto_page_music_local_go_to_artist_by_uuid( 62 | KotoPageMusicLocal * self, 63 | gchar * artist_uuid 64 | ); 65 | 66 | int koto_page_music_local_sort_artists( 67 | GtkListBoxRow * artist1, 68 | GtkListBoxRow * artist2, 69 | gpointer user_data 70 | ); 71 | 72 | G_END_DECLS 73 | -------------------------------------------------------------------------------- /src/pages/audiobooks/library.h: -------------------------------------------------------------------------------- 1 | /* library.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../../db/cartographer.h" 23 | #include "../../indexer/structs.h" 24 | 25 | G_BEGIN_DECLS 26 | 27 | #define KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE koto_audiobooks_library_page_get_type() 28 | #define KOTO_AUDIOBOOKS_LIBRARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE, KotoAudiobooksLibraryPage)) 29 | typedef struct _KotoAudiobooksLibraryPage KotoAudiobooksLibraryPage; 30 | typedef struct _KotoAudiobooksLibraryPageClass KotoAudiobooksLibraryPageClass; 31 | 32 | GLIB_AVAILABLE_IN_ALL 33 | GType koto_audiobooks_library_page_get_type(void) G_GNUC_CONST; 34 | 35 | #define KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE)) 36 | 37 | void koto_audiobooks_library_page_add_genres( 38 | KotoAudiobooksLibraryPage * self, 39 | GList * genres 40 | ); 41 | 42 | void koto_audiobooks_library_page_handle_add_album( 43 | KotoCartographer * carto, 44 | KotoAlbum * album, 45 | KotoAudiobooksLibraryPage * self 46 | ); 47 | 48 | void koto_audiobooks_library_page_handle_add_artist( 49 | KotoCartographer * carto, 50 | KotoArtist * artist, 51 | KotoAudiobooksLibraryPage * self 52 | ); 53 | 54 | GtkWidget * koto_audiobooks_library_page_get_main(KotoAudiobooksLibraryPage * self); 55 | 56 | KotoAudiobooksLibraryPage * koto_audiobooks_library_page_new(); -------------------------------------------------------------------------------- /src/playback/mpris.h: -------------------------------------------------------------------------------- 1 | /* mpris.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "../indexer/structs.h" 23 | 24 | void koto_update_mpris_playback_state(GstState state); 25 | 26 | void koto_update_mpris_info_for_track(KotoTrack * track); 27 | 28 | void koto_update_mpris_info_for_track_with_metadata( 29 | KotoTrack * track, 30 | GVariant * metadata 31 | ); 32 | 33 | void handle_method_call( 34 | GDBusConnection * connection, 35 | const gchar * sender, 36 | const gchar * object_path, 37 | const gchar * interface_name, 38 | const gchar * method_name, 39 | GVariant * parameters, 40 | GDBusMethodInvocation * invocation, 41 | gpointer user_data 42 | ); 43 | 44 | GVariant * handle_get_property( 45 | GDBusConnection * connection, 46 | const gchar * sender, 47 | const gchar * object_path, 48 | const gchar * interface_name, 49 | const gchar * property_name, 50 | GError ** error, 51 | gpointer user_data 52 | ); 53 | 54 | gboolean handle_set_property( 55 | GDBusConnection * connection, 56 | const gchar * sender, 57 | const gchar * object_path, 58 | const gchar * interface_name, 59 | const gchar * property_name, 60 | GVariant * value, 61 | GError ** error, 62 | gpointer user_data 63 | ); 64 | 65 | void on_main_mpris_bus_acquired( 66 | GDBusConnection * connection, 67 | const gchar * name, 68 | gpointer user_data 69 | ); 70 | 71 | void setup_mpris_interfaces(); 72 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | add_global_arguments([ 2 | '-I' + meson.current_build_dir(), 3 | '-Db_sanitize=address', 4 | '-Dwerror=true', 5 | ], language: 'c') 6 | 7 | koto_sources = [ 8 | 'components/album-info.c', 9 | 'components/action-bar.c', 10 | 'components/button.c', 11 | 'components/cover-art-button.c', 12 | 'components/track-item.c', 13 | 'components/track-table.c', 14 | 'config/config.c', 15 | 'db/cartographer.c', 16 | 'db/db.c', 17 | 'db/loaders.c', 18 | 'indexer/album.c', 19 | 'indexer/artist.c', 20 | 'indexer/file-indexer.c', 21 | 'indexer/library.c', 22 | 'indexer/track-helpers.c', 23 | 'indexer/track.c', 24 | 'pages/audiobooks/audiobook-view.c', 25 | 'pages/audiobooks/genres-banner.c', 26 | 'pages/audiobooks/genre-button.c', 27 | 'pages/audiobooks/library.c', 28 | 'pages/audiobooks/writer-page.c', 29 | 'pages/music/album-view.c', 30 | 'pages/music/artist-view.c', 31 | 'pages/music/disc-view.c', 32 | 'pages/music/music-local.c', 33 | 'pages/playlist/list.c', 34 | 'playback/engine.c', 35 | 'playback/media-keys.c', 36 | 'playback/mimes.c', 37 | 'playback/mpris.c', 38 | 'playlist/add-remove-track-popover.c', 39 | 'playlist/create-modify-dialog.c', 40 | 'playlist/current.c', 41 | 'playlist/playlist.c', 42 | 'main.c', 43 | 'koto-dialog-container.c', 44 | 'koto-expander.c', 45 | 'koto-nav.c', 46 | 'koto-playerbar.c', 47 | 'koto-paths.c', 48 | 'koto-utils.c', 49 | 'koto-window.c', 50 | ] 51 | 52 | koto_deps = [ 53 | dependency('glib-2.0', version: '>= 2.66'), 54 | dependency('gio-2.0', version: '>= 2.66'), 55 | dependency('gstreamer-1.0', version: '>= 1.18'), 56 | dependency('gstreamer-player-1.0', version: '>= 1.18'), 57 | dependency('gtk4', version: '>= 4.0'), 58 | dependency('libmagic', version: '>=5.39'), 59 | dependency('sqlite3', version: '>=3.34'), 60 | dependency('taglib_c', version: '>=1.11'), 61 | toml_dep, 62 | ] 63 | 64 | gnome = import('gnome') 65 | 66 | koto_sources += gnome.compile_resources('koto-resources', 67 | 'koto.gresource.xml', 68 | dependencies: themes, 69 | c_name: 'koto', 70 | ) 71 | 72 | executable('com.github.joshstrobl.koto', koto_sources, 73 | dependencies: koto_deps, 74 | install: true, 75 | ) 76 | -------------------------------------------------------------------------------- /src/playlist/create-modify-dialog.h: -------------------------------------------------------------------------------- 1 | /* create-modify-dialog.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | 22 | G_BEGIN_DECLS 23 | 24 | /** 25 | * Type Definition 26 | **/ 27 | 28 | #define KOTO_TYPE_CREATE_MODIFY_PLAYLIST_DIALOG koto_create_modify_playlist_dialog_get_type() 29 | G_DECLARE_FINAL_TYPE(KotoCreateModifyPlaylistDialog, koto_create_modify_playlist_dialog, KOTO, CREATE_MODIFY_PLAYLIST_DIALOG, GtkBox); 30 | #define KOTO_IS_CURRENT_MODIFY_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_CREATE_MODIFY_PLAYLIST_DIALOG)) 31 | 32 | /** 33 | * Functions 34 | **/ 35 | 36 | KotoCreateModifyPlaylistDialog * koto_create_modify_playlist_dialog_new(); 37 | 38 | void koto_create_modify_playlist_dialog_handle_chooser_response( 39 | GtkNativeDialog * native, 40 | int response, 41 | gpointer user_data 42 | ); 43 | 44 | void koto_create_modify_playlist_dialog_handle_create_click( 45 | GtkButton * button, 46 | gpointer user_data 47 | ); 48 | 49 | gboolean koto_create_modify_playlist_dialog_handle_drop( 50 | GtkDropTarget * target, 51 | const GValue * val, 52 | double x, 53 | double y, 54 | gpointer user_data 55 | ); 56 | 57 | void koto_create_modify_playlist_dialog_handle_image_click( 58 | GtkGestureClick * gesture, 59 | int n_press, 60 | double x, 61 | double y, 62 | gpointer user_data 63 | ); 64 | 65 | void koto_create_modify_playlist_dialog_reset(KotoCreateModifyPlaylistDialog * self); 66 | 67 | void koto_create_modify_playlist_dialog_set_playlist_uuid( 68 | KotoCreateModifyPlaylistDialog * self, 69 | gchar * playlist_uuid 70 | ); 71 | 72 | G_END_DECLS 73 | -------------------------------------------------------------------------------- /src/playlist/add-remove-track-popover.h: -------------------------------------------------------------------------------- 1 | /* add-remove-track-popover.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | #include "../db/cartographer.h" 22 | #include "playlist.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | /** 27 | * Type Definition 28 | **/ 29 | 30 | #define KOTO_TYPE_ADD_REMOVE_TRACK_POPOVER koto_add_remove_track_popover_get_type() 31 | G_DECLARE_FINAL_TYPE(KotoAddRemoveTrackPopover, koto_add_remove_track_popover, KOTO, ADD_REMOVE_TRACK_POPOVER, GtkPopover); 32 | #define KOTO_JS_ADD_REMOVE_TRACK_POPOVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ADD_REMOVE_TRACK_POPOVER)) 33 | 34 | /** 35 | * Functions 36 | **/ 37 | 38 | KotoAddRemoveTrackPopover * koto_add_remove_track_popover_new(); 39 | 40 | void koto_add_remove_track_popover_add_playlist( 41 | KotoAddRemoveTrackPopover * self, 42 | KotoPlaylist * playlist 43 | ); 44 | 45 | void koto_add_remove_track_popover_clear_tracks(KotoAddRemoveTrackPopover * self); 46 | 47 | void koto_add_remove_track_popover_remove_playlist( 48 | KotoAddRemoveTrackPopover * self, 49 | gchar * playlist_uuid 50 | ); 51 | 52 | void koto_add_remove_track_popover_handle_checkbutton_toggle( 53 | GtkCheckButton * btn, 54 | gpointer user_data 55 | ); 56 | 57 | void koto_add_remove_track_popover_handle_playlist_added( 58 | KotoCartographer * carto, 59 | KotoPlaylist * playlist, 60 | gpointer user_data 61 | ); 62 | 63 | void koto_add_remove_track_popover_handle_playlist_removed( 64 | KotoCartographer * carto, 65 | gchar * playlist_uuid, 66 | gpointer user_data 67 | ); 68 | 69 | void koto_add_remove_track_popover_set_pointing_to_widget( 70 | KotoAddRemoveTrackPopover * self, 71 | GtkWidget * widget, 72 | GtkPositionType pos 73 | ); 74 | 75 | void koto_add_remove_track_popover_set_tracks( 76 | KotoAddRemoveTrackPopover * self, 77 | GList * tracks 78 | ); 79 | 80 | G_END_DECLS -------------------------------------------------------------------------------- /src/pages/playlist/list.h: -------------------------------------------------------------------------------- 1 | /* list.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../../components/action-bar.h" 23 | #include "../../playlist/playlist.h" 24 | #include "../../koto-utils.h" 25 | 26 | G_BEGIN_DECLS 27 | 28 | #define KOTO_TYPE_PLAYLIST_PAGE koto_playlist_page_get_type() 29 | G_DECLARE_FINAL_TYPE(KotoPlaylistPage, koto_playlist_page, KOTO, PLAYLIST_PAGE, GObject); 30 | #define KOTO_IS_PLAYLIST_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYLIST_PAGE)) 31 | 32 | KotoPlaylistPage * koto_playlist_page_new(gchar * playlist_uuid); 33 | 34 | void koto_playlist_page_add_track( 35 | KotoPlaylistPage * self, 36 | const gchar * track_uuid 37 | ); 38 | 39 | void koto_playlist_page_create_tracks_header(KotoPlaylistPage * self); 40 | 41 | void koto_playlist_page_bind_track_item( 42 | GtkListItemFactory * factory, 43 | GtkListItem * item, 44 | KotoPlaylistPage * self 45 | ); 46 | 47 | void koto_playlist_page_remove_track( 48 | KotoPlaylistPage * self, 49 | const gchar * track_uuid 50 | ); 51 | 52 | GtkWidget * koto_playlist_page_get_main(KotoPlaylistPage * self); 53 | 54 | 55 | void koto_playlist_page_handle_cover_art_clicked( 56 | GtkGestureClick * gesture, 57 | int n_press, 58 | double x, 59 | double y, 60 | gpointer user_data 61 | ); 62 | 63 | void koto_playlist_page_handle_edit_button_clicked( 64 | GtkGestureClick * gesture, 65 | int n_press, 66 | double x, 67 | double y, 68 | gpointer user_data 69 | ); 70 | 71 | void koto_playlist_page_handle_playlist_modified( 72 | KotoPlaylist * playlist, 73 | gpointer user_data 74 | ); 75 | 76 | void koto_playlist_page_hide_popover(KotoPlaylistPage * self); 77 | 78 | void koto_playlist_page_set_playlist_uuid( 79 | KotoPlaylistPage * self, 80 | gchar * playlist_uuid 81 | ); 82 | 83 | void koto_playlist_page_show_popover(KotoPlaylistPage * self); 84 | 85 | void koto_playlist_page_update_header(KotoPlaylistPage * self); 86 | 87 | G_END_DECLS 88 | -------------------------------------------------------------------------------- /src/components/action-bar.h: -------------------------------------------------------------------------------- 1 | /* action-bar.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../indexer/structs.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | typedef enum { 27 | KOTO_ACTION_BAR_IS_ALBUM_RELATIVE = 1, 28 | KOTO_ACTION_BAR_IS_PLAYLIST_RELATIVE = 2 29 | } KotoActionBarRelative; 30 | 31 | #define KOTO_TYPE_ACTION_BAR (koto_action_bar_get_type()) 32 | #define KOTO_IS_ACTION_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ACTION_BAR)) 33 | 34 | typedef struct _KotoActionBar KotoActionBar; 35 | typedef struct _KotoActionBarClass KotoActionBarClass; 36 | 37 | GLIB_AVAILABLE_IN_ALL 38 | GType koto_action_bar_get_type(void) G_GNUC_CONST; 39 | 40 | /** 41 | * Action Bar Functions 42 | **/ 43 | 44 | KotoActionBar * koto_action_bar_new(void); 45 | 46 | void koto_action_bar_close(KotoActionBar * self); 47 | 48 | GtkActionBar * koto_action_bar_get_main(KotoActionBar * self); 49 | 50 | void koto_action_bar_handle_close_button_clicked( 51 | GtkGestureClick * gesture, 52 | int n_press, 53 | double x, 54 | double y, 55 | gpointer data 56 | ); 57 | 58 | void koto_action_bar_handle_go_to_artist_button_clicked( 59 | GtkButton * button, 60 | gpointer data 61 | ); 62 | 63 | void koto_action_bar_handle_playlists_button_clicked( 64 | GtkButton * button, 65 | gpointer data 66 | ); 67 | 68 | void koto_action_bar_handle_play_track_button_clicked( 69 | GtkButton * button, 70 | gpointer data 71 | ); 72 | 73 | void koto_action_bar_handle_remove_from_playlist_button_clicked( 74 | GtkButton * button, 75 | gpointer data 76 | ); 77 | 78 | void koto_action_bar_set_tracks_in_album_selection( 79 | KotoActionBar * self, 80 | gchar * album_uuid, 81 | GList * tracks 82 | ); 83 | 84 | void koto_action_bar_set_tracks_in_playlist_selection( 85 | KotoActionBar * self, 86 | gchar * playlist_uuid, 87 | GList * tracks 88 | ); 89 | 90 | void koto_action_bar_toggle_go_to_artist_visibility( 91 | KotoActionBar * self, 92 | gboolean visible 93 | ); 94 | 95 | void koto_action_bar_toggle_play_button_visibility( 96 | KotoActionBar * self, 97 | gboolean visible 98 | ); 99 | 100 | void koto_action_bar_toggle_reveal( 101 | KotoActionBar * self, 102 | gboolean state 103 | ); 104 | 105 | G_END_DECLS -------------------------------------------------------------------------------- /src/playback/mimes.c: -------------------------------------------------------------------------------- 1 | /* mimes.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "../koto-utils.h" 21 | 22 | GHashTable * supported_mimes_hash = NULL; 23 | GList * supported_mimes = NULL; 24 | 25 | gboolean koto_playback_engine_gst_caps_iter( 26 | GstCapsFeatures * features, 27 | GstStructure * structure, 28 | gpointer user_data 29 | ) { 30 | (void) features; 31 | (void) user_data; 32 | gchar * caps_name = (gchar*) gst_structure_get_name(structure); // Get the name, typically a mimetype 33 | 34 | if (g_str_has_prefix(caps_name, "unknown")) { // Is unknown 35 | return TRUE; 36 | } 37 | 38 | if (g_hash_table_contains(supported_mimes_hash, caps_name)) { // Found in list already 39 | return TRUE; 40 | } 41 | 42 | g_hash_table_add(supported_mimes_hash, g_strdup(caps_name)); 43 | supported_mimes = g_list_prepend(supported_mimes, g_strdup(caps_name)); 44 | supported_mimes = g_list_prepend(supported_mimes, g_strdup(koto_utils_string_replace_all(caps_name, "x-", ""))); 45 | 46 | return TRUE; 47 | } 48 | 49 | void koto_playback_engine_gst_pad_iter( 50 | gpointer list_data, 51 | gpointer user_data 52 | ) { 53 | (void) user_data; 54 | GstStaticPadTemplate * templ = list_data; 55 | 56 | if (templ->direction == GST_PAD_SINK) { // Is a sink pad 57 | GstCaps * capabilities = gst_static_pad_template_get_caps(templ); // Get the capabilities 58 | gst_caps_foreach(capabilities, koto_playback_engine_gst_caps_iter, NULL); // Iterate over and add to mimes 59 | gst_caps_unref(capabilities); 60 | } 61 | } 62 | 63 | void koto_playback_engine_get_supported_mimetypes() { 64 | // Credit for code goes to https://github.com/mopidy/mopidy/issues/812#issuecomment-75868363 65 | // These are GstElementFactory 66 | GList * elements = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER | GST_ELEMENT_FACTORY_TYPE_DEMUXER | GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, GST_RANK_NONE); 67 | 68 | GList * ele; 69 | 70 | for (ele = elements; ele != NULL; ele = ele->next) { // For each of the elements 71 | // GList of GstStaticPadTemplate 72 | GList * static_pads = (GList*) gst_element_factory_get_static_pad_templates(ele->data); // Get the pads 73 | g_list_foreach(static_pads, koto_playback_engine_gst_pad_iter, NULL); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /theme/components/_gtk-overrides.scss: -------------------------------------------------------------------------------- 1 | @import '../vars'; 2 | 3 | @mixin selected-row-styling { 4 | color: $selected-row-color-text; 5 | background-color: $selected-row-color-bg; 6 | border: 0; // Don't have a border 7 | border-image: none; // GTK uses an image which is weird 8 | border-image-width: 0; 9 | outline: none; 10 | outline-offset: 0; 11 | outline-style: none; 12 | } 13 | 14 | button { 15 | .text-button { 16 | &:not(.destructive-action):not(.suggested-action) { // Is just a plain ol' normal text button 17 | color: $button-normal-color-text; 18 | background: $button-normal-color-bg; 19 | border-color: $button-normal-color-border; 20 | } 21 | } 22 | 23 | &.destructive-action { 24 | color: $button-destructive-color-text; 25 | background-color: $button-destructive-color-bg; 26 | background-image: none; 27 | border-width: 0; 28 | } 29 | 30 | &.suggested-action { // Adwaita makes it blue but we want it green 31 | color: $button-suggested-color-text; 32 | background-color: $button-suggested-color-bg; 33 | background-image: none; 34 | border-width: 0; 35 | } 36 | } 37 | 38 | checkbutton { 39 | color: $text-color-bright; 40 | 41 | &:active { 42 | background-color: transparent; 43 | } 44 | } 45 | 46 | check { 47 | background-image: none; 48 | 49 | &:not(:checked) { // Not checked 50 | color: $text-color-faded; 51 | } 52 | 53 | &:active { 54 | background-color: transparent; 55 | } 56 | 57 | &:checked { // Checked but not actively pressing on it 58 | color: $text-color-bright; 59 | background-color: $koto-primary-color; 60 | } 61 | } 62 | 63 | entry { 64 | color: $text-color-bright; 65 | background: $input-background; 66 | border-color: $border-color; 67 | 68 | placeholder { // Placeholder text 69 | color: $text-color-faded; 70 | } 71 | } 72 | 73 | listview { 74 | background-color: transparent; 75 | } 76 | 77 | list:not(.discs-list), listview { 78 | &:not(.track-list) > row { // Rows which are now in the track list 79 | &:active, &:selected { // Active or selected 80 | @include selected-row-styling; 81 | } 82 | } 83 | 84 | &.track-list > row { 85 | &:selected { // Only selected rows 86 | @include selected-row-styling; 87 | } 88 | } 89 | } 90 | 91 | popover.background { 92 | & > arrow, & > contents { 93 | background-color: $bg-primary; 94 | border-color: $border-color; 95 | } 96 | 97 | & > contents { 98 | color: $text-color-bright; 99 | } 100 | } 101 | 102 | range { 103 | &.dragging { // Dragging a range 104 | & > trough { 105 | highlight { 106 | border-color: $koto-primary-color; 107 | } 108 | 109 | & > slider { 110 | background-color: $koto-primary-color; 111 | } 112 | } 113 | } 114 | } 115 | 116 | scale { // Progress bar 117 | highlight { 118 | background-color: $koto-primary-color; 119 | border-color: $koto-primary-color; 120 | } 121 | 122 | slider { // Slider 123 | outline-color: $koto-primary-color; 124 | } 125 | } 126 | 127 | scalebutton { 128 | &, button, image { 129 | color: $player-bar-icon-color; 130 | } 131 | 132 | &:active, &:checked, &:hover, button:active, button:checked, button:hover { 133 | background-color: transparent; 134 | } 135 | } -------------------------------------------------------------------------------- /src/components/track-table.h: -------------------------------------------------------------------------------- 1 | /* track-table.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include "../playlist/playlist.h" 23 | #include "action-bar.h" 24 | 25 | G_BEGIN_DECLS 26 | 27 | #define KOTO_TYPE_TRACK_TABLE koto_track_table_get_type() 28 | #define KOTO_TRACK_TABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_TRACK_TABLE, KotoTrackTable)) 29 | typedef struct _KotoTrackTable KotoTrackTable; 30 | typedef struct _KotoTrackTableClass KotoTrackTableClass; 31 | 32 | GLIB_AVAILABLE_IN_ALL 33 | GType koto_track_type_get_type(void) G_GNUC_CONST; 34 | 35 | #define KOTO_IS_TRACK_TABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_TRACK_TABLE)) 36 | 37 | void koto_track_table_bind_track_item( 38 | GtkListItemFactory * factory, 39 | GtkListItem * item, 40 | KotoTrackTable * self 41 | ); 42 | 43 | void koto_track_table_create_tracks_header(KotoTrackTable * self); 44 | 45 | GtkWidget * koto_track_table_get_main(KotoTrackTable * self); 46 | 47 | void koto_track_table_handle_action_bar_closed( 48 | KotoActionBar * bar, 49 | gpointer data 50 | ); 51 | 52 | void koto_track_table_handle_track_album_clicked( 53 | GtkGestureClick * gesture, 54 | int n_press, 55 | double x, 56 | double y, 57 | gpointer user_data 58 | ); 59 | 60 | void koto_track_table_handle_track_artist_clicked( 61 | GtkGestureClick * gesture, 62 | int n_press, 63 | double x, 64 | double y, 65 | gpointer user_data 66 | ); 67 | 68 | void koto_track_table_handle_track_name_clicked( 69 | GtkGestureClick * gesture, 70 | int n_press, 71 | double x, 72 | double y, 73 | gpointer user_data 74 | ); 75 | 76 | void koto_track_table_handle_track_name_clicked( 77 | GtkGestureClick * gesture, 78 | int n_press, 79 | double x, 80 | double y, 81 | gpointer user_data 82 | ); 83 | 84 | void koto_track_table_handle_track_num_clicked( 85 | GtkGestureClick * gesture, 86 | int n_press, 87 | double x, 88 | double y, 89 | gpointer user_data 90 | ); 91 | 92 | void koto_track_table_handle_tracks_selected( 93 | GtkSelectionModel * model, 94 | guint position, 95 | guint n_items, 96 | gpointer user_data 97 | ); 98 | 99 | void koto_track_table_set_model( 100 | KotoTrackTable * self, 101 | KotoPreferredPlaylistSortType model 102 | ); 103 | 104 | void koto_track_table_set_playlist_model( 105 | KotoTrackTable * self, 106 | KotoPreferredPlaylistSortType model 107 | ); 108 | 109 | void koto_track_table_set_playlist( 110 | KotoTrackTable * self, 111 | KotoPlaylist * playlist 112 | ); 113 | 114 | void koto_track_table_setup_track_item( 115 | GtkListItemFactory * factory, 116 | GtkListItem * item, 117 | gpointer user_data 118 | ); 119 | 120 | KotoTrackTable * koto_track_table_new(); 121 | 122 | G_END_DECLS -------------------------------------------------------------------------------- /src/koto-dialog-container.c: -------------------------------------------------------------------------------- 1 | /* koto-dialog-container.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "components/button.h" 21 | #include "koto-dialog-container.h" 22 | 23 | struct _KotoDialogContainer { 24 | GtkBox parent_instance; 25 | KotoButton * close_button; 26 | GtkWidget * dialogs; 27 | }; 28 | 29 | G_DEFINE_TYPE(KotoDialogContainer, koto_dialog_container, GTK_TYPE_BOX); 30 | 31 | static void koto_dialog_container_class_init(KotoDialogContainerClass * c) { 32 | (void) c; 33 | } 34 | 35 | static void koto_dialog_container_init(KotoDialogContainer * self) { 36 | gtk_widget_add_css_class(GTK_WIDGET(self), "koto-dialog-container"); 37 | 38 | g_object_set(GTK_WIDGET(self), "hexpand", TRUE, "vexpand", TRUE, NULL); 39 | 40 | self->close_button = koto_button_new_with_icon(NULL, "window-close-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_LARGE); 41 | gtk_widget_set_halign(GTK_WIDGET(self->close_button), GTK_ALIGN_END); 42 | gtk_box_prepend(GTK_BOX(self), GTK_WIDGET(self->close_button)); // Add our close button 43 | 44 | self->dialogs = gtk_stack_new(); 45 | gtk_stack_set_transition_duration(GTK_STACK(self->dialogs), 0); // No transition timing 46 | gtk_stack_set_transition_type(GTK_STACK(self->dialogs), GTK_STACK_TRANSITION_TYPE_NONE); // No transition 47 | gtk_widget_set_halign(self->dialogs, GTK_ALIGN_CENTER); 48 | gtk_widget_set_hexpand(self->dialogs, TRUE); 49 | gtk_widget_set_valign(self->dialogs, GTK_ALIGN_CENTER); 50 | gtk_widget_set_vexpand(self->dialogs, TRUE); 51 | 52 | gtk_box_append(GTK_BOX(self), self->dialogs); // Add the dialogs stack 53 | 54 | koto_button_add_click_handler(self->close_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_dialog_container_handle_close_click), self); 55 | gtk_widget_hide(GTK_WIDGET(self)); // Hide by default 56 | } 57 | 58 | void koto_dialog_container_add_dialog( 59 | KotoDialogContainer * self, 60 | gchar * dialog_name, 61 | GtkWidget * dialog 62 | ) { 63 | if (!KOTO_IS_DIALOG_CONTAINER(self)) { // Not a dialog container 64 | return; 65 | } 66 | 67 | gtk_stack_add_named(GTK_STACK(self->dialogs), dialog, dialog_name); // Add the dialog to the stack 68 | } 69 | 70 | void koto_dialog_container_handle_close_click( 71 | GtkGestureClick * gesture, 72 | int n_press, 73 | double x, 74 | double y, 75 | gpointer user_data 76 | ) { 77 | (void) gesture; 78 | (void) n_press; 79 | (void) x; 80 | (void) y; 81 | koto_dialog_container_hide((KotoDialogContainer*) user_data); 82 | } 83 | 84 | void koto_dialog_container_hide(KotoDialogContainer * self) { 85 | if (!KOTO_IS_DIALOG_CONTAINER(self)) { // Not a dialog container 86 | return; 87 | } 88 | 89 | gtk_widget_hide(GTK_WIDGET(self)); 90 | } 91 | 92 | void koto_dialog_container_show_dialog( 93 | KotoDialogContainer * self, 94 | gchar * dialog_name 95 | ) { 96 | if (!KOTO_IS_DIALOG_CONTAINER(self)) { // Not a dialog container 97 | return; 98 | } 99 | 100 | gtk_stack_set_visible_child_name(GTK_STACK(self->dialogs), dialog_name); // Set to the dialog name 101 | gtk_widget_show(GTK_WIDGET(self)); // Ensure we show self 102 | } 103 | 104 | KotoDialogContainer * koto_dialog_container_new() { 105 | return g_object_new( 106 | KOTO_TYPE_DIALOG_CONTAINER, 107 | "orientation", 108 | GTK_ORIENTATION_VERTICAL, 109 | NULL 110 | ); 111 | } 112 | -------------------------------------------------------------------------------- /src/playlist/current.c: -------------------------------------------------------------------------------- 1 | /* current.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include "current.h" 20 | 21 | enum { 22 | SIGNAL_PLAYLIST_CHANGED, 23 | N_SIGNALS 24 | }; 25 | 26 | static guint signals[N_SIGNALS] = { 27 | 0 28 | }; 29 | 30 | KotoCurrentPlaylist * current_playlist = NULL; 31 | 32 | struct _KotoCurrentPlaylist { 33 | GObject parent_class; 34 | KotoPlaylist * current_playlist; 35 | }; 36 | 37 | struct _KotoCurrentPlaylistClass { 38 | GObjectClass parent_class; 39 | 40 | void (* playlist_changed) ( 41 | KotoCurrentPlaylist * self, 42 | KotoPlaylist * playlist, 43 | gboolean play_immediately 44 | ); 45 | }; 46 | 47 | static void koto_current_playlist_class_init(KotoCurrentPlaylistClass * c) { 48 | GObjectClass * gobject_class; 49 | 50 | gobject_class = G_OBJECT_CLASS(c); 51 | 52 | signals[SIGNAL_PLAYLIST_CHANGED] = g_signal_new( 53 | "playlist-changed", 54 | G_TYPE_FROM_CLASS(gobject_class), 55 | G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, 56 | G_STRUCT_OFFSET(KotoCurrentPlaylistClass, playlist_changed), 57 | NULL, 58 | NULL, 59 | NULL, 60 | G_TYPE_NONE, 61 | 3, 62 | KOTO_TYPE_PLAYLIST, 63 | G_TYPE_BOOLEAN, 64 | G_TYPE_BOOLEAN 65 | ); 66 | } 67 | 68 | G_DEFINE_TYPE(KotoCurrentPlaylist, koto_current_playlist, G_TYPE_OBJECT); 69 | 70 | static void koto_current_playlist_init(KotoCurrentPlaylist * self) { 71 | self->current_playlist = NULL; 72 | } 73 | 74 | KotoPlaylist * koto_current_playlist_get_playlist(KotoCurrentPlaylist * self) { 75 | return KOTO_IS_CURRENT_PLAYLIST(self) ? self->current_playlist : NULL; 76 | } 77 | 78 | void koto_current_playlist_save_playlist_state(KotoCurrentPlaylist * self) { 79 | if (!KOTO_IS_CURRENT_PLAYLIST(self)) { // Not a CurrentPlaylist 80 | return; 81 | } 82 | 83 | if (!KOTO_IS_PLAYLIST(self->current_playlist)) { // No playlist 84 | return; 85 | } 86 | 87 | koto_playlist_save_current_playback_state(self->current_playlist); // Save the current playlist state 88 | } 89 | 90 | void koto_current_playlist_set_playlist( 91 | KotoCurrentPlaylist * self, 92 | KotoPlaylist * playlist, 93 | gboolean play_immediately, 94 | gboolean play_current 95 | ) { 96 | if (!KOTO_IS_CURRENT_PLAYLIST(self)) { 97 | return; 98 | } 99 | 100 | if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist 101 | return; 102 | } 103 | 104 | if (KOTO_IS_PLAYLIST(self->current_playlist)) { 105 | koto_current_playlist_save_playlist_state(self); // Save the current playlist state if needed 106 | 107 | gboolean * is_temp = FALSE; 108 | g_object_get(self->current_playlist, "ephemeral", &is_temp, NULL); // Get the current ephemeral value 109 | 110 | if (is_temp) { // Is a temporary playlist 111 | koto_playlist_unmap(self->current_playlist); // Unmap the playlist if needed 112 | } else { // Not a temporary playlist 113 | koto_playlist_commit(self->current_playlist); // Save the current playlist 114 | } 115 | 116 | g_object_unref(self->current_playlist); // Unreference 117 | } 118 | 119 | self->current_playlist = playlist; 120 | g_object_ref(playlist); // Increment the reference 121 | g_signal_emit( 122 | self, 123 | signals[SIGNAL_PLAYLIST_CHANGED], 124 | 0, 125 | playlist, 126 | play_immediately, 127 | play_current 128 | ); 129 | } 130 | 131 | KotoCurrentPlaylist * koto_current_playlist_new() { 132 | return g_object_new(KOTO_TYPE_CURRENT_PLAYLIST, NULL); 133 | } 134 | -------------------------------------------------------------------------------- /src/components/track-item.c: -------------------------------------------------------------------------------- 1 | /* track-item.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include "indexer/structs.h" 20 | #include "playlist/add-remove-track-popover.h" 21 | #include "button.h" 22 | #include "track-item.h" 23 | 24 | extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup; 25 | 26 | struct _KotoTrackItem { 27 | GtkBox parent_instance; 28 | KotoTrack * track; 29 | 30 | GtkWidget * track_label; 31 | }; 32 | 33 | struct _KotoTrackItemClass { 34 | GtkBoxClass parent_class; 35 | }; 36 | 37 | enum { 38 | PROP_0, 39 | PROP_TRACK, 40 | N_PROPERTIES 41 | }; 42 | 43 | static GParamSpec * props[N_PROPERTIES] = { 44 | NULL, 45 | }; 46 | 47 | G_DEFINE_TYPE(KotoTrackItem, koto_track_item, GTK_TYPE_BOX); 48 | 49 | static void koto_track_item_get_property( 50 | GObject * obj, 51 | guint prop_id, 52 | GValue * val, 53 | GParamSpec * spec 54 | ); 55 | 56 | static void koto_track_item_set_property( 57 | GObject * obj, 58 | guint prop_id, 59 | const GValue * val, 60 | GParamSpec * spec 61 | ); 62 | 63 | static void koto_track_item_class_init(KotoTrackItemClass * c) { 64 | GObjectClass * gobject_class; 65 | 66 | gobject_class = G_OBJECT_CLASS(c); 67 | gobject_class->set_property = koto_track_item_set_property; 68 | gobject_class->get_property = koto_track_item_get_property; 69 | 70 | props[PROP_TRACK] = g_param_spec_object( 71 | "track", 72 | "Track", 73 | "Track", 74 | KOTO_TYPE_TRACK, 75 | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE 76 | ); 77 | 78 | g_object_class_install_properties(gobject_class, N_PROPERTIES, props); 79 | } 80 | 81 | static void koto_track_item_get_property( 82 | GObject * obj, 83 | guint prop_id, 84 | GValue * val, 85 | GParamSpec * spec 86 | ) { 87 | KotoTrackItem * self = KOTO_TRACK_ITEM(obj); 88 | 89 | switch (prop_id) { 90 | case PROP_TRACK: 91 | g_value_set_object(val, self->track); 92 | break; 93 | default: 94 | G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); 95 | break; 96 | } 97 | } 98 | 99 | static void koto_track_item_set_property( 100 | GObject * obj, 101 | guint prop_id, 102 | const GValue * val, 103 | GParamSpec * spec 104 | ) { 105 | KotoTrackItem * self = KOTO_TRACK_ITEM(obj); 106 | 107 | switch (prop_id) { 108 | case PROP_TRACK: 109 | koto_track_item_set_track(self, (KotoTrack*) g_value_get_object(val)); 110 | break; 111 | default: 112 | G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); 113 | break; 114 | } 115 | } 116 | 117 | static void koto_track_item_init(KotoTrackItem * self) { 118 | self->track_label = gtk_label_new(NULL); // Create with no track name 119 | gtk_label_set_xalign(GTK_LABEL(self->track_label), 0.0); 120 | 121 | gtk_widget_add_css_class(GTK_WIDGET(self), "track-item"); 122 | gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE); 123 | gtk_widget_set_hexpand(GTK_WIDGET(self->track_label), TRUE); 124 | 125 | gtk_box_prepend(GTK_BOX(self), self->track_label); 126 | } 127 | 128 | KotoTrack * koto_track_item_get_track(KotoTrackItem * self) { 129 | if (!KOTO_IS_TRACK_ITEM(self)) { 130 | return NULL; 131 | } 132 | 133 | return self->track; 134 | } 135 | 136 | void koto_track_item_set_track( 137 | KotoTrackItem * self, 138 | KotoTrack * track 139 | ) { 140 | if (track == NULL) { // Not a track 141 | return; 142 | } 143 | 144 | self->track = track; 145 | gchar * track_name; 146 | 147 | g_object_get(self->track, "parsed-name", &track_name, NULL); 148 | gtk_label_set_text(GTK_LABEL(self->track_label), track_name); // Update the text 149 | } 150 | 151 | KotoTrackItem * koto_track_item_new(KotoTrack * track) { 152 | return g_object_new( 153 | KOTO_TYPE_TRACK_ITEM, 154 | "track", 155 | track, 156 | NULL 157 | ); 158 | } 159 | -------------------------------------------------------------------------------- /src/components/button.h: -------------------------------------------------------------------------------- 1 | /* button.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | G_BEGIN_DECLS 25 | 26 | typedef enum { 27 | KOTO_BUTTON_CLICK_TYPE_PRIMARY = 1, 28 | KOTO_BUTTON_CLICK_TYPE_SECONDARY = 3 29 | } KotoButtonClickType; 30 | 31 | typedef enum { 32 | KOTO_BUTTON_PIXBUF_SIZE_INVALID, 33 | KOTO_BUTTON_PIXBUF_SIZE_TINY, 34 | KOTO_BUTTON_PIXBUF_SIZE_SMALL, 35 | KOTO_BUTTON_PIXBUF_SIZE_NORMAL, 36 | KOTO_BUTTON_PIXBUF_SIZE_LARGE, 37 | KOTO_BUTTON_PIXBUF_SIZE_MASSIVE, 38 | KOTO_BUTTON_PIXBUF_SIZE_GODLIKE 39 | } KotoButtonPixbufSize; 40 | 41 | typedef enum { 42 | KOTO_BUTTON_IMAGE_POS_LEFT, 43 | KOTO_BUTTON_IMAGE_POS_RIGHT 44 | } KotoButtonImagePosition; 45 | 46 | #define NUM_BUILTIN_SIZES 7 47 | 48 | #define KOTO_TYPE_BUTTON (koto_button_get_type()) 49 | G_DECLARE_FINAL_TYPE(KotoButton, koto_button, KOTO, BUTTON, GtkBox) 50 | #define KOTO_IS_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_BUTTON)) 51 | 52 | guint koto_get_pixbuf_size(KotoButtonPixbufSize size); 53 | 54 | KotoButton * koto_button_new_plain(gchar * label); 55 | 56 | KotoButton * koto_button_new_with_icon( 57 | gchar * label, 58 | gchar * icon_name, 59 | gchar * alt_icon_name, 60 | KotoButtonPixbufSize size 61 | ); 62 | 63 | KotoButton * koto_button_new_with_file( 64 | gchar * label, 65 | gchar * file_path, 66 | KotoButtonPixbufSize size 67 | ); 68 | 69 | KotoButton * koto_button_new_with_resource( 70 | gchar * resource_path, 71 | KotoButtonPixbufSize size 72 | ); 73 | 74 | void koto_button_add_click_handler( 75 | KotoButton * self, 76 | KotoButtonClickType button, 77 | GCallback handler, 78 | gpointer user_data 79 | ); 80 | 81 | void koto_button_flip(KotoButton * self); 82 | 83 | gpointer koto_button_get_data(KotoButton * self); 84 | 85 | void koto_button_global_page_nav_callback( 86 | GtkGestureClick * gesture, 87 | int n_press, 88 | double x, 89 | double y, 90 | gpointer user_data 91 | ); 92 | 93 | void koto_button_handle_mouse_enter( 94 | GtkEventControllerMotion * controller, 95 | double x, 96 | double y, 97 | gpointer user_data 98 | ); 99 | 100 | void koto_button_handle_mouse_leave( 101 | GtkEventControllerMotion * controller, 102 | gpointer user_data 103 | ); 104 | 105 | void koto_button_hide_image(KotoButton * self); 106 | 107 | void koto_button_set_data( 108 | KotoButton * self, 109 | gpointer data 110 | ); 111 | 112 | void koto_button_set_pseudoactive_styling(KotoButton * self); 113 | 114 | void koto_button_set_badge_text( 115 | KotoButton * self, 116 | gchar * text 117 | ); 118 | 119 | void koto_button_set_file_path( 120 | KotoButton * self, 121 | gchar * file_path 122 | ); 123 | 124 | void koto_button_set_icon_name( 125 | KotoButton * self, 126 | gchar * icon_name, 127 | gboolean for_alt 128 | ); 129 | 130 | void koto_button_set_image_position( 131 | KotoButton * self, 132 | KotoButtonImagePosition pos 133 | ); 134 | 135 | void koto_button_set_resource_path( 136 | KotoButton * self, 137 | gchar * resource_path 138 | ); 139 | 140 | void koto_button_set_pixbuf( 141 | KotoButton * self, 142 | GdkPixbuf * pix 143 | ); 144 | 145 | void koto_button_set_pixbuf_size( 146 | KotoButton * self, 147 | guint size 148 | ); 149 | 150 | void koto_button_set_text( 151 | KotoButton * self, 152 | gchar * text 153 | ); 154 | 155 | void koto_button_set_text_justify( 156 | KotoButton * self, 157 | GtkJustification j 158 | ); 159 | 160 | void koto_button_set_text_wrap( 161 | KotoButton * self, 162 | gboolean wrap 163 | ); 164 | 165 | void koto_button_show_image( 166 | KotoButton * self, 167 | gboolean use_alt 168 | ); 169 | 170 | void koto_button_unflatten(KotoButton * self); 171 | 172 | void koto_button_unset_pseudoactive_styling(KotoButton * self); 173 | 174 | G_END_DECLS 175 | -------------------------------------------------------------------------------- /src/playback/engine.h: -------------------------------------------------------------------------------- 1 | /* engine.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | #include 22 | #include "../config/config.h" 23 | #include "../playlist/current.h" 24 | 25 | G_BEGIN_DECLS 26 | 27 | /** 28 | * Type Definition 29 | **/ 30 | 31 | #define KOTO_TYPE_PLAYBACK_ENGINE (koto_playback_engine_get_type()) 32 | #define KOTO_PLAYBACK_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_PLAYBACK_ENGINE, KotoPlaybackEngine)) 33 | #define KOTO_IS_PLAYBACK_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYBACK_ENGINE)) 34 | 35 | typedef struct _KotoPlaybackEngine KotoPlaybackEngine; 36 | typedef struct _KotoPlaybackEngineClass KotoPlaybackEngineClass; 37 | 38 | GLIB_AVAILABLE_IN_ALL 39 | GType koto_playback_engine_get_type(void) G_GNUC_CONST; 40 | 41 | /** 42 | * Playback Engine Functions 43 | **/ 44 | 45 | KotoPlaybackEngine * koto_playback_engine_new(); 46 | 47 | void koto_playback_engine_apply_configuration_state( 48 | KotoConfig * config, 49 | guint prop_id, 50 | KotoPlaybackEngine * self 51 | ); 52 | 53 | void koto_playback_engine_backwards(KotoPlaybackEngine * self); 54 | 55 | void koto_playback_engine_handle_current_playlist_changed( 56 | KotoCurrentPlaylist * current_pl, 57 | KotoPlaylist * playlist, 58 | gboolean play_immediately, 59 | gboolean play_current, 60 | gpointer user_data 61 | ); 62 | 63 | void koto_playback_engine_forwards(KotoPlaybackEngine * self); 64 | 65 | KotoTrack * koto_playback_engine_get_current_track(KotoPlaybackEngine * self); 66 | 67 | gint64 koto_playback_engine_get_duration(KotoPlaybackEngine * self); 68 | 69 | gdouble koto_playback_engine_get_progress(KotoPlaybackEngine * self); 70 | 71 | ; 72 | 73 | gdouble koto_playback_engine_get_sane_playback_rate(gdouble rate); 74 | 75 | GstState koto_playback_engine_get_state(KotoPlaybackEngine * self); 76 | 77 | gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine * self); 78 | 79 | gboolean koto_playback_engine_get_track_shuffle(KotoPlaybackEngine * self); 80 | 81 | gdouble koto_playback_engine_get_volume(KotoPlaybackEngine * self); 82 | 83 | void koto_playback_engine_jump_backwards(KotoPlaybackEngine * self); 84 | 85 | void koto_playback_engine_jump_forwards(KotoPlaybackEngine * self); 86 | 87 | void koto_playback_engine_mute(KotoPlaybackEngine * self); 88 | 89 | gboolean koto_playback_engine_monitor_changed( 90 | GstBus * bus, 91 | GstMessage * msg, 92 | gpointer user_data 93 | ); 94 | 95 | void koto_playback_engine_pause(KotoPlaybackEngine * self); 96 | 97 | void koto_playback_engine_play(KotoPlaybackEngine * self); 98 | 99 | void koto_playback_engine_toggle(KotoPlaybackEngine * self); 100 | 101 | void koto_playback_engine_set_playback_rate( 102 | KotoPlaybackEngine * self, 103 | gdouble rate 104 | ); 105 | 106 | void koto_playback_engine_set_position( 107 | KotoPlaybackEngine * self, 108 | int position 109 | ); 110 | 111 | void koto_playback_engine_set_track_playback_position( 112 | KotoPlaybackEngine * self, 113 | guint64 pos 114 | ); 115 | 116 | void koto_playback_engine_set_track_repeat( 117 | KotoPlaybackEngine * self, 118 | gboolean enable_repeat 119 | ); 120 | 121 | void koto_playback_engine_set_track_shuffle( 122 | KotoPlaybackEngine * self, 123 | gboolean enable_shuffle 124 | ); 125 | 126 | void koto_playback_engine_set_track_by_uuid( 127 | KotoPlaybackEngine * self, 128 | gchar * track_uuid, 129 | gboolean playing_specific_track 130 | ); 131 | 132 | void koto_playback_engine_set_volume( 133 | KotoPlaybackEngine * self, 134 | gdouble volume 135 | ); 136 | 137 | void koto_playback_engine_stop(KotoPlaybackEngine * self); 138 | 139 | void koto_playback_engine_toggle_track_repeat(KotoPlaybackEngine * self); 140 | 141 | void koto_playback_engine_toggle_track_shuffle(KotoPlaybackEngine * self); 142 | 143 | void koto_playback_engine_update_duration(KotoPlaybackEngine * self); 144 | 145 | void koto_playback_engine_update_track_position(KotoPlaybackEngine * self); 146 | 147 | gboolean koto_playback_engine_tick_duration(gpointer user_data); 148 | 149 | gboolean koto_playback_engine_tick_track(gpointer user_data); 150 | 151 | gboolean koto_playback_engine_tick_update_playlist_state(gpointer user_data); 152 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* main.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "config/config.h" 22 | #include "db/cartographer.h" 23 | #include "db/db.h" 24 | #include "db/loaders.h" 25 | #include "indexer/track-helpers.h" 26 | #include "playback/engine.h" 27 | #include "playback/media-keys.h" 28 | #include "playback/mimes.h" 29 | #include "playback/mpris.h" 30 | #include "paths.h" 31 | #include "playlist/current.h" 32 | 33 | #include "config/config.h" 34 | #include "koto-paths.h" 35 | #include "koto-window.h" 36 | 37 | extern KotoConfig * config; 38 | 39 | extern guint mpris_bus_id; 40 | extern GDBusNodeInfo * introspection_data; 41 | 42 | extern KotoPlaybackEngine * playback_engine; 43 | extern KotoCartographer * koto_maps; 44 | extern KotoCurrentPlaylist * current_playlist; 45 | extern sqlite3 * koto_db; 46 | 47 | extern GHashTable * supported_mimes_hash; 48 | extern GList * supported_mimes; 49 | 50 | extern gchar * koto_path_to_conf; 51 | extern gchar * koto_rev_dns; 52 | 53 | extern gboolean created_new_db; 54 | 55 | GVolumeMonitor * volume_monitor = NULL; 56 | GtkApplication * app = NULL; 57 | GtkWindow * main_window; 58 | magic_t magic_cookie; 59 | 60 | static void on_activate (GtkApplication * app) { 61 | g_assert(GTK_IS_APPLICATION(app)); 62 | 63 | main_window = gtk_application_get_active_window(app); 64 | if (main_window == NULL) { 65 | main_window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL); 66 | setup_mpris_interfaces(); // Set up our MPRIS interfaces 67 | setup_mediakeys_interface(); // Set up our media key support 68 | 69 | if (!created_new_db) { 70 | read_from_db(); // Read the database, allowing us to propagate the UI with various data such as artists and playlist navigation elements 71 | } 72 | } 73 | 74 | gtk_window_present(main_window); 75 | } 76 | 77 | static void on_shutdown(GtkApplication * app) { 78 | (void) app; 79 | koto_current_playlist_save_playlist_state(current_playlist); // Save the current playlist state if necessary before closure 80 | koto_config_save(config); // Save our config 81 | close_db(); // Close the database 82 | g_bus_unown_name(mpris_bus_id); 83 | g_dbus_node_info_unref(introspection_data); 84 | } 85 | 86 | int main ( 87 | int argc, 88 | char * argv[] 89 | ) { 90 | int ret; 91 | 92 | gtk_init(); 93 | gst_init(&argc, &argv); 94 | 95 | koto_track_helpers_init(); // Init our track helpers (primarily our genre replacement hashtable) 96 | koto_paths_setup(); // Set up our required paths 97 | 98 | supported_mimes_hash = g_hash_table_new(g_str_hash, g_str_equal); 99 | supported_mimes = NULL; // Ensure our mimes GList is initialized 100 | koto_playback_engine_get_supported_mimetypes(supported_mimes); 101 | 102 | koto_maps = koto_cartographer_new(); // Create our new cartographer and their collection of maps 103 | 104 | volume_monitor = g_volume_monitor_get(); // Get a VolumeMonitor 105 | 106 | config = koto_config_new(); // Set our config 107 | koto_config_load(config, koto_path_to_conf); 108 | playback_engine = koto_playback_engine_new(); // Initialize the engine now that the config is available, since it listens on various config signals 109 | 110 | taglib_id3v2_set_default_text_encoding(TagLib_ID3v2_UTF8); // Ensure our id3v2 text encoding is UTF-8 111 | magic_cookie = magic_open(MAGIC_MIME); 112 | 113 | if (magic_cookie == NULL) { // Failed to open 114 | g_critical("Failed to allocate a cookie pointer from libmagic."); 115 | } 116 | 117 | if (magic_load(magic_cookie, NULL) != 0) { // Failed to load data 118 | magic_close(magic_cookie); 119 | g_critical("Failed to load the system magic database."); 120 | } 121 | 122 | open_db(); // Open our database 123 | g_thread_new("indexing-any-necessary-libs", (void*) koto_config_load_libs, config); // Load our libraries, now that our database is set up. Note that read_from_db is called in koto-window.c 124 | 125 | app = gtk_application_new(koto_rev_dns, G_APPLICATION_FLAGS_NONE); 126 | g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL); 127 | g_signal_connect(app, "shutdown", G_CALLBACK(on_shutdown), NULL); 128 | ret = g_application_run(G_APPLICATION(app), argc, argv); 129 | 130 | return ret; 131 | } 132 | -------------------------------------------------------------------------------- /src/db/db.c: -------------------------------------------------------------------------------- 1 | /* db.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "db.h" 24 | #include "../koto-paths.h" 25 | 26 | extern gchar * koto_path_to_db; 27 | 28 | int KOTO_DB_SUCCESS = 0; 29 | int KOTO_DB_NEW = 1; 30 | int KOTO_DB_FAIL = 2; 31 | 32 | sqlite3 * koto_db = NULL; 33 | gchar * db_filepath = NULL; 34 | gboolean created_new_db = FALSE; 35 | 36 | void close_db() { 37 | sqlite3_close(koto_db); 38 | } 39 | 40 | int create_db_tables() { 41 | gchar * tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, name string, art_path string);" 42 | "CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, artist_id string, name string, description string, narrator string, art_path string, genres string, year int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" 43 | "CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, artist_id string, album_id string, name string, disc int, position int, duration int, genres string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" 44 | "CREATE TABLE IF NOT EXISTS libraries_albums(id string, album_id string, path string, PRIMARY KEY (id, album_id) FOREIGN KEY(album_id) REFERENCES albums(id) ON DELETE CASCADE);" 45 | "CREATE TABLE IF NOT EXISTS libraries_artists(id string, artist_id string, path string, PRIMARY KEY(id, artist_id) FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" 46 | "CREATE TABLE IF NOT EXISTS libraries_tracks(id string, track_id string, path string, PRIMARY KEY(id, track_id) FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);" 47 | "CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int, album_id string, track_id string, playback_position_of_track int);" 48 | "CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);"; 49 | 50 | return (new_transaction(tables_creation_queries, "Failed to create required tables", TRUE) == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL; 51 | } 52 | 53 | int enable_foreign_keys() { 54 | gchar * commit_op = g_strdup("PRAGMA foreign_keys = ON;"); 55 | const gchar * transaction_err_msg = "Failed to enable foreign key support. Ensure your sqlite3 is compiled with neither SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER defined"; 56 | 57 | return (new_transaction(commit_op, transaction_err_msg, FALSE) == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL; 58 | } 59 | 60 | int have_existing_db() { 61 | struct stat db_stat; 62 | int success = stat(koto_path_to_db, &db_stat); 63 | return ((success == 0) && S_ISREG(db_stat.st_mode)) ? 0 : 1; 64 | } 65 | 66 | int new_transaction( 67 | gchar * operation, 68 | const gchar * transaction_err_msg, 69 | gboolean fatal 70 | ) { 71 | gchar * commit_op_errmsg = NULL; 72 | int rc = sqlite3_exec(koto_db, operation, 0, 0, &commit_op_errmsg); 73 | 74 | if (rc != SQLITE_OK) { 75 | (fatal) ? g_critical("%s: %s", transaction_err_msg, commit_op_errmsg) : g_warning("%s: %s", transaction_err_msg, commit_op_errmsg); 76 | } 77 | 78 | if (commit_op_errmsg == NULL) { 79 | g_free(commit_op_errmsg); 80 | } 81 | 82 | return rc; 83 | } 84 | 85 | int open_db() { 86 | int ret = KOTO_DB_SUCCESS; // Default to last return being SUCCESS 87 | 88 | if (have_existing_db() == 1) { // If we do not have an existing DB 89 | ret = KOTO_DB_NEW; 90 | } 91 | 92 | if (sqlite3_open(koto_path_to_db, &koto_db) != KOTO_DB_SUCCESS) { // If we failed to open the database file 93 | g_critical("Failed to open or create database: %s", sqlite3_errmsg(koto_db)); 94 | return KOTO_DB_FAIL; 95 | } 96 | 97 | if (enable_foreign_keys() != KOTO_DB_SUCCESS) { // If we failed to enable foreign keys 98 | return KOTO_DB_FAIL; 99 | } 100 | 101 | if (create_db_tables() != KOTO_DB_SUCCESS) { // Failed to create our database tables 102 | return KOTO_DB_FAIL; 103 | } 104 | 105 | if (ret == KOTO_DB_NEW) { 106 | created_new_db = TRUE; 107 | } 108 | 109 | return ret; 110 | } 111 | -------------------------------------------------------------------------------- /src/playlist/playlist.h: -------------------------------------------------------------------------------- 1 | /* playlist.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | #include "../indexer/structs.h" 22 | 23 | G_BEGIN_DECLS 24 | 25 | typedef enum { 26 | KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT, // Considered to be newest first 27 | KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST, 28 | KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM, 29 | KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST, 30 | KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME, 31 | KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS 32 | } KotoPreferredPlaylistSortType; 33 | 34 | /** 35 | * Type Definition 36 | **/ 37 | 38 | #define KOTO_TYPE_PLAYLIST koto_playlist_get_type() 39 | #define KOTO_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_PLAYLIST, KotoPlaylist)) 40 | #define KOTO_IS_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYLIST)) 41 | 42 | typedef struct _KotoPlaylist KotoPlaylist; 43 | typedef struct _KotoPlaylistClass KotoPlaylistClass; 44 | 45 | GLIB_AVAILABLE_IN_ALL 46 | GType koto_playlist_get_type(void) G_GNUC_CONST; 47 | 48 | /** 49 | * Playlist Functions 50 | **/ 51 | 52 | KotoPlaylist * koto_playlist_new(); 53 | 54 | KotoPlaylist * koto_playlist_new_with_uuid(const gchar * uuid); 55 | 56 | void koto_playlist_add_to_played_tracks( 57 | KotoPlaylist * self, 58 | gchar * uuid 59 | ); 60 | 61 | void koto_playlist_add_track( 62 | KotoPlaylist * self, 63 | KotoTrack * track, 64 | gboolean current, 65 | gboolean commit_to_table 66 | ); 67 | 68 | void koto_playlist_apply_model( 69 | KotoPlaylist * self, 70 | KotoPreferredPlaylistSortType preferred_model 71 | ); 72 | 73 | void koto_playlist_commit(KotoPlaylist * self); 74 | 75 | void koto_playlist_emit_modified(KotoPlaylist * self); 76 | 77 | gchar * koto_playlist_get_artwork(KotoPlaylist * self); 78 | 79 | KotoPreferredPlaylistSortType koto_playlist_get_current_model(KotoPlaylist * self); 80 | 81 | gint koto_playlist_get_current_position(KotoPlaylist * self); 82 | 83 | KotoTrack * koto_playlist_get_current_track(KotoPlaylist * self); 84 | 85 | guint koto_playlist_get_length(KotoPlaylist * self); 86 | 87 | gboolean koto_playlist_get_is_finalized(KotoPlaylist * self); 88 | 89 | gboolean koto_playlist_get_is_hidden(KotoPlaylist * self); 90 | 91 | gchar * koto_playlist_get_name(KotoPlaylist * self); 92 | 93 | gint koto_playlist_get_position_of_track( 94 | KotoPlaylist * self, 95 | KotoTrack * track 96 | ); 97 | 98 | GListStore * koto_playlist_get_store(KotoPlaylist * self); 99 | 100 | GQueue * koto_playlist_get_tracks(KotoPlaylist * self); 101 | 102 | gchar * koto_playlist_get_uuid(KotoPlaylist * self); 103 | 104 | gchar * koto_playlist_go_to_next(KotoPlaylist * self); 105 | 106 | gchar * koto_playlist_go_to_previous(KotoPlaylist * self); 107 | 108 | void koto_playlist_mark_as_finalized(KotoPlaylist * self); 109 | 110 | gint koto_playlist_model_sort_by_uuid( 111 | gconstpointer first_item, 112 | gconstpointer second_item, 113 | gpointer data_list 114 | ); 115 | 116 | gint koto_playlist_model_sort_by_track( 117 | gconstpointer first_item, 118 | gconstpointer second_item, 119 | gpointer model_ptr 120 | ); 121 | 122 | void koto_playlist_remove_from_played_tracks( 123 | KotoPlaylist * self, 124 | gchar * uuid 125 | ); 126 | 127 | void koto_playlist_remove_track_by_uuid( 128 | KotoPlaylist * self, 129 | gchar * uuid 130 | ); 131 | 132 | void koto_playlist_set_album_uuid( 133 | KotoPlaylist * self, 134 | const gchar * album_uuid 135 | ); 136 | 137 | void koto_playlist_set_artwork( 138 | KotoPlaylist * self, 139 | const gchar * path 140 | ); 141 | 142 | void koto_playlist_save_current_playback_state(KotoPlaylist * self); 143 | 144 | void koto_playlist_set_name( 145 | KotoPlaylist * self, 146 | const gchar * name 147 | ); 148 | 149 | void koto_playlist_set_position( 150 | KotoPlaylist * self, 151 | gint position 152 | ); 153 | 154 | void koto_playlist_set_track_as_current( 155 | KotoPlaylist * self, 156 | gchar * track_uuid 157 | ); 158 | 159 | void koto_playlist_set_uuid( 160 | KotoPlaylist * self, 161 | const gchar * uuid 162 | ); 163 | 164 | void koto_playlist_tracks_queue_push_to_store( 165 | gpointer data, 166 | gpointer user_data 167 | ); 168 | 169 | void koto_playlist_unmap(KotoPlaylist * self); 170 | 171 | G_END_DECLS 172 | -------------------------------------------------------------------------------- /src/koto-playerbar.h: -------------------------------------------------------------------------------- 1 | /* koto-playerbar.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include 21 | #include "playback/engine.h" 22 | 23 | G_BEGIN_DECLS 24 | 25 | #define KOTO_TYPE_PLAYERBAR (koto_playerbar_get_type()) 26 | G_DECLARE_FINAL_TYPE(KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GObject) 27 | #define KOTO_IS_PLAYERBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYERBAR)) 28 | 29 | KotoPlayerBar * koto_playerbar_new(void); 30 | GtkWidget * koto_playerbar_get_main(KotoPlayerBar * self); 31 | 32 | void koto_playerbar_apply_configuration_state( 33 | KotoConfig * config, 34 | guint prop_id, 35 | KotoPlayerBar * self 36 | ); 37 | 38 | void koto_playerbar_create_playback_details(KotoPlayerBar * self); 39 | 40 | void koto_playerbar_create_primary_controls(KotoPlayerBar * self); 41 | 42 | void koto_playerbar_create_secondary_controls(KotoPlayerBar * self); 43 | 44 | void koto_playerbar_go_backwards( 45 | GtkGestureClick * gesture, 46 | int n_press, 47 | double x, 48 | double y, 49 | gpointer data 50 | ); 51 | 52 | void koto_playerbar_go_forwards( 53 | GtkGestureClick * gesture, 54 | int n_press, 55 | double x, 56 | double y, 57 | gpointer data 58 | ); 59 | 60 | void koto_playerbar_handle_is_playing( 61 | KotoPlaybackEngine * engine, 62 | gpointer user_data 63 | ); 64 | 65 | void koto_playerbar_handle_is_paused( 66 | KotoPlaybackEngine * engine, 67 | gpointer user_data 68 | ); 69 | 70 | void koto_playerbar_handle_playlist_button_clicked( 71 | GtkGestureClick * gesture, 72 | int n_press, 73 | double x, 74 | double y, 75 | gpointer data 76 | ); 77 | 78 | void koto_playerbar_handle_progressbar_scroll_begin( 79 | GtkEventControllerScroll * controller, 80 | gpointer data 81 | ); 82 | 83 | void koto_playerbar_handle_progressbar_gesture_begin( 84 | GtkGesture * gesture, 85 | GdkEventSequence * seq, 86 | gpointer data 87 | ); 88 | 89 | void koto_playerbar_handle_progressbar_gesture_end( 90 | GtkGesture * gesture, 91 | GdkEventSequence * seq, 92 | gpointer data 93 | ); 94 | 95 | void koto_playerbar_handle_progressbar_pressed( 96 | GtkGestureClick * gesture, 97 | int n_press, 98 | double x, 99 | double y, 100 | gpointer data 101 | ); 102 | 103 | void koto_playerbar_handle_progressbar_value_changed( 104 | GtkRange * progress_bar, 105 | gpointer data 106 | ); 107 | 108 | void koto_playerbar_handle_speed_input_activate( 109 | GtkEntry * playback_speed_input, 110 | gpointer user_data 111 | ); 112 | 113 | void koto_playerbar_handle_tick_duration( 114 | KotoPlaybackEngine * engine, 115 | gpointer user_data 116 | ); 117 | 118 | void koto_playerbar_handle_tick_track( 119 | KotoPlaybackEngine * engine, 120 | gpointer user_data 121 | ); 122 | 123 | void koto_playerbar_handle_track_repeat( 124 | KotoPlaybackEngine * engine, 125 | gpointer user_data 126 | ); 127 | 128 | void koto_playerbar_handle_track_shuffle( 129 | KotoPlaybackEngine * engine, 130 | gpointer user_data 131 | ); 132 | 133 | void koto_playerbar_handle_volume_button_change( 134 | GtkScaleButton * button, 135 | double value, 136 | gpointer user_data 137 | ); 138 | 139 | void koto_playerbar_jump_backwards( 140 | GtkGestureClick * gesture, 141 | int n_press, 142 | double x, 143 | double y, 144 | gpointer data 145 | ); 146 | 147 | void koto_playerbar_jump_forwards( 148 | GtkGestureClick * gesture, 149 | int n_press, 150 | double x, 151 | double y, 152 | gpointer data 153 | ); 154 | 155 | void koto_playerbar_reset_progressbar(KotoPlayerBar * self); 156 | 157 | void koto_playerbar_set_progressbar_duration( 158 | KotoPlayerBar * self, 159 | gint64 duration 160 | ); 161 | 162 | void koto_playerbar_set_progressbar_value( 163 | KotoPlayerBar * self, 164 | gdouble progress 165 | ); 166 | 167 | void koto_playerbar_toggle_play_pause( 168 | GtkGestureClick * gesture, 169 | int n_press, 170 | double x, 171 | double y, 172 | gpointer data 173 | ); 174 | 175 | void koto_playerbar_toggle_playlist_shuffle( 176 | GtkGestureClick * gesture, 177 | int n_press, 178 | double x, 179 | double y, 180 | gpointer data 181 | ); 182 | 183 | void koto_playerbar_toggle_track_repeat( 184 | GtkGestureClick * gesture, 185 | int n_press, 186 | double x, 187 | double y, 188 | gpointer data 189 | ); 190 | 191 | void koto_playerbar_update_track_info( 192 | KotoPlaybackEngine * engine, 193 | gpointer user_data 194 | ); 195 | 196 | G_END_DECLS 197 | -------------------------------------------------------------------------------- /src/db/cartographer.h: -------------------------------------------------------------------------------- 1 | /* cartographer.h 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | #include 20 | #include "../indexer/structs.h" 21 | #include "../playlist/playlist.h" 22 | 23 | G_BEGIN_DECLS 24 | 25 | /** 26 | * Type Definition 27 | **/ 28 | 29 | #define KOTO_TYPE_CARTOGRAPHER koto_cartographer_get_type() 30 | 31 | typedef struct _KotoCartographer KotoCartographer; 32 | typedef struct _KotoCartographerClass KotoCartographerClass; 33 | 34 | GLIB_AVAILABLE_IN_ALL 35 | GType koto_cartographer_get_type(void) G_GNUC_CONST; 36 | 37 | #define KOTO_IS_CARTOGRAPHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_CARTOGRAPHER)) 38 | 39 | /** 40 | * Cartographer Functions 41 | **/ 42 | 43 | KotoCartographer * koto_cartographer_new(); 44 | 45 | void koto_cartographer_add_album( 46 | KotoCartographer * self, 47 | KotoAlbum * album 48 | ); 49 | 50 | void koto_cartographer_add_artist( 51 | KotoCartographer * self, 52 | KotoArtist * artist 53 | ); 54 | 55 | void koto_cartographer_add_library( 56 | KotoCartographer * self, 57 | KotoLibrary * library 58 | ); 59 | 60 | void koto_cartographer_add_playlist( 61 | KotoCartographer * self, 62 | KotoPlaylist * playlist 63 | ); 64 | 65 | void koto_cartographer_add_track( 66 | KotoCartographer * self, 67 | KotoTrack * track 68 | ); 69 | 70 | void koto_cartographer_emit_playlist_added( 71 | KotoPlaylist * playlist, 72 | KotoCartographer * self 73 | ); 74 | 75 | KotoAlbum * koto_cartographer_get_album_by_uuid( 76 | KotoCartographer * self, 77 | gchar * album_uuid 78 | ); 79 | 80 | GHashTable * koto_cartographer_get_artists(KotoCartographer * self); 81 | 82 | KotoArtist * koto_cartographer_get_artist_by_name( 83 | KotoCartographer * self, 84 | gchar * artist_name 85 | ); 86 | 87 | KotoArtist * koto_cartographer_get_artist_by_uuid( 88 | KotoCartographer * self, 89 | gchar * artist_uuid 90 | ); 91 | 92 | KotoLibrary * koto_cartographer_get_library_by_uuid( 93 | KotoCartographer * self, 94 | gchar * library_uuid 95 | ); 96 | 97 | KotoLibrary * koto_cartographer_get_library_containing_path( 98 | KotoCartographer * self, 99 | gchar * path 100 | ); 101 | 102 | GList * koto_cartographer_get_libraries(KotoCartographer * self); 103 | 104 | GList * koto_cartographer_get_libraries_for_storage_uuid( 105 | KotoCartographer * self, 106 | gchar * storage_uuid 107 | ); 108 | 109 | KotoPlaylist * koto_cartographer_get_playlist_by_uuid( 110 | KotoCartographer * self, 111 | gchar * playlist_uuid 112 | ); 113 | 114 | GHashTable * koto_cartographer_get_playlists(KotoCartographer * self); 115 | 116 | KotoTrack * koto_cartographer_get_track_by_uuid( 117 | KotoCartographer * self, 118 | gchar * track_uuid 119 | ); 120 | 121 | KotoTrack * koto_cartographer_get_track_by_uniqueish_key( 122 | KotoCartographer * self, 123 | gchar * key 124 | ); 125 | 126 | gboolean koto_cartographer_has_album( 127 | KotoCartographer * self, 128 | KotoAlbum * album 129 | ); 130 | 131 | gboolean koto_cartographer_has_album_by_uuid( 132 | KotoCartographer * self, 133 | gchar * album_uuid 134 | ); 135 | 136 | gboolean koto_cartographer_has_artist( 137 | KotoCartographer * self, 138 | KotoArtist * artist 139 | ); 140 | 141 | gboolean koto_cartographer_has_artist_by_uuid( 142 | KotoCartographer * self, 143 | gchar * artist_uuid 144 | ); 145 | 146 | gboolean koto_cartographer_has_library( 147 | KotoCartographer * self, 148 | KotoLibrary * library 149 | ); 150 | 151 | gboolean koto_cartographer_has_library_by_uuid( 152 | KotoCartographer * self, 153 | gchar * library_uuid 154 | ); 155 | 156 | gboolean koto_cartographer_has_playlist( 157 | KotoCartographer * self, 158 | KotoPlaylist * playlist 159 | ); 160 | 161 | gboolean koto_cartographer_has_playlist_by_uuid( 162 | KotoCartographer * self, 163 | gchar * playlist_uuid 164 | ); 165 | 166 | gboolean koto_cartographer_has_track( 167 | KotoCartographer * self, 168 | KotoTrack * track 169 | ); 170 | 171 | gboolean koto_cartographer_has_track_by_uuid( 172 | KotoCartographer * self, 173 | gchar * track_uuid 174 | ); 175 | 176 | void koto_cartographer_remove_album( 177 | KotoCartographer * self, 178 | KotoAlbum * album 179 | ); 180 | 181 | void koto_cartographer_remove_album_by_uuid( 182 | KotoCartographer * self, 183 | gchar * album_uuid 184 | ); 185 | 186 | void koto_cartographer_remove_artist( 187 | KotoCartographer * self, 188 | KotoArtist * artist 189 | ); 190 | 191 | void koto_cartographer_remove_artist_by_uuid( 192 | KotoCartographer * self, 193 | gchar * artist_uuid 194 | ); 195 | 196 | void koto_cartographer_remove_library( 197 | KotoCartographer * self, 198 | KotoLibrary * library 199 | ); 200 | 201 | void koto_cartographer_remove_playlist( 202 | KotoCartographer * self, 203 | KotoPlaylist * playlist 204 | ); 205 | 206 | void koto_cartographer_remove_playlist_by_uuid( 207 | KotoCartographer * self, 208 | gchar * playlist_uuid 209 | ); 210 | 211 | void koto_cartographer_remove_track( 212 | KotoCartographer * self, 213 | KotoTrack * track 214 | ); 215 | 216 | void koto_cartographer_remove_track_by_uuid( 217 | KotoCartographer * self, 218 | gchar * track_uuid 219 | ); 220 | 221 | G_END_DECLS 222 | -------------------------------------------------------------------------------- /src/playback/media-keys.c: -------------------------------------------------------------------------------- 1 | /* media-keys.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include "engine.h" 22 | #include "media-keys.h" 23 | #include "../koto-paths.h" 24 | 25 | extern GtkWindow * main_window; 26 | extern KotoPlaybackEngine * playback_engine; 27 | extern gchar * koto_rev_dns; 28 | 29 | GDBusConnection * media_keys_dbus_conn = NULL; 30 | GDBusProxy * media_keys_proxy = NULL; 31 | GDBusNodeInfo * media_keys_introspection_data = NULL; 32 | 33 | static const gchar introspection_xml[] = 34 | "" 35 | " " 36 | " " 37 | " " 38 | " " 39 | " " 40 | " " 41 | " " 42 | " " 43 | " " 44 | " " 45 | " " 46 | " " 47 | " " 48 | " " 49 | ""; 50 | 51 | void grab_media_keys() { 52 | if (media_keys_proxy == NULL) { // No connection 53 | return; 54 | } 55 | 56 | g_dbus_proxy_call( 57 | media_keys_proxy, 58 | "GrabMediaPlayerKeys", 59 | g_variant_new("(su)", koto_rev_dns, 0), 60 | G_DBUS_CALL_FLAGS_NONE, 61 | -1, 62 | NULL, 63 | handle_media_keys_async_done, 64 | NULL 65 | ); 66 | } 67 | 68 | void handle_media_keys_async_done( 69 | GObject * source_object, 70 | GAsyncResult * res, 71 | gpointer user_data 72 | ) { 73 | (void) user_data; 74 | g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), res, NULL); // Ensure we finish our call 75 | } 76 | 77 | void handle_media_keys_signal( 78 | GDBusProxy * proxy, 79 | const gchar * sender_name, 80 | const gchar * signal_name, 81 | GVariant * parameters, 82 | gpointer user_data 83 | ) { 84 | (void) proxy; 85 | (void) sender_name; 86 | (void) user_data; 87 | if (g_strcmp0(signal_name, "MediaPlayerKeyPressed") != 0) { // Not MediaPlayerKeyPressed 88 | return; 89 | } 90 | 91 | gchar * application_name = NULL; 92 | gchar * key = NULL; 93 | 94 | g_variant_get(parameters, "(ss)", &application_name, &key); 95 | 96 | if (g_strcmp0(application_name, koto_rev_dns) != 0) { // Not for Koto 97 | return; 98 | } 99 | 100 | if (g_strcmp0(key, "Play") == 0) { 101 | koto_playback_engine_play(playback_engine); 102 | } else if (g_strcmp0(key, "Pause") == 0) { 103 | koto_playback_engine_pause(playback_engine); 104 | } else if (g_strcmp0(key, "Stop") == 0) { 105 | koto_playback_engine_stop(playback_engine); 106 | } else if (g_strcmp0(key, "Previous") == 0) { 107 | koto_playback_engine_backwards(playback_engine); 108 | } else if (g_strcmp0(key, "Next") == 0) { 109 | koto_playback_engine_forwards(playback_engine); 110 | } else if (g_strcmp0(key, "Repeat") == 0) { 111 | koto_playback_engine_toggle_track_repeat(playback_engine); 112 | } else if (g_strcmp0(key, "Shuffle") == 0) { 113 | koto_playback_engine_toggle_track_shuffle(playback_engine); 114 | } 115 | } 116 | 117 | void handle_window_enter( 118 | GtkEventControllerFocus * controller, 119 | gpointer user_data 120 | ) { 121 | (void) controller; 122 | (void) user_data; 123 | grab_media_keys(); // Grab our media keys 124 | } 125 | 126 | void handle_window_leave( 127 | GtkEventControllerFocus * controller, 128 | gpointer user_data 129 | ) { 130 | (void) controller; 131 | (void) user_data; 132 | release_media_keys(); // Release our media keys 133 | } 134 | 135 | void release_media_keys() { 136 | if (media_keys_dbus_conn == NULL) { // No connection 137 | return; 138 | } 139 | 140 | GVariant * params = g_variant_new_string(g_strdup(koto_rev_dns)); 141 | 142 | g_dbus_proxy_call( 143 | media_keys_proxy, 144 | "ReleaseMediaPlayerKeys", 145 | params, 146 | G_DBUS_CALL_FLAGS_NONE, 147 | -1, 148 | NULL, 149 | handle_media_keys_async_done, 150 | NULL 151 | ); 152 | } 153 | 154 | void setup_mediakeys_interface() { 155 | media_keys_introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL); 156 | g_assert(media_keys_introspection_data != NULL); 157 | 158 | GDBusConnection * bus; 159 | GError * error = NULL; 160 | 161 | bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); 162 | 163 | if (bus == NULL) { // Failed to get session bus 164 | g_printerr("Failed to get our session bus: %s\n", error->message); 165 | g_error_free(error); 166 | return; 167 | } 168 | 169 | media_keys_proxy = g_dbus_proxy_new_sync( 170 | bus, 171 | G_DBUS_PROXY_FLAGS_NONE, 172 | NULL, 173 | "org.gnome.SettingsDaemon.MediaKeys", 174 | "/org/gnome/SettingsDaemon/MediaKeys", 175 | "org.gnome.SettingsDaemon.MediaKeys", 176 | NULL, 177 | &error 178 | ); 179 | 180 | if (media_keys_proxy == NULL) { 181 | g_printerr("Failed to get a proxy to GNOME Settings Daemon Media-Keys: %s\n", error->message); 182 | g_error_free(error); 183 | return; 184 | } 185 | 186 | g_signal_connect_object( 187 | media_keys_proxy, 188 | "g-signal", 189 | G_CALLBACK(handle_media_keys_signal), 190 | NULL, 191 | 0 192 | ); 193 | 194 | GtkEventController * focus_controller = gtk_event_controller_focus_new(); // Create a new focus controller 195 | 196 | g_signal_connect(focus_controller, "enter", G_CALLBACK(handle_window_enter), NULL); 197 | g_signal_connect(focus_controller, "leave", G_CALLBACK(handle_window_leave), NULL); 198 | gtk_widget_add_controller(GTK_WIDGET(main_window), focus_controller); 199 | } 200 | -------------------------------------------------------------------------------- /data/vectors/business-and-personal-finance.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 32 | 34 | 41 | 45 | 49 | 50 | 52 | 62 | 63 | 64 | 68 | 71 | 82 | 86 | 95 | 99 | 110 | 117 | 118 | 123 | 132 | 141 | 150 | 159 | 168 | 169 | 170 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /src/pages/audiobooks/writer-page.c: -------------------------------------------------------------------------------- 1 | /* writers-page.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "../../db/cartographer.h" 21 | #include "../../indexer/structs.h" 22 | #include "../../koto-utils.h" 23 | #include "../../koto-window.h" 24 | #include "audiobook-view.h" 25 | #include "writer-page.h" 26 | 27 | extern KotoCartographer * koto_maps; 28 | extern KotoWindow * main_window; 29 | 30 | enum { 31 | PROP_0, 32 | PROP_ARTIST, 33 | N_PROPERTIES 34 | }; 35 | 36 | static GParamSpec * props[N_PROPERTIES] = { 37 | NULL, 38 | }; 39 | 40 | struct _KotoWriterPage { 41 | GObject parent_instance; 42 | 43 | KotoArtist * artist; 44 | 45 | GtkWidget * main; // Our main content will be a scrolled Scrolled Window 46 | GtkWidget * content; // Content inside scrolled window 47 | 48 | GtkWidget * writers_header; // Header GtkLabel for the writer 49 | GListModel * model; 50 | 51 | GtkWidget * audiobooks_flow; 52 | }; 53 | 54 | struct _KotoWriterPageClass { 55 | GObjectClass parent_class; 56 | }; 57 | 58 | G_DEFINE_TYPE(KotoWriterPage, koto_writer_page, G_TYPE_OBJECT); 59 | 60 | static void koto_writer_page_get_property( 61 | GObject * obj, 62 | guint prop_id, 63 | GValue * val, 64 | GParamSpec * spec 65 | ); 66 | 67 | static void koto_writer_page_set_property( 68 | GObject * obj, 69 | guint prop_id, 70 | const GValue * val, 71 | GParamSpec * spec 72 | ); 73 | 74 | static void koto_writer_page_class_init(KotoWriterPageClass * c) { 75 | GObjectClass * gobject_class; 76 | 77 | gobject_class = G_OBJECT_CLASS(c); 78 | gobject_class->set_property = koto_writer_page_set_property; 79 | gobject_class->get_property = koto_writer_page_get_property; 80 | 81 | props[PROP_ARTIST] = g_param_spec_object( 82 | "artist", 83 | "Artist", 84 | "Artist", 85 | KOTO_TYPE_ARTIST, 86 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE 87 | ); 88 | 89 | g_object_class_install_properties(gobject_class, N_PROPERTIES, props); 90 | } 91 | 92 | static void koto_writer_page_init(KotoWriterPage * self) { 93 | self->main = gtk_scrolled_window_new(); // Set main to be a scrolled window 94 | self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 95 | gtk_widget_add_css_class(self->content, "writer-page"); 96 | 97 | gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->main), self->content); // Set content to be the child of the scrolled window 98 | 99 | self->writers_header = gtk_label_new(NULL); // Create an empty label 100 | gtk_widget_add_css_class(self->writers_header, "writer-header"); 101 | gtk_widget_set_halign(self->writers_header, GTK_ALIGN_START); 102 | 103 | self->audiobooks_flow = gtk_flow_box_new(); // Create our flowbox of the audiobooks views 104 | gtk_widget_add_css_class(self->audiobooks_flow, "audiobooks-flow"); 105 | 106 | gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(self->audiobooks_flow), 2); // Allow 2 to ensure adequate spacing for description 107 | gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->audiobooks_flow), GTK_SELECTION_NONE); // Do not allow selection 108 | 109 | gtk_widget_set_hexpand(self->audiobooks_flow, TRUE); // Expand horizontally 110 | gtk_widget_set_vexpand(self->audiobooks_flow, TRUE); // Expand vertically 111 | 112 | gtk_box_append(GTK_BOX(self->content), self->writers_header); 113 | gtk_box_append(GTK_BOX(self->content), self->audiobooks_flow); // Add the audiobooks flow to the content 114 | } 115 | 116 | static void koto_writer_page_get_property( 117 | GObject * obj, 118 | guint prop_id, 119 | GValue * val, 120 | GParamSpec * spec 121 | ) { 122 | KotoWriterPage * self = KOTO_WRITER_PAGE(obj); 123 | 124 | switch (prop_id) { 125 | case PROP_ARTIST: 126 | g_value_set_object(val, self->artist); 127 | break; 128 | default: 129 | G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); 130 | break; 131 | } 132 | } 133 | 134 | static void koto_writer_page_set_property( 135 | GObject * obj, 136 | guint prop_id, 137 | const GValue * val, 138 | GParamSpec * spec 139 | ) { 140 | KotoWriterPage * self = KOTO_WRITER_PAGE(obj); 141 | 142 | switch (prop_id) { 143 | case PROP_ARTIST: 144 | koto_writer_page_set_artist(self, (KotoArtist*) g_value_get_object(val)); 145 | break; 146 | default: 147 | G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); 148 | break; 149 | } 150 | } 151 | 152 | GtkWidget * koto_writer_page_create_item( 153 | gpointer item, 154 | gpointer user_data 155 | ) { 156 | (void) user_data; 157 | 158 | KotoAlbum * album = KOTO_ALBUM(item); // Cast the model data as an album 159 | 160 | if (!KOTO_IS_ALBUM(album)) { // Fetched item from list is not album 161 | return NULL; 162 | } 163 | 164 | KotoAudiobookView * view = koto_audiobook_view_new(); // Create our KotoAudiobookView 165 | koto_audiobook_view_set_album(view, album); // Set the item for this set up audiobook view 166 | return GTK_WIDGET(view); 167 | } 168 | 169 | GtkWidget * koto_writer_page_get_main(KotoWriterPage * self) { 170 | return self->main; 171 | } 172 | 173 | void koto_writer_page_set_artist( 174 | KotoWriterPage * self, 175 | KotoArtist * artist 176 | ) { 177 | if (!KOTO_IS_WRITER_PAGE(self)) { // Not a writers page 178 | return; 179 | } 180 | 181 | if (!KOTO_IS_ARTIST(artist)) { // Not an artist 182 | return; 183 | } 184 | 185 | self->artist = artist; 186 | gtk_label_set_text(GTK_LABEL(self->writers_header), koto_artist_get_name(self->artist)); // Get the label for the writers header 187 | 188 | self->model = G_LIST_MODEL(koto_artist_get_albums_store(artist)); // Get the store and cast it as a list model for our model 189 | 190 | gtk_flow_box_bind_model( 191 | GTK_FLOW_BOX(self->audiobooks_flow), 192 | self->model, 193 | koto_writer_page_create_item, 194 | NULL, 195 | NULL 196 | ); 197 | } 198 | 199 | KotoWriterPage * koto_writer_page_new(KotoArtist * artist) { 200 | return g_object_new( 201 | KOTO_TYPE_WRITER_PAGE, 202 | "artist", 203 | artist, 204 | NULL 205 | ); 206 | } -------------------------------------------------------------------------------- /src/pages/audiobooks/genre-button.c: -------------------------------------------------------------------------------- 1 | /* genres-button.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "../../components/button.h" 21 | #include "../../koto-utils.h" 22 | #include "genre-button.h" 23 | 24 | enum { 25 | PROP_0, 26 | PROP_GENRE_ID, 27 | PROP_GENRE_NAME, 28 | N_PROPS 29 | }; 30 | 31 | static GParamSpec * genre_button_props[N_PROPS] = { 32 | NULL, 33 | }; 34 | 35 | struct _KotoAudiobooksGenreButton { 36 | GtkBox parent_instance; 37 | 38 | gchar * genre_id; 39 | gchar * genre_name; 40 | 41 | GtkWidget * bg_image; 42 | KotoButton * button; 43 | }; 44 | 45 | struct _KotoAudiobooksGenreButtonClass { 46 | GtkBoxClass parent_class; 47 | }; 48 | 49 | G_DEFINE_TYPE(KotoAudiobooksGenreButton, koto_audiobooks_genre_button, GTK_TYPE_BOX); 50 | 51 | static void koto_audiobooks_genre_button_get_property( 52 | GObject * obj, 53 | guint prop_id, 54 | GValue * val, 55 | GParamSpec * spec 56 | ); 57 | 58 | static void koto_audiobooks_genre_button_set_property( 59 | GObject * obj, 60 | guint prop_id, 61 | const GValue * val, 62 | GParamSpec * spec 63 | ); 64 | 65 | static void koto_audiobooks_genre_button_class_init(KotoAudiobooksGenreButtonClass * c) { 66 | GObjectClass * gobject_class; 67 | 68 | gobject_class = G_OBJECT_CLASS(c); 69 | gobject_class->set_property = koto_audiobooks_genre_button_set_property; 70 | gobject_class->get_property = koto_audiobooks_genre_button_get_property; 71 | 72 | genre_button_props[PROP_GENRE_ID] = g_param_spec_string( 73 | "genre-id", 74 | "Genre ID", 75 | "Genre ID", 76 | NULL, 77 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE 78 | ); 79 | 80 | genre_button_props[PROP_GENRE_NAME] = g_param_spec_string( 81 | "genre-name", 82 | "Genre Name", 83 | "Genre Name", 84 | NULL, 85 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE 86 | ); 87 | 88 | g_object_class_install_properties(gobject_class, N_PROPS, genre_button_props); 89 | } 90 | 91 | static void koto_audiobooks_genre_button_init(KotoAudiobooksGenreButton * self) { 92 | gtk_widget_add_css_class(GTK_WIDGET(self), "audiobook-genre-button"); 93 | gtk_widget_set_hexpand(GTK_WIDGET(self), FALSE); 94 | gtk_widget_set_vexpand(GTK_WIDGET(self), FALSE); 95 | 96 | gtk_widget_set_size_request(GTK_WIDGET(self), 260, 120); 97 | GtkWidget * overlay = gtk_overlay_new(); // Create our overlay 98 | self->bg_image = gtk_picture_new(); // Create our new image 99 | 100 | self->button = koto_button_new_plain(NULL); // Create a new button 101 | koto_button_set_text_wrap(self->button, TRUE); // Enable text wrapping for long genre lines 102 | gtk_widget_set_valign(GTK_WIDGET(self->button), GTK_ALIGN_END); // Align towards bottom of button 103 | 104 | gtk_widget_set_hexpand(overlay, TRUE); 105 | 106 | gtk_overlay_set_child(GTK_OVERLAY(overlay), self->bg_image); 107 | gtk_overlay_add_overlay(GTK_OVERLAY(overlay), GTK_WIDGET(self->button)); 108 | 109 | gtk_box_append(GTK_BOX(self), overlay); // Add the overlay to self 110 | } 111 | 112 | static void koto_audiobooks_genre_button_get_property( 113 | GObject * obj, 114 | guint prop_id, 115 | GValue * val, 116 | GParamSpec * spec 117 | ) { 118 | KotoAudiobooksGenreButton * self = KOTO_AUDIOBOOKS_GENRE_BUTTON(obj); 119 | 120 | switch (prop_id) { 121 | case PROP_GENRE_ID: 122 | g_value_set_string(val, self->genre_id); 123 | break; 124 | case PROP_GENRE_NAME: 125 | g_value_set_string(val, self->genre_name); 126 | break; 127 | default: 128 | G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); 129 | break; 130 | } 131 | } 132 | 133 | static void koto_audiobooks_genre_button_set_property( 134 | GObject * obj, 135 | guint prop_id, 136 | const GValue * val, 137 | GParamSpec * spec 138 | ) { 139 | KotoAudiobooksGenreButton * self = KOTO_AUDIOBOOKS_GENRE_BUTTON(obj); 140 | 141 | switch (prop_id) { 142 | case PROP_GENRE_ID: 143 | koto_audiobooks_genre_button_set_id(self, g_strdup(g_value_get_string(val))); 144 | break; 145 | case PROP_GENRE_NAME: 146 | koto_audiobooks_genre_button_set_name(self, g_strdup(g_value_get_string(val))); 147 | break; 148 | default: 149 | G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); 150 | break; 151 | } 152 | } 153 | 154 | KotoButton * koto_audiobooks_genre_button_get_button(KotoAudiobooksGenreButton * self) { 155 | return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->button : NULL; 156 | } 157 | 158 | gchar * koto_audiobooks_genre_button_get_id(KotoAudiobooksGenreButton * self) { 159 | return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->genre_id : NULL; 160 | } 161 | 162 | gchar * koto_audiobooks_genre_button_get_name(KotoAudiobooksGenreButton * self) { 163 | return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->genre_name : NULL; 164 | } 165 | 166 | void koto_audiobooks_genre_button_set_id( 167 | KotoAudiobooksGenreButton * self, 168 | gchar * genre_id 169 | ) { 170 | if (!KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self)) { 171 | return; 172 | } 173 | 174 | if (!koto_utils_string_is_valid(genre_id)) { 175 | return; 176 | } 177 | 178 | self->genre_id = genre_id; 179 | 180 | gtk_picture_set_resource(GTK_PICTURE(self->bg_image), g_strdup_printf("/com/github/joshstrobl/koto/%s.png", genre_id)); 181 | gtk_widget_set_size_request(self->bg_image, 260, 120); 182 | } 183 | 184 | void koto_audiobooks_genre_button_set_name( 185 | KotoAudiobooksGenreButton * self, 186 | gchar * genre_name 187 | ) { 188 | if (!KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self)) { 189 | return; 190 | } 191 | 192 | if (!koto_utils_string_is_valid(genre_name)) { 193 | return; 194 | } 195 | 196 | if (koto_utils_string_is_valid(self->genre_name)) { // Already have a genre name 197 | g_free(self->genre_name); 198 | } 199 | 200 | self->genre_name = genre_name; 201 | koto_button_set_text(self->button, genre_name); 202 | } 203 | 204 | KotoAudiobooksGenreButton * koto_audiobooks_genre_button_new( 205 | gchar * genre_id, 206 | gchar * genre_name 207 | ) { 208 | return g_object_new( 209 | KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON, 210 | "genre-id", 211 | genre_id, 212 | "genre-name", 213 | genre_name, 214 | NULL 215 | ); 216 | } -------------------------------------------------------------------------------- /src/pages/audiobooks/genres-banner.c: -------------------------------------------------------------------------------- 1 | /* genres-banner.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "../../components/button.h" 21 | #include "../../koto-utils.h" 22 | #include "genre-button.h" 23 | #include "genres-banner.h" 24 | 25 | enum { 26 | SIGNAL_GENRE_CLICKED, 27 | N_SIGNALS 28 | }; 29 | 30 | static guint banner_signals[N_SIGNALS] = { 31 | 0 32 | }; 33 | 34 | struct _KotoAudiobooksGenresBanner { 35 | GObject parent_instance; 36 | 37 | GtkWidget * main; // Our main content 38 | 39 | GtkWidget * banner_revealer; // Our GtkRevealer for the strip 40 | GtkWidget * banner_viewport; // Our banner viewport 41 | GtkWidget * banner_content; // Our banner content as a horizontal box 42 | 43 | GtkWidget * strip_revealer; // Our GtkRevealer for the strip 44 | GtkWidget * strip_viewport; // Our strip viewport 45 | GtkWidget * strip_content; // Our strip content 46 | 47 | GHashTable * genre_ids_to_names; // Our genre "ids" to the name 48 | GHashTable * genre_ids_to_ptrs; // Our HashTable of genre IDs to our GPtrArray with pointers to banner and strip buttons 49 | }; 50 | 51 | struct _KotoAudiobooksGenresBannerClass { 52 | GObjectClass parent_instance; 53 | void (* genre_clicked) (gchar * genre); 54 | }; 55 | 56 | G_DEFINE_TYPE(KotoAudiobooksGenresBanner, koto_audiobooks_genres_banner, G_TYPE_OBJECT); 57 | 58 | static void koto_audiobooks_genres_banner_class_init(KotoAudiobooksGenresBannerClass * c) { 59 | GObjectClass * gobject_class = G_OBJECT_CLASS(c); 60 | 61 | banner_signals[SIGNAL_GENRE_CLICKED] = g_signal_new( 62 | "genre-clicked", 63 | G_TYPE_FROM_CLASS(gobject_class), 64 | G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, 65 | G_STRUCT_OFFSET(KotoAudiobooksGenresBannerClass, genre_clicked), 66 | NULL, 67 | NULL, 68 | NULL, 69 | G_TYPE_NONE, 70 | 1, 71 | G_TYPE_CHAR 72 | ); 73 | } 74 | 75 | static void koto_audiobooks_genres_banner_init(KotoAudiobooksGenresBanner * self) { 76 | self->genre_ids_to_ptrs = g_hash_table_new(g_str_hash, g_str_equal); 77 | self->genre_ids_to_names = g_hash_table_new(g_str_hash, g_str_equal); 78 | g_hash_table_insert(self->genre_ids_to_names, g_strdup("hip-hop"), g_strdup("Hip-hop")); 79 | g_hash_table_insert(self->genre_ids_to_names, g_strdup("indie"), g_strdup("Indie")); 80 | g_hash_table_insert(self->genre_ids_to_names, g_strdup("sci-fi"), g_strdup("Science Fiction")); 81 | 82 | self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Create the main box as a vertical box 83 | gtk_widget_add_css_class(self->main, "genres-banner"); 84 | gtk_widget_set_halign(self->main, GTK_ALIGN_START); 85 | gtk_widget_set_vexpand(self->main, FALSE); 86 | 87 | self->banner_revealer = gtk_revealer_new(); // Create our revealer for the banner 88 | self->strip_revealer = gtk_revealer_new(); // Create a revealer for the strip 89 | 90 | gtk_revealer_set_transition_type(GTK_REVEALER(self->strip_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN); 91 | gtk_revealer_set_transition_type(GTK_REVEALER(self->banner_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP); 92 | 93 | self->banner_viewport = gtk_viewport_new(NULL, NULL); // Create our viewport for enabling the banner content to be scrollable 94 | self->strip_viewport = gtk_viewport_new(NULL, NULL); // Create our viewport for enabling the strip content to be scrollable 95 | 96 | self->banner_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create our horizontal box for the content 97 | gtk_widget_add_css_class(self->banner_content, "large-banner"); 98 | 99 | self->strip_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create the horizontal box for the strip content 100 | gtk_widget_add_css_class(self->strip_content, "strip"); 101 | 102 | gtk_revealer_set_child(GTK_REVEALER(self->banner_revealer), self->banner_viewport); // Set the viewport to be the content which gets revealed 103 | gtk_revealer_set_child(GTK_REVEALER(self->strip_revealer), self->strip_viewport); // Set the viewport to be the cont ent which gets revealed 104 | gtk_revealer_set_reveal_child(GTK_REVEALER(self->banner_revealer), TRUE); // Show the banner by default 105 | 106 | gtk_viewport_set_child(GTK_VIEWPORT(self->banner_viewport), self->banner_content); // Set the banner content for this viewport 107 | gtk_viewport_set_child(GTK_VIEWPORT(self->strip_viewport), self->strip_content); // Set the strip content for this viewport 108 | 109 | gtk_box_append(GTK_BOX(self->main), self->strip_revealer); 110 | gtk_box_append(GTK_BOX(self->main), self->banner_revealer); 111 | } 112 | 113 | void koto_audiobooks_genres_banner_add_genre( 114 | KotoAudiobooksGenresBanner * self, 115 | gchar * genre_id 116 | ) { 117 | if (!KOTO_IS_AUDIOBOOKS_GENRES_BANNER(self)) { // Not a banner 118 | return; 119 | } 120 | 121 | if (!koto_utils_string_is_valid(genre_id)) { // Not a valid genre ID 122 | return; 123 | } 124 | 125 | if (g_hash_table_contains(self->genre_ids_to_ptrs, genre_id)) { // Already have added this 126 | return; 127 | } 128 | 129 | gchar * name = g_hash_table_lookup(self->genre_ids_to_names, genre_id); // Get the named equivelant for this ID 130 | 131 | if (name == NULL) { // Didn't find a name equivelant 132 | name = g_strdup(genre_id); // Just duplicate the genre ID for now 133 | } 134 | 135 | KotoButton * genre_strip_button = koto_button_new_plain(name); // Create a new button with the name 136 | gtk_box_append(GTK_BOX(self->strip_content), GTK_WIDGET(genre_strip_button)); // Add our KotoButton to the strip content 137 | KotoAudiobooksGenreButton * genre_banner_button = koto_audiobooks_genre_button_new(genre_id, name); // Create our big button 138 | gtk_box_append(GTK_BOX(self->banner_content), GTK_WIDGET(genre_banner_button)); 139 | 140 | GPtrArray * buttons = g_ptr_array_new(); 141 | g_ptr_array_add(buttons, (gpointer) genre_strip_button); // Add our KotoButton as a gpointer to our GPtrArray as the first item 142 | g_ptr_array_add(buttons, (gpointer) genre_banner_button); // Add our KotoAudiobooksGenresButton as a gpointer to our GPtrArray as the second item 143 | 144 | g_hash_table_replace(self->genre_ids_to_ptrs, genre_id, buttons); // Add our GPtrArray to our genre_ids_to_ptrs 145 | } 146 | 147 | GtkWidget * koto_audiobooks_genres_banner_get_main(KotoAudiobooksGenresBanner * self) { 148 | return KOTO_IS_AUDIOBOOKS_GENRES_BANNER(self) ? self->main : NULL; 149 | } 150 | 151 | KotoAudiobooksGenresBanner * koto_audiobooks_genres_banner_new() { 152 | return g_object_new(KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER, NULL); 153 | } -------------------------------------------------------------------------------- /data/vectors/foreign-languages.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 32 | 34 | 36 | 44 | 45 | 46 | 53 | 62 | 66 | 69 | 74 | 79 | 84 | 90 | 96 | 97 | Bonjour 119 | Hello 141 | 142 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /src/components/cover-art-button.c: -------------------------------------------------------------------------------- 1 | /* cover-art-button.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "../koto-utils.h" 21 | #include "button.h" 22 | #include "cover-art-button.h" 23 | 24 | struct _KotoCoverArtButton { 25 | GObject parent_instance; 26 | 27 | GtkWidget * art; 28 | GtkWidget * main; 29 | GtkWidget * revealer; 30 | KotoButton * play_button; 31 | 32 | gchar * art_path; 33 | 34 | guint height; 35 | guint width; 36 | }; 37 | 38 | G_DEFINE_TYPE(KotoCoverArtButton, koto_cover_art_button, G_TYPE_OBJECT); 39 | 40 | enum { 41 | PROP_0, 42 | PROP_DESIRED_HEIGHT, 43 | PROP_DESIRED_WIDTH, 44 | PROP_ART_PATH, 45 | N_PROPERTIES 46 | }; 47 | 48 | static GParamSpec * props[N_PROPERTIES] = { 49 | NULL, 50 | }; 51 | static void koto_cover_art_button_get_property( 52 | GObject * obj, 53 | guint prop_id, 54 | GValue * val, 55 | GParamSpec * spec 56 | ); 57 | 58 | static void koto_cover_art_button_set_property( 59 | GObject * obj, 60 | guint prop_id, 61 | const GValue * val, 62 | GParamSpec * spec 63 | ); 64 | 65 | static void koto_cover_art_button_class_init(KotoCoverArtButtonClass * c) { 66 | GObjectClass * gobject_class; 67 | 68 | gobject_class = G_OBJECT_CLASS(c); 69 | gobject_class->get_property = koto_cover_art_button_get_property; 70 | gobject_class->set_property = koto_cover_art_button_set_property; 71 | 72 | props[PROP_DESIRED_HEIGHT] = g_param_spec_uint( 73 | "desired-height", 74 | "Desired height", 75 | "Desired height", 76 | 0, 77 | G_MAXUINT, 78 | 0, 79 | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE 80 | ); 81 | 82 | props[PROP_DESIRED_WIDTH] = g_param_spec_uint( 83 | "desired-width", 84 | "Desired width", 85 | "Desired width", 86 | 0, 87 | G_MAXUINT, 88 | 0, 89 | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE 90 | ); 91 | 92 | props[PROP_ART_PATH] = g_param_spec_string( 93 | "art-path", 94 | "Path to art", 95 | "Path to art", 96 | NULL, 97 | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE 98 | ); 99 | 100 | g_object_class_install_properties(gobject_class, N_PROPERTIES, props); 101 | } 102 | 103 | static void koto_cover_art_button_init(KotoCoverArtButton * self) { 104 | self->main = gtk_overlay_new(); // Create our overlay container 105 | gtk_widget_add_css_class(self->main, "cover-art-button"); 106 | self->revealer = gtk_revealer_new(); // Create a new revealer 107 | gtk_revealer_set_transition_type(GTK_REVEALER(self->revealer), GTK_REVEALER_TRANSITION_TYPE_CROSSFADE); 108 | gtk_revealer_set_transition_duration(GTK_REVEALER(self->revealer), 400); 109 | 110 | GtkWidget * controls = gtk_center_box_new(); // Create a center box for the controls 111 | 112 | self->play_button = koto_button_new_with_icon("", "media-playback-start-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); 113 | gtk_center_box_set_center_widget(GTK_CENTER_BOX(controls), GTK_WIDGET(self->play_button)); 114 | 115 | gtk_revealer_set_child(GTK_REVEALER(self->revealer), controls); 116 | koto_cover_art_button_hide_overlay_controls(NULL, self); // Hide by default 117 | gtk_overlay_add_overlay(GTK_OVERLAY(self->main), self->revealer); // Add our revealer as the overlay 118 | 119 | GtkEventController * motion_controller = gtk_event_controller_motion_new(); // Create our new motion event controller to track mouse leave and enter 120 | 121 | g_signal_connect(motion_controller, "enter", G_CALLBACK(koto_cover_art_button_show_overlay_controls), self); 122 | g_signal_connect(motion_controller, "leave", G_CALLBACK(koto_cover_art_button_hide_overlay_controls), self); 123 | gtk_widget_add_controller(self->main, motion_controller); 124 | 125 | self->art_path = NULL; 126 | } 127 | 128 | static void koto_cover_art_button_get_property( 129 | GObject * obj, 130 | guint prop_id, 131 | GValue * val, 132 | GParamSpec * spec 133 | ) { 134 | (void) val; 135 | 136 | switch (prop_id) { 137 | default: 138 | G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); 139 | break; 140 | } 141 | } 142 | 143 | static void koto_cover_art_button_set_property( 144 | GObject * obj, 145 | guint prop_id, 146 | const GValue * val, 147 | GParamSpec * spec 148 | ) { 149 | KotoCoverArtButton * self = KOTO_COVER_ART_BUTTON(obj); 150 | 151 | switch (prop_id) { 152 | case PROP_ART_PATH: 153 | koto_cover_art_button_set_art_path(self, (gchar*) g_value_get_string(val)); // Get the value and call our set_art_path with it 154 | break; 155 | case PROP_DESIRED_HEIGHT: 156 | koto_cover_art_button_set_dimensions(self, g_value_get_uint(val), 0); 157 | break; 158 | case PROP_DESIRED_WIDTH: 159 | koto_cover_art_button_set_dimensions(self, 0, g_value_get_uint(val)); 160 | break; 161 | default: 162 | G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); 163 | break; 164 | } 165 | } 166 | 167 | void koto_cover_art_button_hide_overlay_controls( 168 | GtkEventControllerFocus * controller, 169 | gpointer data 170 | ) { 171 | (void) controller; 172 | KotoCoverArtButton * self = data; 173 | 174 | gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), FALSE); 175 | } 176 | 177 | KotoButton * koto_cover_art_button_get_button(KotoCoverArtButton * self) { 178 | if (!KOTO_IS_COVER_ART_BUTTON(self)) { 179 | return NULL; 180 | } 181 | 182 | return self->play_button; 183 | } 184 | 185 | GtkWidget * koto_cover_art_button_get_main(KotoCoverArtButton * self) { 186 | if (!KOTO_IS_COVER_ART_BUTTON(self)) { 187 | return NULL; 188 | } 189 | 190 | return self->main; 191 | } 192 | 193 | void koto_cover_art_button_set_art_path( 194 | KotoCoverArtButton * self, 195 | gchar * art_path 196 | ) { 197 | if (!KOTO_IS_COVER_ART_BUTTON(self)) { 198 | return; 199 | } 200 | 201 | gboolean defined_artwork = koto_utils_string_is_valid(art_path); 202 | 203 | if (GTK_IS_IMAGE(self->art)) { // Already have an image 204 | if (!defined_artwork) { // No art path or empty string 205 | gtk_image_set_from_icon_name(GTK_IMAGE(self->art), "audio-x-generic-symbolic"); 206 | } else { // Have an art path 207 | if (g_strcmp0(self->art_path, art_path) != 0) { 208 | self->art_path = g_strdup(art_path); // Set our art path 209 | gtk_image_set_from_file(GTK_IMAGE(self->art), self->art_path); // Set from the file 210 | } 211 | } 212 | } else { // If we don't have an image 213 | self->art = koto_utils_create_image_from_filepath(defined_artwork ? g_strdup(art_path) : NULL, "audio-x-generic-symbolic", self->width, self->height); 214 | gtk_overlay_set_child(GTK_OVERLAY(self->main), self->art); // Set the child 215 | } 216 | } 217 | 218 | void koto_cover_art_button_set_dimensions( 219 | KotoCoverArtButton * self, 220 | guint height, 221 | guint width 222 | ) { 223 | if (!KOTO_IS_COVER_ART_BUTTON(self)) { 224 | return; 225 | } 226 | 227 | if (height != 0) { 228 | self->height = height; 229 | } 230 | 231 | if (width != 0) { 232 | self->width = width; 233 | } 234 | 235 | if ((self->height != 0) && (self->width != 0)) { // Both height and width set 236 | gtk_widget_set_size_request(self->main, self->width, self->height); // Update our widget 237 | 238 | if (GTK_IS_IMAGE(self->art)) { // Art is defined 239 | gtk_widget_set_size_request(self->art, self->width, self->height); // Update our image as well 240 | } 241 | } 242 | } 243 | 244 | void koto_cover_art_button_show_overlay_controls( 245 | GtkEventControllerFocus * controller, 246 | gpointer data 247 | ) { 248 | (void) controller; 249 | KotoCoverArtButton * self = data; 250 | 251 | gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), TRUE); 252 | } 253 | 254 | KotoCoverArtButton * koto_cover_art_button_new( 255 | guint height, 256 | guint width, 257 | gchar * art_path 258 | ) { 259 | return g_object_new( 260 | KOTO_TYPE_COVER_ART_BUTTON, 261 | "desired-height", 262 | height, 263 | "desired-width", 264 | width, 265 | "art-path", 266 | art_path, 267 | NULL 268 | ); 269 | } -------------------------------------------------------------------------------- /src/pages/audiobooks/library.c: -------------------------------------------------------------------------------- 1 | /* library.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "../../components/button.h" 21 | #include "../../db/cartographer.h" 22 | #include "../../indexer/structs.h" 23 | #include "../../koto-utils.h" 24 | #include "../../koto-window.h" 25 | #include "genres-banner.h" 26 | #include "library.h" 27 | #include "writer-page.h" 28 | 29 | extern KotoCartographer * koto_maps; 30 | extern KotoWindow * main_window; 31 | 32 | struct _KotoAudiobooksLibraryPage { 33 | GObject parent_instance; 34 | 35 | GtkWidget * main; // Our main content, contains banner and scrolled window 36 | GtkWidget * content_scroll; // Our Scrolled Window 37 | GtkWidget * content; // Content inside scrolled window 38 | 39 | KotoAudiobooksGenresBanner * banner; 40 | 41 | GtkWidget * writers_flow; 42 | GHashTable * writers_to_buttons; // HashTable of UUIDs of "Artists" to their KotoButton 43 | GHashTable * writers_to_pages; // HashTable of UUIDs of "Artists" to their KotoAudiobooksWritersPage 44 | }; 45 | 46 | struct _KotoAudiobooksLibraryPageClass { 47 | GObjectClass parent_instance; 48 | }; 49 | 50 | G_DEFINE_TYPE(KotoAudiobooksLibraryPage, koto_audiobooks_library_page, G_TYPE_OBJECT); 51 | 52 | KotoAudiobooksLibraryPage * audiobooks_library_page; 53 | 54 | static void koto_audiobooks_library_page_init(KotoAudiobooksLibraryPage * self) { 55 | self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 56 | gtk_widget_add_css_class(self->main, "audiobook-library"); 57 | self->content_scroll = gtk_scrolled_window_new(); // Create our GtkScrolledWindow 58 | self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 59 | gtk_widget_set_vexpand(self->content, TRUE); // Ensure content expands vertically to allow flowboxchild to take up as much space as it requests 60 | gtk_widget_add_css_class(self->content, "content"); 61 | 62 | self->banner = koto_audiobooks_genres_banner_new(); // Create our banner 63 | 64 | self->writers_flow = gtk_flow_box_new(); // Create our flow box 65 | //gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(self->writers_flow), TRUE); 66 | gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(self->writers_flow), 100); // Set to a random amount that is not realistic, however GTK sets a default to 7 which is too small. 67 | gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->writers_flow), GTK_SELECTION_NONE); 68 | gtk_widget_add_css_class(self->writers_flow, "writers-button-flow"); 69 | 70 | self->writers_to_buttons = g_hash_table_new(g_str_hash, g_str_equal); 71 | self->writers_to_pages = g_hash_table_new(g_str_hash, g_str_equal); 72 | 73 | g_signal_connect(koto_maps, "album-added", G_CALLBACK(koto_audiobooks_library_page_handle_add_album), self); // Notify when we have a new Album 74 | g_signal_connect(koto_maps, "artist-added", G_CALLBACK(koto_audiobooks_library_page_handle_add_artist), self); // Notify when we have a new Artist 75 | 76 | gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->content_scroll), self->content); // Add our content to the content scroll 77 | gtk_box_append(GTK_BOX(self->main), koto_audiobooks_genres_banner_get_main(self->banner)); // Add the banner to the content 78 | gtk_box_append(GTK_BOX(self->main), self->content_scroll); // Add our scroll window to the main content 79 | gtk_box_append(GTK_BOX(self->content), self->writers_flow); // Add our flowbox to the content 80 | } 81 | 82 | static void koto_audiobooks_library_page_class_init(KotoAudiobooksLibraryPageClass * c) { 83 | (void) c; 84 | } 85 | 86 | void koto_audiobooks_library_page_create_artist_ux( 87 | KotoAudiobooksLibraryPage * self, 88 | KotoArtist * artist 89 | ) { 90 | if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage 91 | return; 92 | } 93 | 94 | if (!KOTO_IS_ARTIST(artist)) { // Not an artist 95 | return; 96 | } 97 | 98 | gchar * artist_uuid = koto_artist_get_uuid(artist); // Get the artist UUID 99 | 100 | if (!g_hash_table_contains(self->writers_to_pages, artist_uuid)) { // Don't have the page 101 | KotoWriterPage * writers_page = koto_writer_page_new(artist); 102 | koto_window_add_page(main_window, artist_uuid, koto_writer_page_get_main(writers_page)); // Add the page to the stack 103 | } 104 | 105 | if (!g_hash_table_contains(self->writers_to_buttons, artist_uuid)) { // Don't have the button for this 106 | KotoButton * artist_button = koto_button_new_plain(koto_artist_get_name(artist)); // Create a KotoButton for this artist 107 | koto_button_set_data(artist_button, artist_uuid); // Set the artist_uuid as the data for the button 108 | 109 | koto_button_set_text_justify(artist_button, GTK_JUSTIFY_CENTER); // Center the text 110 | koto_button_set_text_wrap(artist_button, TRUE); 111 | gtk_widget_add_css_class(GTK_WIDGET(artist_button), "writer-button"); 112 | gtk_widget_set_size_request(GTK_WIDGET(artist_button), 260, 120); 113 | 114 | koto_button_add_click_handler(artist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), artist_button); 115 | 116 | g_hash_table_replace(self->writers_to_buttons, artist_uuid, artist_button); // Add the button to the Writers to Buttons hashtable 117 | gtk_flow_box_insert(GTK_FLOW_BOX(self->writers_flow), GTK_WIDGET(artist_button), -1); // Append the button to our flowbox 118 | 119 | GtkWidget * button_parent = gtk_widget_get_parent(GTK_WIDGET(artist_button)); // This is the GtkFlowboxChild that the button is in 120 | gtk_widget_set_halign(button_parent, GTK_ALIGN_START); 121 | } 122 | } 123 | 124 | void koto_audiobooks_library_page_handle_add_album( 125 | KotoCartographer * carto, 126 | KotoAlbum * album, 127 | KotoAudiobooksLibraryPage * self 128 | ) { 129 | 130 | if (!KOTO_IS_CARTOGRAPHER(carto)) { // Not cartographer 131 | return; 132 | } 133 | 134 | if (!KOTO_IS_ALBUM(album)) { // Not an album 135 | return; 136 | } 137 | 138 | if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage 139 | return; 140 | } 141 | 142 | gchar * artist_uuid = koto_album_get_artist_uuid(album); // Get the Album's artist UUID 143 | KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); 144 | 145 | if (!KOTO_IS_ARTIST(artist)) { // Failed to get artist 146 | return; 147 | } 148 | 149 | if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Not in an Audiobook library 150 | return; 151 | } 152 | 153 | koto_audiobooks_library_page_add_genres(self, koto_album_get_genres(album)); // Add all the genres necessary for this album 154 | } 155 | 156 | void koto_audiobooks_library_page_handle_add_artist( 157 | KotoCartographer * carto, 158 | KotoArtist * artist, 159 | KotoAudiobooksLibraryPage * self 160 | ) { 161 | if (!KOTO_IS_CARTOGRAPHER(carto)) { // Not cartographer 162 | return; 163 | } 164 | 165 | if (!KOTO_IS_ARTIST(artist)) { // Not an artist 166 | return; 167 | } 168 | 169 | if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage 170 | return; 171 | } 172 | 173 | if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Not in an Audiobook library 174 | return; 175 | } 176 | 177 | koto_audiobooks_library_page_create_artist_ux(self, artist); // Create the UX for the artist if necessary 178 | } 179 | 180 | void koto_audiobooks_library_page_add_genres( 181 | KotoAudiobooksLibraryPage * self, 182 | GList * genres 183 | ) { 184 | if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage 185 | return; 186 | } 187 | 188 | if (g_list_length(genres) == 0) { // No contents 189 | return; 190 | } 191 | 192 | GList * cur_list; 193 | for (cur_list = genres; cur_list != NULL; cur_list = cur_list->next) { // Iterate over each genre 194 | gchar * genre = (gchar*) cur_list->data; 195 | if (!koto_utils_string_is_valid(genre)) { 196 | continue; 197 | } 198 | 199 | if (g_strcmp0(genre, "audiobook") == 0) { // Is generic 200 | continue; 201 | } 202 | 203 | koto_audiobooks_genres_banner_add_genre(self->banner, genre); // Add this genre 204 | } 205 | } 206 | 207 | GtkWidget * koto_audiobooks_library_page_get_main(KotoAudiobooksLibraryPage * self) { 208 | return KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self) ? self->main : NULL; 209 | } 210 | 211 | KotoAudiobooksLibraryPage * koto_audiobooks_library_page_new() { 212 | return g_object_new(KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE, NULL); 213 | } -------------------------------------------------------------------------------- /src/koto-utils.c: -------------------------------------------------------------------------------- 1 | /* koto-utils.c 2 | * 3 | * Copyright 2021 Joshua Strobl 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "koto-utils.h" 24 | 25 | extern GtkWindow * main_window; 26 | 27 | GtkFileChooserNative * koto_utils_create_image_file_chooser(gchar * file_chooser_label) { 28 | GtkFileChooserNative * chooser = gtk_file_chooser_native_new( 29 | file_chooser_label, 30 | main_window, 31 | GTK_FILE_CHOOSER_ACTION_OPEN, 32 | "Choose", 33 | "Cancel" 34 | ); 35 | 36 | GtkFileFilter * image_filter = gtk_file_filter_new(); // Create our file filter 37 | 38 | gtk_file_filter_add_mime_type(image_filter, "image/*"); // Only allow for images 39 | gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(chooser), image_filter); // Only allow picking images 40 | gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), FALSE); 41 | 42 | return chooser; 43 | } 44 | 45 | GtkWidget * koto_utils_create_image_from_filepath( 46 | gchar * filepath, 47 | gchar * fallback_icon, 48 | guint width, 49 | guint height 50 | ) { 51 | GtkWidget * image = NULL; 52 | 53 | if ((filepath != NULL) && (strcmp(filepath, "") != 0)) { // If we have a filepath 54 | if (g_file_test(filepath, G_FILE_TEST_EXISTS)) { // File exists 55 | image = gtk_image_new_from_file(filepath); // Load from the filepath 56 | } 57 | } 58 | 59 | if (!GTK_IS_IMAGE(image)) { // If we failed to get the image or never passed a valid filepath to begin with 60 | image = gtk_image_new_from_icon_name(fallback_icon); // Set to the fallback icon 61 | } 62 | 63 | gtk_image_set_icon_size(GTK_IMAGE(image), GTK_ICON_SIZE_INHERIT); 64 | gtk_image_set_pixel_size(GTK_IMAGE(image), width); 65 | gtk_widget_set_size_request(image, width, height); 66 | 67 | return image; 68 | } 69 | 70 | gchar * koto_utils_gboolean_to_string(gboolean b) { 71 | return g_strdup(b ? "true" : "false"); 72 | } 73 | 74 | gchar * koto_utils_get_filename_without_extension(gchar * filename) { 75 | gchar * trimmed_file_name = g_strdup(g_path_get_basename(filename)); // Ensure the filename provided is the base name of any possible path and duplicate it 76 | gchar ** split = g_strsplit(trimmed_file_name, ".", -1); // Split every time we see . 77 | 78 | g_free(trimmed_file_name); 79 | guint len_of_extension_split = g_strv_length(split); 80 | 81 | if (len_of_extension_split == 2) { // Only have two elements 82 | trimmed_file_name = g_strdup(split[0]); // Get the first element 83 | } else { 84 | gchar * new_parsed_name = ""; 85 | for (guint i = 0; i < len_of_extension_split - 1; i++) { // Iterate over everything except the last item 86 | if (g_strcmp0(new_parsed_name, "") == 0) { // Currently empty 87 | new_parsed_name = g_strdup(split[i]); // Just duplicate this string 88 | } else { 89 | gchar * tmp_copy = g_strdup(new_parsed_name); 90 | g_free(new_parsed_name); // Free the old 91 | new_parsed_name = g_strjoin(".", tmp_copy, split[i], NULL); // Join the two strings with a . again and duplicate it, setting it to our new_parsed_name 92 | g_free(tmp_copy); // Free our temporary copy 93 | } 94 | } 95 | 96 | trimmed_file_name = g_strdup(new_parsed_name); 97 | g_free(new_parsed_name); 98 | } 99 | 100 | gchar * stripped_file_name = g_strstrip(g_strdup(trimmed_file_name)); // Strip leading and trailing whitespace 101 | 102 | g_free(trimmed_file_name); 103 | g_strfreev(split); 104 | return stripped_file_name; 105 | } 106 | 107 | gchar * koto_utils_join_string_list ( 108 | GList * list, 109 | gchar * sep 110 | ) { 111 | gchar * liststring = NULL; 112 | GList * cur_list; 113 | for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) { // For each item in the list 114 | gchar * current_item = cur_list->data; 115 | if (!koto_utils_string_is_valid(current_item)) { // Not a valid string 116 | continue; 117 | } 118 | 119 | gchar * item_plus_sep = g_strdup_printf("%s%s", current_item, sep); 120 | 121 | if (koto_utils_string_is_valid(liststring)) { // Is a valid string 122 | gchar * new_string = g_strconcat(liststring, item_plus_sep, NULL); 123 | g_free(liststring); 124 | liststring = new_string; 125 | } else { // Don't have any content yet 126 | liststring = item_plus_sep; 127 | } 128 | } 129 | 130 | return (liststring == NULL) ? g_strdup("") : liststring; 131 | } 132 | void koto_utils_mkdir(gchar * path) { 133 | mkdir(path, 0755); 134 | chown(path, getuid(), getgid()); 135 | } 136 | 137 | void koto_utils_push_queue_element_to_store( 138 | gpointer data, 139 | gpointer user_data 140 | ) { 141 | g_list_store_append(G_LIST_STORE(user_data), data); 142 | } 143 | 144 | gchar * koto_utils_seconds_to_time_format(guint64 seconds) { 145 | GDateTime * date = g_date_time_new_from_unix_utc(seconds); // Add our seconds after UTC 146 | gchar * date_string = g_date_time_format(date, (g_date_time_get_hour(date) != 0) ? "%H:%M:%S" : "%M:%S"); 147 | g_date_time_unref(date); 148 | return date_string; 149 | } 150 | 151 | gboolean koto_utils_string_contains_substring( 152 | gchar * s, 153 | gchar * sub 154 | ) { 155 | gchar ** separated_string = g_strsplit(s, sub, -1); // Split on our substring 156 | gboolean contains = (g_strv_length(separated_string) > 1); 157 | g_strfreev(separated_string); 158 | return contains; 159 | } 160 | 161 | gchar * koto_utils_string_get_valid(gchar * str) { 162 | return koto_utils_string_is_valid(str) ? str : g_strdup(""); // Return string if a string, otherwise return an empty string 163 | } 164 | 165 | gboolean koto_utils_string_is_valid(const gchar * str) { 166 | return ((str != NULL) && (g_strcmp0(str, "") != 0)); 167 | } 168 | 169 | gchar * koto_utils_string_replace_all( 170 | gchar * str, 171 | gchar * find, 172 | gchar * repl 173 | ) { 174 | if (!koto_utils_string_is_valid(str)) { // Not a valid string 175 | return g_strdup(""); 176 | } 177 | 178 | gchar ** split = g_strsplit(str, find, -1); // Split on find 179 | 180 | guint split_len = g_strv_length(split); 181 | 182 | if (split_len == 1) { // Only one item 183 | g_strfreev(split); 184 | return g_strdup(str); // Just set to the string we were provided 185 | } 186 | 187 | return g_strdup(g_strjoinv(repl, split)); 188 | } 189 | 190 | GList * koto_utils_string_to_string_list( 191 | gchar * s, 192 | gchar * sep 193 | ) { 194 | GList * list = NULL; 195 | if (!koto_utils_string_is_valid(s)) { // Provided string is not valid 196 | return list; 197 | } 198 | 199 | gchar ** separated_strings = g_strsplit(s, sep, -1); // Split on separator for the string 200 | 201 | for (guint i = 0; i < g_strv_length(separated_strings); i++) { // Iterate over each item 202 | gchar * item = separated_strings[i]; 203 | if (g_strcmp0(item, "") != 0) { // Not an empty string 204 | list = g_list_append(list, g_strdup(item)); 205 | } 206 | } 207 | 208 | g_strfreev(separated_strings); // Free our strings 209 | return list; 210 | } 211 | 212 | gchar * koto_utils_string_title(const gchar * s) { 213 | if (!koto_utils_string_is_valid(s)) { // Not a valid string 214 | return NULL; 215 | } 216 | 217 | glong len = g_utf8_strlen(s, -1); 218 | 219 | if (len == 0) { // Empty string 220 | return g_strdup(s); // Just duplicate itself 221 | } else if (len == 1) { // One char 222 | return g_utf8_strup(s, -1); // Uppercase all relevant cases 223 | } else { 224 | gchar * first_char = g_utf8_substring(s, 0, 1); 225 | gchar * rest_of_string = g_utf8_substring(s, 1, len); // Rest of string 226 | gchar * titled_string = g_strdup_printf("%s%s", g_utf8_strup(first_char, -1), rest_of_string); 227 | 228 | g_free(first_char); 229 | g_free(rest_of_string); 230 | return titled_string; 231 | } 232 | } 233 | 234 | gchar * koto_utils_string_unquote(gchar * s) { 235 | gchar * new_s = NULL; 236 | 237 | if (!koto_utils_string_is_valid(s)) { // Not a valid string 238 | new_s = g_strdup(""); 239 | return new_s; 240 | } 241 | 242 | if (g_str_has_prefix(s, "'") && g_str_has_suffix(s, "'")) { // Begins and ends with ' 243 | new_s = g_utf8_substring(s, 1, g_utf8_strlen(s, -1) - 1); // Start at 1 and end at n-1 244 | } else { 245 | new_s = g_strdup(s); 246 | } 247 | 248 | gchar ** split_on_double_single = g_strsplit(new_s, "''", -1); // Split on instances of '' 249 | 250 | new_s = g_strjoinv("'", split_on_double_single); // Rejoin as ' 251 | g_strfreev(split_on_double_single); // Free our array 252 | 253 | return new_s; 254 | } 255 | --------------------------------------------------------------------------------
Koto is an in-development audiobook, music, and podcast manager that is designed for and caters to a modern desktop Linux experience.