Skip to content

Commit 3648594

Browse files
committed
feat: more player controls (#66)
1 parent c53220a commit 3648594

12 files changed

+308
-60
lines changed

data/dev.geopjr.Turntable.gresource.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
<file preprocess="xml-stripblanks" alias="left-large-symbolic.svg">icons/hicolor/symbolic/actions/left-large-symbolic.svg</file>
2121
<file preprocess="xml-stripblanks" alias="right-large-symbolic.svg">icons/hicolor/symbolic/actions/right-large-symbolic.svg</file>
2222
<file preprocess="xml-stripblanks" alias="user-trash-symbolic.svg">icons/hicolor/symbolic/actions/user-trash-symbolic.svg</file>
23+
<file preprocess="xml-stripblanks" alias="playlist-shuffle-symbolic.svg">icons/hicolor/symbolic/actions/playlist-shuffle-symbolic.svg</file>
24+
<file preprocess="xml-stripblanks" alias="playlist-repeat-symbolic.svg">icons/hicolor/symbolic/actions/playlist-repeat-symbolic.svg</file>
25+
<file preprocess="xml-stripblanks" alias="playlist-repeat-song-symbolic.svg">icons/hicolor/symbolic/actions/playlist-repeat-song-symbolic.svg</file>
26+
<file preprocess="xml-stripblanks" alias="playlist-consecutive-symbolic.svg">icons/hicolor/symbolic/actions/playlist-consecutive-symbolic.svg</file>
2327
<file preprocess="xml-stripblanks" alias="listenbrainz.svg">icons/hicolor/scalable/actions/listenbrainz.svg</file>
2428
<file preprocess="xml-stripblanks" alias="maloja.svg">icons/hicolor/scalable/actions/maloja.svg</file>
2529
<file preprocess="xml-stripblanks" alias="librefm.svg">icons/hicolor/scalable/actions/librefm.svg</file>

data/dev.geopjr.Turntable.gschema.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
<key name="now-playing" type="b">
5353
<default>false</default>
5454
</key>
55+
<key name="component-more-controls" type="b">
56+
<default>false</default>
57+
</key>
5558

5659
<key name="window-w" type="i">
5760
<default>0</default>
Lines changed: 2 additions & 0 deletions
Loading
Lines changed: 2 additions & 0 deletions
Loading
Lines changed: 2 additions & 0 deletions
Loading
Lines changed: 2 additions & 0 deletions
Loading

src/Mpris/Entry.vala

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@ public class Turntable.Mpris.Entry : GLib.Object {
1818
public string client_info_name { get { return this.client_info.identity; } }
1919
public string client_info_icon { get { return this.client_info.icon; } }
2020

21-
public bool looping { get; private set; default = false; }
21+
public enum LoopStatus {
22+
NONE,
23+
TRACK,
24+
PLAYLIST;
25+
}
26+
27+
public LoopStatus loop_status { get; private set; default = LoopStatus.NONE; }
2228
public bool can_go_next { get; private set; default = false; }
2329
public bool can_go_back { get; private set; default = false; }
2430
public bool can_control { get; private set; default = false; }
2531
public bool playing { get; private set; default = false; }
32+
public bool shuffle { get; private set; default = false; }
2633
public string? art { get; private set; default = null; }
2734
public string? title { get; private set; default = null; }
2835
public string? artist { get; private set; default = null; }
@@ -54,6 +61,22 @@ public class Turntable.Mpris.Entry : GLib.Object {
5461
}
5562
}
5663

64+
public void toggle_shuffle () {
65+
this.player.shuffle = !this.player.shuffle;
66+
}
67+
68+
public void loop_none () {
69+
this.player.loop_status = "None";
70+
}
71+
72+
public void loop_track () {
73+
this.player.loop_status = "Track";
74+
}
75+
76+
public void loop_playlist () {
77+
this.player.loop_status = "Playlist";
78+
}
79+
5780
#if SANDBOXED
5881
GLib.HashTable<string, string> cached_desktop_icons = new GLib.HashTable<string, string> (str_hash, str_equal);
5982
private string get_sandboxed_icon_for_id (string id) {
@@ -127,6 +150,7 @@ public class Turntable.Mpris.Entry : GLib.Object {
127150
update_playback_status ();
128151
update_controls ();
129152
update_loop_status ();
153+
update_shuffle_status ();
130154

131155
GLib.Timeout.add (PROGRESS_UPDATE_TIME, update_position);
132156
} catch (Error e) {
@@ -157,6 +181,9 @@ public class Turntable.Mpris.Entry : GLib.Object {
157181
case "LoopStatus":
158182
update_loop_status ();
159183
break;
184+
case "Shuffle":
185+
update_shuffle_status ();
186+
break;
160187
case "CanGoNext":
161188
case "CanGoPrevious":
162189
case "CanPlay":
@@ -171,7 +198,17 @@ public class Turntable.Mpris.Entry : GLib.Object {
171198
}
172199

173200
private void update_loop_status () {
174-
this.looping = this.player.loop_status == "Track";
201+
switch (this.player.loop_status) {
202+
case "Track":
203+
this.loop_status = LoopStatus.TRACK;
204+
break;
205+
case "Playlist":
206+
this.loop_status = LoopStatus.PLAYLIST;
207+
break;
208+
default:
209+
this.loop_status = LoopStatus.NONE;
210+
break;
211+
}
175212
}
176213

177214
private bool update_position () {
@@ -186,6 +223,10 @@ public class Turntable.Mpris.Entry : GLib.Object {
186223
return GLib.Source.CONTINUE;
187224
}
188225

226+
private void update_shuffle_status () {
227+
this.shuffle = this.player.shuffle;
228+
}
229+
189230
private void update_controls () {
190231
if (!this.player.can_control) {
191232
this.can_go_back =

src/Utils/Settings.vala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class Turntable.Utils.Settings : GLib.Settings {
99
public bool component_cover_fit { get; set; }
1010
public bool component_tonearm { get; set; }
1111
public bool component_center_text { get; set; }
12+
public bool component_more_controls { get; set; }
1213
public bool meta_dim { get; set; }
1314
public bool mbid_required { get; set; }
1415
public bool now_playing { get; set; }
@@ -34,7 +35,8 @@ public class Turntable.Utils.Settings : GLib.Settings {
3435
"mbid-required",
3536
"component-tonearm",
3637
"component-center-text",
37-
"now-playing"
38+
"now-playing",
39+
"component-more-controls"
3840
};
3941

4042
public Settings () {

src/Views/Window.vala

Lines changed: 59 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
1313
GLib.SimpleAction cover_scaling_action;
1414
GLib.SimpleAction component_tonearm_action;
1515
GLib.SimpleAction component_center_text_action;
16+
GLib.SimpleAction component_more_controls_action;
1617
public string uuid { get; private set; }
1718

1819
~Window () {
@@ -219,7 +220,7 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
219220
prog.progress = 0;
220221
} else {
221222
#if SCROBBLING
222-
if (value == 0 && this.player != null && this.player.looping && _position > 0) {
223+
if (value == 0 && this.player != null && this.player.loop_status == Mpris.Entry.LoopStatus.TRACK && _position > 0) {
223224
this.length = this.length; // re-trigger it
224225
}
225226
#endif
@@ -251,11 +252,9 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
251252
public bool playing {
252253
get { return _playing; }
253254
set {
254-
_playing = value;
255+
_playing =
256+
mpris_controls.playing =
255257
art_pic.turntable_playing = value;
256-
button_play.icon_name = value ? "pause-large-symbolic" : "play-large-symbolic";
257-
// translators: button tooltip text
258-
button_play.tooltip_text = value ? _("Pause") : _("Play");
259258
#if SCROBBLING
260259
update_scrobbler_playing ();
261260
#endif
@@ -357,10 +356,8 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
357356
Gtk.Box non_art_box;
358357
Widgets.ProgressBin prog;
359358
Gtk.Box main_box;
360-
Gtk.Button button_play;
361-
Gtk.Button button_prev;
362-
Gtk.Button button_next;
363359
Widgets.ControlsOverlay controls_overlay;
360+
Widgets.MPRISControls mpris_controls;
364361
construct {
365362
this.uuid = GLib.Uuid.string_random ();
366363
this.icon_name = Build.DOMAIN;
@@ -416,43 +413,14 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
416413
content = main_box
417414
};
418415

419-
var box3 = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 8) {
416+
mpris_controls = new Widgets.MPRISControls () {
420417
hexpand = true,
421418
vexpand = true,
422419
halign = Gtk.Align.CENTER,
423420
valign = Gtk.Align.CENTER,
424421
};
425-
non_art_box.append (box3);
426-
427-
button_prev = new Gtk.Button.from_icon_name (is_rtl ? "skip-forward-large-symbolic" : "skip-backward-large-symbolic") {
428-
css_classes = {"circular"},
429-
halign = Gtk.Align.CENTER,
430-
valign = Gtk.Align.CENTER,
431-
// translators: button tooltip text
432-
tooltip_text = _("Previous Song")
433-
};
434-
box3.append (button_prev);
435-
436-
button_play = new Gtk.Button.from_icon_name ("play-large-symbolic") {
437-
css_classes = {"circular", "large"},
438-
halign = Gtk.Align.CENTER,
439-
valign = Gtk.Align.CENTER,
440-
tooltip_text = _("Play")
441-
};
442-
box3.append (button_play);
443-
444-
button_next = new Gtk.Button.from_icon_name (is_rtl ? "skip-backward-large-symbolic" : "skip-forward-large-symbolic") {
445-
css_classes = {"circular"},
446-
halign = Gtk.Align.CENTER,
447-
valign = Gtk.Align.CENTER,
448-
// translators: button tooltip text
449-
tooltip_text = _("Next Song")
450-
};
451-
box3.append (button_next);
452-
453-
button_next.clicked.connect (play_next);
454-
button_play.clicked.connect (play_pause);
455-
button_prev.clicked.connect (play_back);
422+
mpris_controls.commanded.connect (mpris_command_received);
423+
non_art_box.append (mpris_controls);
456424

457425
#if SCROBBLING
458426
var scrobbling_action = new GLib.SimpleAction ("open-scrobbling-setup", null);
@@ -512,6 +480,10 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
512480
component_center_text_action.change_state.connect (on_change_component_center_text);
513481
this.add_action (component_center_text_action);
514482

483+
component_more_controls_action = new GLib.SimpleAction.stateful ("component-more-controls", null, settings.component_more_controls);
484+
component_more_controls_action.change_state.connect (on_change_component_more_controls);
485+
this.add_action (component_more_controls_action);
486+
515487
component_cover_fit_action = new GLib.SimpleAction.stateful ("component-cover-fit", null, settings.component_cover_fit);
516488
component_cover_fit_action.change_state.connect (on_change_component_cover_fit);
517489
this.add_action (component_cover_fit_action);
@@ -535,6 +507,7 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
535507
settings.notify["component-client-icon"].connect (update_component_client_icon_from_settings);
536508
settings.notify["component-tonearm"].connect (update_component_tonearm_from_settings);
537509
settings.notify["component-center-text"].connect (update_component_center_text_from_settings);
510+
settings.notify["component-more-controls"].connect (update_component_more_controls_from_settings);
538511
settings.notify["component-cover-fit"].connect (update_component_cover_fit_from_settings);
539512
settings.notify["meta-dim"].connect (update_meta_dim_from_settings);
540513
settings.notify["text-size"].connect (update_text_size_from_settings);
@@ -560,16 +533,34 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
560533
if (controls_overlay.hide_overlay ()) this.focus_widget = null;
561534
}
562535

563-
private void play_next () {
564-
if (this.player != null) this.player.next ();
565-
}
566-
567-
private void play_back () {
568-
if (this.player != null) this.player.back ();
569-
}
570-
571-
private void play_pause () {
572-
if (this.player != null) this.player.play_pause ();
536+
private void mpris_command_received (Widgets.MPRISControls.Command command) {
537+
if (this.player == null || !this.player.can_control) return;
538+
539+
switch (command) {
540+
case PLAY_PAUSE:
541+
this.player.play_pause ();
542+
break;
543+
case NEXT:
544+
this.player.next ();
545+
break;
546+
case PREVIOUS:
547+
this.player.back ();
548+
break;
549+
case SHUFFLE:
550+
this.player.toggle_shuffle ();
551+
break;
552+
case LOOP_NONE:
553+
this.player.loop_none ();
554+
break;
555+
case LOOP_PLAYLIST:
556+
this.player.loop_playlist ();
557+
break;
558+
case LOOP_TRACK:
559+
this.player.loop_track ();
560+
break;
561+
default:
562+
assert_not_reached ();
563+
}
573564
}
574565

575566
private void on_mapped () {
@@ -609,6 +600,7 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
609600
update_cover_size_from_settings ();
610601
update_cover_scaling_from_settings ();
611602
update_component_center_text_from_settings ();
603+
update_component_more_controls_from_settings ();
612604
}
613605

614606
private void update_cover_scaling_from_settings () {
@@ -686,6 +678,11 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
686678
component_center_text_action.set_state (settings.component_center_text);
687679
}
688680

681+
private void update_component_more_controls_from_settings () {
682+
mpris_controls.more_controls = settings.component_more_controls;
683+
component_more_controls_action.set_state (settings.component_more_controls);
684+
}
685+
689686
private void update_component_cover_fit_from_settings () {
690687
this.art_pic.fit_cover = settings.component_cover_fit;
691688
component_cover_fit_action.set_state (settings.component_cover_fit);
@@ -738,9 +735,7 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
738735
this.length = 0;
739736

740737
this.playing =
741-
button_next.sensitive =
742-
button_prev.sensitive =
743-
button_play.sensitive = false;
738+
mpris_controls.can_control = false;
744739

745740
return;
746741
}
@@ -752,14 +747,16 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
752747
player_bindings += this.player.bind_property ("position", this, "position", GLib.BindingFlags.SYNC_CREATE);
753748
player_bindings += this.player.bind_property ("length", this, "length", GLib.BindingFlags.SYNC_CREATE);
754749
player_bindings += this.player.bind_property ("playing", this, "playing", GLib.BindingFlags.SYNC_CREATE);
755-
player_bindings += this.player.bind_property ("can-go-next", button_next, "sensitive", GLib.BindingFlags.SYNC_CREATE);
756-
player_bindings += this.player.bind_property ("can-go-back", button_prev, "sensitive", GLib.BindingFlags.SYNC_CREATE);
757-
player_bindings += this.player.bind_property ("can-control", button_play, "sensitive", GLib.BindingFlags.SYNC_CREATE);
750+
player_bindings += this.player.bind_property ("can-go-next", mpris_controls, "can-go-next", GLib.BindingFlags.SYNC_CREATE);
751+
player_bindings += this.player.bind_property ("can-go-back", mpris_controls, "can-go-back", GLib.BindingFlags.SYNC_CREATE);
752+
player_bindings += this.player.bind_property ("can-control", mpris_controls, "can-control", GLib.BindingFlags.SYNC_CREATE);
753+
player_bindings += this.player.bind_property ("shuffle", mpris_controls, "shuffle", GLib.BindingFlags.SYNC_CREATE);
754+
player_bindings += this.player.bind_property ("loop-status", mpris_controls, "loop-status", GLib.BindingFlags.SYNC_CREATE);
758755

759756
prog.client_icon = this.player.client_info_icon;
760757
prog.client_name = this.player.client_info_name;
761758

762-
button_play.grab_focus ();
759+
mpris_controls.grab_play_focus ();
763760
#if SCROBBLING
764761
update_scrobble_status ();
765762
#endif
@@ -853,6 +850,11 @@ public class Turntable.Views.Window : Adw.ApplicationWindow {
853850
settings.component_center_text = value.get_boolean ();
854851
}
855852

853+
private void on_change_component_more_controls (GLib.SimpleAction action, GLib.Variant? value) {
854+
if (value == null) return;
855+
settings.component_more_controls = value.get_boolean ();
856+
}
857+
856858
private void on_change_component_cover_fit (GLib.SimpleAction action, GLib.Variant? value) {
857859
if (value == null) return;
858860
settings.component_cover_fit = value.get_boolean ();

src/Widgets/ControlsOverlay.vala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ public class Turntable.Widgets.ControlsOverlay : Adw.Bin {
145145
component_submenu_model.append (_("Fit Art on Cover"), "win.component-cover-fit");
146146
// translators: whether to extract the colors of the cover and use them in UI elements (like Amberol or Material You)
147147
component_submenu_model.append (_("Extract Cover Colors"), "win.component-extract-colors");
148+
// translators: whether to show the shuffle and loop buttons
149+
component_submenu_model.append (_("More Player Controls"), "win.component-more-controls");
148150
// translators: whether to show a turntable tonearm in turntable styled cover art; tonearm is the 'arm' part of the turntable,
149151
// you may translate it as 'arm' (mechanical part)
150152
component_submenu_model.append (_("Tonearm"), "win.component-tonearm");

0 commit comments

Comments
 (0)