From c783a6ca1cb13126f2ff06a92296994b446c8fed Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Sun, 26 Apr 2026 12:51:39 +0200 Subject: subscribe to sway events --- .../workspaces/hyprland-workspace-service.c | 1 - src/services/workspaces/sway-workspace-service.c | 339 ++++++++++++++++++--- 2 files changed, 298 insertions(+), 42 deletions(-) (limited to 'src/services') diff --git a/src/services/workspaces/hyprland-workspace-service.c b/src/services/workspaces/hyprland-workspace-service.c index 9a64c49..f7a7395 100644 --- a/src/services/workspaces/hyprland-workspace-service.c +++ b/src/services/workspaces/hyprland-workspace-service.c @@ -137,7 +137,6 @@ static gint foobar_hyprland_workspace_service_sort_func gconstpointer item_b, gpointer userdata ); - G_DEFINE_TYPE_WITH_CODE( FoobarHyprlandWorkspaceService, foobar_hyprland_workspace_service, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE( FOOBAR_TYPE_WORKSPACE_SERVICE, foobar_hyprland_workspace_service_interface_init ) ); diff --git a/src/services/workspaces/sway-workspace-service.c b/src/services/workspaces/sway-workspace-service.c index ebb0167..e031db8 100644 --- a/src/services/workspaces/sway-workspace-service.c +++ b/src/services/workspaces/sway-workspace-service.c @@ -10,11 +10,15 @@ static const gchar MAGIC_STRING[] = "i3-ipc"; -#define MESSAGE_TYPE_COMMAND 0 -#define MESSAGE_TYPE_WORKSPACES 1 +#define MESSAGE_TYPE_COMMAND 0x0 +#define MESSAGE_TYPE_WORKSPACES 0x1 +#define MESSAGE_TYPE_SUBSCRIBE 0x2 +#define EVENT_TYPE_WORKSPACE 0x80000000 -typedef struct _MessageHeader MessageHeader; -typedef enum _FoobarSwayError FoobarSwayError; +typedef struct _EventData EventData; +typedef struct _WorkspaceEventHandler WorkspaceEventHandler; +typedef struct _MessageHeader MessageHeader; +typedef enum _FoobarSwayError FoobarSwayError; // // FoobarSwayWorkspace: @@ -89,6 +93,8 @@ struct _FoobarSwayWorkspaceService GListStore* workspaces; GtkSortListModel* sorted_workspaces; gchar* socket_path; + GCancellable* event_cancellable; + GThread* event_thread; }; enum @@ -97,40 +103,105 @@ enum N_PROPS, }; -static void foobar_sway_workspace_service_class_init ( FoobarSwayWorkspaceServiceClass* klass ); -static void foobar_sway_workspace_service_interface_init ( FoobarWorkspaceServiceInterface* iface ); -static void foobar_sway_workspace_service_init ( FoobarSwayWorkspaceService* self ); -static void foobar_sway_workspace_service_get_property ( GObject* object, - guint prop_id, - GValue* value, - GParamSpec* pspec ); -static void foobar_sway_workspace_service_dispose ( GObject* object ); -static void foobar_sway_workspace_service_finalize ( GObject* object ); -static GListModel* foobar_sway_workspace_service_get_workspaces ( FoobarWorkspaceService* service ); -static gboolean foobar_sway_workspace_service_is_available ( FoobarWorkspaceService* service ); -static void foobar_sway_workspace_service_initialize ( FoobarSwayWorkspaceService* self ); -static JsonNode* foobar_sway_workspace_service_send_request ( FoobarSwayWorkspaceService* self, - guint type, - gchar const* payload, - GError** error ); -static gboolean foobar_sway_workspace_service_write ( GSocket* socket, - guint32 type, - gchar const* payload, - GCancellable* cancellable, - GError** out_error ); -static JsonNode* foobar_sway_workspace_service_read ( GSocket* socket, - guint32* out_type, - GCancellable* cancellable, - GError** out_error ); -static gchar* foobar_sway_workspace_service_get_socket_path ( void ); -static gint foobar_sway_workspace_service_sort_func ( gconstpointer item_a, - gconstpointer item_b, - gpointer userdata ); +static void foobar_sway_workspace_service_class_init ( FoobarSwayWorkspaceServiceClass* klass ); +static void foobar_sway_workspace_service_interface_init ( FoobarWorkspaceServiceInterface* iface ); +static void foobar_sway_workspace_service_init ( FoobarSwayWorkspaceService* self ); +static void foobar_sway_workspace_service_get_property ( GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ); +static void foobar_sway_workspace_service_dispose ( GObject* object ); +static void foobar_sway_workspace_service_finalize ( GObject* object ); +static GListModel* foobar_sway_workspace_service_get_workspaces ( FoobarWorkspaceService* service ); +static gboolean foobar_sway_workspace_service_is_available ( FoobarWorkspaceService* service ); +static void foobar_sway_workspace_service_handle_workspace_init ( FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ); +static void foobar_sway_workspace_service_handle_workspace_empty ( FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ); +static void foobar_sway_workspace_service_handle_workspace_focus ( FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ); +static void foobar_sway_workspace_service_handle_workspace_move ( FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ); +static void foobar_sway_workspace_service_handle_workspace_rename( FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ); +static void foobar_sway_workspace_service_handle_workspace_urgent( FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ); +static void foobar_sway_workspace_service_handle_workspace_reload( FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ); +static gpointer foobar_sway_workspace_service_event_thread_func ( gpointer userdata ); +static void foobar_sway_workspace_service_initialize ( FoobarSwayWorkspaceService* self ); +static gboolean foobar_sway_workspace_service_handle_event ( EventData* data ); +static JsonNode* foobar_sway_workspace_service_send_request ( FoobarSwayWorkspaceService* self, + guint type, + gchar const* payload, + GError** error ); +static gboolean foobar_sway_workspace_service_write ( GSocket* socket, + guint32 type, + gchar const* payload, + GCancellable* cancellable, + GError** out_error ); +static JsonNode* foobar_sway_workspace_service_read ( GSocket* socket, + guint32* out_type, + GCancellable* cancellable, + GError** out_error ); +static gchar* foobar_sway_workspace_service_get_socket_path ( void ); +static gint foobar_sway_workspace_service_sort_func ( gconstpointer item_a, + gconstpointer item_b, + gpointer userdata ); G_DEFINE_TYPE_WITH_CODE( FoobarSwayWorkspaceService, foobar_sway_workspace_service, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE( FOOBAR_TYPE_WORKSPACE_SERVICE, foobar_sway_workspace_service_interface_init ) ); +// +// EventData: +// +// Custom userdata passed to event handlers in a single pointer. +// + +struct _EventData +{ + FoobarSwayWorkspaceService* service; + JsonNode* payload; +}; + +static EventData* event_data_new ( FoobarSwayWorkspaceService* service, + JsonNode* payload ); +static void event_data_free( EventData* self ); + +// +// WorkspaceEventHandler: +// +// Description for the handler of a particular "workspace" event change. +// + +struct _WorkspaceEventHandler +{ + gchar const* change; + void ( *fn )( FoobarSwayWorkspaceService* service, JsonObject* current, JsonObject* old ); +}; + +// +// Static list of registered "workspace" event handlers. +// +static WorkspaceEventHandler const workspace_event_handlers[] = + { + { .change = "init", .fn = foobar_sway_workspace_service_handle_workspace_init }, + { .change = "empty", .fn = foobar_sway_workspace_service_handle_workspace_empty }, + { .change = "focus", .fn = foobar_sway_workspace_service_handle_workspace_focus }, + { .change = "move", .fn = foobar_sway_workspace_service_handle_workspace_move }, + { .change = "rename", .fn = foobar_sway_workspace_service_handle_workspace_rename }, + { .change = "urgent", .fn = foobar_sway_workspace_service_handle_workspace_urgent }, + { .change = "reload", .fn = foobar_sway_workspace_service_handle_workspace_reload }, + }; + // // MessageHeader: // @@ -456,13 +527,10 @@ void foobar_sway_workspace_service_init( FoobarSwayWorkspaceService* self ) return; } - foobar_sway_workspace_service_initialize( self ); + self->event_cancellable = g_cancellable_new( ); + self->event_thread = g_thread_new( "workspace-event-listener", foobar_sway_workspace_service_event_thread_func, self ); - (void)foobar_sway_workspace_set_id; - (void)foobar_sway_workspace_set_name; - (void)foobar_sway_workspace_set_monitor; - (void)foobar_sway_workspace_set_flags; - (void)foobar_sway_workspace_new; + foobar_sway_workspace_service_initialize( self ); } // @@ -493,7 +561,12 @@ void foobar_sway_workspace_service_dispose( GObject* object ) { FoobarSwayWorkspaceService* self = (FoobarSwayWorkspaceService*)object; - (void)self; + if ( self->event_thread ) + { + g_cancellable_cancel( self->event_cancellable ); + g_thread_join( self->event_thread ); + self->event_thread = NULL; + } G_OBJECT_CLASS( foobar_sway_workspace_service_parent_class )->dispose( object ); } @@ -510,7 +583,7 @@ void foobar_sway_workspace_service_finalize( GObject* object ) FoobarSwayWorkspace* workspace = g_list_model_get_item( G_LIST_MODEL( self->workspaces ), i ); workspace->service = NULL; } - // g_clear_object( &self->event_cancellable ); + g_clear_object( &self->event_cancellable ); g_clear_object( &self->sorted_workspaces ); g_clear_object( &self->workspaces ); g_clear_pointer( &self->socket_path, g_free ); @@ -548,10 +621,149 @@ FoobarWorkspaceService* foobar_sway_workspace_service_new( void ) return g_object_new( FOOBAR_TYPE_SWAY_WORKSPACE_SERVICE, NULL ); } +// --------------------------------------------------------------------------------------------------------------------- +// Event Handlers +// --------------------------------------------------------------------------------------------------------------------- + +void foobar_sway_workspace_service_handle_workspace_init( + FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ) +{ + g_print( "[init] %p %p\n", current, old ); +} + +void foobar_sway_workspace_service_handle_workspace_empty( + FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ) +{ + g_print( "[empty] %p %p\n", current, old ); +} + +void foobar_sway_workspace_service_handle_workspace_focus( + FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ) +{ + g_print( "[focus] %p %p\n", current, old ); +} + +void foobar_sway_workspace_service_handle_workspace_move( + FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ) +{ + g_print( "[move] %p %p\n", current, old ); +} + +void foobar_sway_workspace_service_handle_workspace_rename( + FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ) +{ + g_print( "[rename] %p %p\n", current, old ); +} + +void foobar_sway_workspace_service_handle_workspace_urgent( + FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ) +{ + g_print( "[urgent] %p %p\n", current, old ); +} + +void foobar_sway_workspace_service_handle_workspace_reload( + FoobarSwayWorkspaceService* service, + JsonObject* current, + JsonObject* old ) +{ + g_print( "[reload] %p %p\n", current, old ); +} + // --------------------------------------------------------------------------------------------------------------------- // Helper Methods // --------------------------------------------------------------------------------------------------------------------- +// +// Implementation of the event thread. +// +// This thread creates a new socket and sends a SUBSCRIBE request to receive workspace changes. These are then +// submitted through their registered handlers (if any) to the main loop. +// +gpointer foobar_sway_workspace_service_event_thread_func( gpointer userdata ) +{ + FoobarSwayWorkspaceService* self = (FoobarSwayWorkspaceService*)userdata; + + g_autoptr( GError ) error = NULL; + g_autoptr( GSocket ) sock = g_socket_new( + G_SOCKET_FAMILY_UNIX, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error ); + if ( !sock ) + { + g_warning( "Unable to create socket for Sway events: %s", error->message ); + return NULL; + } + + g_autoptr( GSocketAddress ) addr = g_unix_socket_address_new( self->socket_path ); + if ( !g_socket_connect( sock, addr, self->event_cancellable, &error ) && + !g_cancellable_is_cancelled( self->event_cancellable ) ) + { + g_warning( "Unable to connect socket to Sway: %s", error->message ); + return NULL; + } + + if ( !foobar_sway_workspace_service_write( sock, MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\"]", self->event_cancellable, &error ) && + !g_cancellable_is_cancelled( self->event_cancellable ) ) + { + g_warning( "Unable to subscribe to Sway events: %s", error->message ); + return NULL; + } + + while ( !g_cancellable_is_cancelled( self->event_cancellable ) ) + { + // Synchronously receive data from the socket, but support cancellation using Gio. + + guint msg_type; + g_autoptr(JsonNode) msg = foobar_sway_workspace_service_read( sock, &msg_type, self->event_cancellable, &error ); + if ( !msg ) + { + if ( !g_cancellable_is_cancelled( self->event_cancellable ) ) + { + g_warning( "Unable to receive message from Sway: %s", error->message ); + } + break; + } + + if ( msg_type == MESSAGE_TYPE_SUBSCRIBE ) + { + // This is the response to our initial SUBSCRIBE request. + + JsonObject* status_object = json_node_get_object( msg ); + gboolean success = json_object_get_boolean_member( status_object, "success" ); + if ( !success ) + { + g_warning( "Failed to subscribe to Sway events." ); + break; + } + } + else if ( msg_type == EVENT_TYPE_WORKSPACE ) + { + // This is an event, dispatch it to the main loop. + + g_idle_add_full( + G_PRIORITY_DEFAULT, + G_SOURCE_FUNC( foobar_sway_workspace_service_handle_event ), + event_data_new( self, msg ), + (GDestroyNotify)event_data_free ); + } + } + + return NULL; +} + // // Refresh the list of workspaces by actively sending a request to Sway. // @@ -613,6 +825,26 @@ void foobar_sway_workspace_service_initialize( FoobarSwayWorkspaceService* self } } +gboolean foobar_sway_workspace_service_handle_event( EventData* data ) +{ + JsonObject* event_object = json_node_get_object( data->payload ); + gchar const* change = json_object_get_string_member( event_object, "change" ); + JsonObject* current = json_object_get_object_member( event_object, "current" ); + JsonObject* old = json_object_get_object_member( event_object, "old" ); + + for ( gsize i = 0; i < G_N_ELEMENTS( workspace_event_handlers ); ++i ) + { + WorkspaceEventHandler handler = workspace_event_handlers[i]; + if ( !strcmp( change, handler.change ) ) + { + handler.fn( data->service, current, old ); + break; + } + } + + return G_SOURCE_REMOVE; +} + // // Synchronously send a command to Sway, returning the entire response. // @@ -786,3 +1018,28 @@ gint foobar_sway_workspace_service_sort_func( // both named return g_strcmp0( name_a, name_b ); } + +// +// Create a new EventData structure. +// +static EventData* event_data_new( + FoobarSwayWorkspaceService* service, + JsonNode* payload ) +{ + EventData* self = g_new0( EventData, 1 ); + self->service = g_object_ref( service ); + self->payload = json_node_ref( payload ); + return self; +} + +// +// Destroy an EventData structure. +// +static void event_data_free( EventData* self ) +{ + if ( self ) + { + g_object_unref( self->service ); + json_node_unref( self->payload ); + } +} -- cgit v1.3.1