From e18b8ca100f6128214afcad21637c005dddf5839 Mon Sep 17 00:00:00 2001 From: Joshua Strobl Date: Tue, 6 Apr 2021 15:35:01 +0300 Subject: [PATCH] Implement GNOME Settings Daemon MediaKeys handling. Clean up some warnings. --- src/koto-playerbar.c | 9 -- src/main.c | 4 +- src/meson.build | 1 + src/playback/media-keys.c | 174 ++++++++++++++++++++++++++++++++++++++ src/playback/media-keys.h | 32 +++++++ 5 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 src/playback/media-keys.c create mode 100644 src/playback/media-keys.h diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c index a3c3d94..7cca72c 100644 --- a/src/koto-playerbar.c +++ b/src/koto-playerbar.c @@ -88,10 +88,6 @@ static void koto_playerbar_constructed(GObject *obj) { gtk_range_set_increments(GTK_RANGE(self->progress_bar), 1, 1); gtk_range_set_round_digits(GTK_RANGE(self->progress_bar), 1); - GtkEventController *scroll_controller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_HORIZONTAL); - g_signal_connect(scroll_controller, "scroll-begin", G_CALLBACK(koto_playerbar_handle_progressbar_scroll_begin), self); - gtk_widget_add_controller(GTK_WIDGET(self->progress_bar), scroll_controller); - GtkGesture *press_controller = gtk_gesture_click_new(); // Create a new GtkGestureLongPress gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(press_controller), 1); // Set to left click @@ -241,7 +237,6 @@ void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) { if (GTK_IS_VOLUME_BUTTON(bar->volume_button)) { GtkAdjustment *granular_volume_change = gtk_adjustment_new(0.5, 0, 1.0, 0.02, 0.02, 0.02); g_object_set(bar->volume_button, "use-symbolic", TRUE, NULL); - gtk_range_set_round_digits(GTK_RANGE(bar->volume_button), FALSE); gtk_scale_button_set_adjustment(GTK_SCALE_BUTTON(bar->volume_button), granular_volume_change); // Set our adjustment gtk_box_append(GTK_BOX(bar->secondary_controls_section), bar->volume_button); @@ -290,10 +285,6 @@ void koto_playerbar_handle_is_paused(KotoPlaybackEngine *engine, gpointer user_d koto_button_show_image(bar->play_pause_button, FALSE); // Set to FALSE to show play as the next action } -void koto_playerbar_handle_progressbar_scroll_begin(GtkEventControllerScroll *controller, gpointer data){ - (void) controller; -} - void koto_playerbar_handle_progressbar_gesture_begin(GtkGesture *gesture, GdkEventSequence *seq, gpointer data) { (void) gesture; (void) seq; KotoPlayerBar *bar = data; diff --git a/src/main.c b/src/main.c index bdc464b..f6f28ba 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,7 @@ #include #include "db/cartographer.h" #include "db/db.h" +#include "playback/media-keys.h" #include "playback/mimes.h" #include "playback/mpris.h" @@ -43,6 +44,8 @@ static void on_activate (GtkApplication *app) { main_window = gtk_application_get_active_window (app); if (main_window == NULL) { main_window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL); + setup_mpris_interfaces(); // Set up our MPRIS interfaces + setup_mediakeys_interface(); // Set up our media key support } gtk_window_present(main_window); @@ -72,7 +75,6 @@ int main (int argc, char *argv[]) { koto_maps = koto_cartographer_new(); // Create our new cartographer and their collection of maps open_db(); // Open our database - setup_mpris_interfaces(); // Set up our MPRIS interfaces app = gtk_application_new ("com.github.joshstrobl.koto", G_APPLICATION_FLAGS_NONE); g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); diff --git a/src/meson.build b/src/meson.build index 957b92b..e7374b6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,6 +12,7 @@ koto_sources = [ 'pages/music/disc-view.c', 'pages/music/music-local.c', 'playback/engine.c', + 'playback/media-keys.c', 'playback/mimes.c', 'playback/mpris.c', 'playlist/current.c', diff --git a/src/playback/media-keys.c b/src/playback/media-keys.c new file mode 100644 index 0000000..67fd835 --- /dev/null +++ b/src/playback/media-keys.c @@ -0,0 +1,174 @@ +/* media-keys.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "engine.h" +#include "media-keys.h" + +extern GtkWindow *main_window; +extern KotoPlaybackEngine *playback_engine; + +GDBusConnection *media_keys_dbus_conn = NULL; +GDBusProxy *media_keys_proxy = NULL; +GDBusNodeInfo *media_keys_introspection_data = NULL; + +static const gchar introspection_xml[] = +"" +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +""; + +void grab_media_keys() { + if (media_keys_proxy == NULL) { // No connection + return; + } + + g_dbus_proxy_call( + media_keys_proxy, + "GrabMediaPlayerKeys", + g_variant_new("(su)", "com.github.joshstrobl.koto", 0), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + handle_media_keys_async_done, + NULL + ); +} + +void handle_media_keys_async_done(GObject *source_object, GAsyncResult *res, gpointer user_data) { + (void) user_data; + g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), res, NULL); // Ensure we finish our call +} + +void handle_media_keys_signal(GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { + (void) proxy; (void) sender_name; (void) user_data; + if (g_strcmp0(signal_name, "MediaPlayerKeyPressed") != 0) { // Not MediaPlayerKeyPressed + return; + } + + gchar *application_name = NULL; + gchar *key = NULL; + + g_variant_get(parameters, "(ss)", &application_name, &key); + + if (g_strcmp0(application_name, "com.github.joshstrobl.koto") != 0) { // Not for Koto + return; + } + + if (g_strcmp0(key, "Play") == 0) { + koto_playback_engine_play(playback_engine); + } else if (g_strcmp0(key, "Pause") == 0) { + koto_playback_engine_pause(playback_engine); + } else if (g_strcmp0(key, "Stop") == 0) { + koto_playback_engine_stop(playback_engine); + } else if (g_strcmp0(key, "Previous") == 0) { + koto_playback_engine_backwards(playback_engine); + } else if (g_strcmp0(key, "Next") == 0) { + koto_playback_engine_forwards(playback_engine); + } else if (g_strcmp0(key, "Repeat") == 0) { + koto_playback_engine_toggle_track_repeat(playback_engine); + } else if (g_strcmp0(key, "Shuffle") == 0) { + koto_playback_engine_toggle_track_shuffle(playback_engine); + } +} + +void handle_window_enter(GtkEventControllerFocus *controller, gpointer user_data) { + grab_media_keys(); // Grab our media keys +} + +void handle_window_leave(GtkEventControllerFocus *controller, gpointer user_data) { + release_media_keys(); // Release our media keys +} + +void release_media_keys() { + if (media_keys_dbus_conn == NULL) { // No connection + return; + } + + GVariant *params = g_variant_new_string(g_strdup("com.github.joshstrobl.koto")); + + g_dbus_proxy_call( + media_keys_proxy, + "ReleaseMediaPlayerKeys", + params, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + handle_media_keys_async_done, + NULL + ); +} + +void setup_mediakeys_interface() { + media_keys_introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL); + g_assert(media_keys_introspection_data != NULL); + + GDBusConnection *bus; + GError *error = NULL; + + bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); + + if (bus == NULL) { // Failed to get session bus + g_printerr("Failed to get our session bus: %s\n", error->message); + g_error_free(error); + return; + } + + media_keys_proxy = g_dbus_proxy_new_sync( + bus, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.gnome.SettingsDaemon.MediaKeys", + "/org/gnome/SettingsDaemon/MediaKeys", + "org.gnome.SettingsDaemon.MediaKeys", + NULL, + &error + ); + + if (media_keys_proxy == NULL) { + g_printerr("Failed to get a proxy to GNOME Settings Daemon Media-Keys: %s\n", error->message); + g_error_free(error); + return; + } + + g_signal_connect_object( + media_keys_proxy, + "g-signal", + G_CALLBACK(handle_media_keys_signal), + NULL, + 0 + ); + + GtkEventController *focus_controller = gtk_event_controller_focus_new(); // Create a new focus controller + g_signal_connect(focus_controller, "enter", G_CALLBACK(handle_window_enter), NULL); + g_signal_connect(focus_controller, "leave", G_CALLBACK(handle_window_leave), NULL); + gtk_widget_add_controller(GTK_WIDGET(main_window), focus_controller); +} diff --git a/src/playback/media-keys.h b/src/playback/media-keys.h new file mode 100644 index 0000000..2549d99 --- /dev/null +++ b/src/playback/media-keys.h @@ -0,0 +1,32 @@ +/* media-keys.h + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +G_BEGIN_DECLS + +void grab_media_keys(); +void handle_media_keys_async_done(GObject *source_object, GAsyncResult *res, gpointer user_data); +void handle_media_keys_signal(GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, gpointer user_data); +void handle_window_enter(GtkEventControllerFocus *controller, gpointer user_data); +void handle_window_leave(GtkEventControllerFocus *controller, gpointer user_data); +void release_media_keys(); +void setup_mediakeys_interface(); + +G_END_DECLS