summaryrefslogtreecommitdiff
path: root/src/services
diff options
context:
space:
mode:
authorHannes Schulze <contact@haschu.me>2026-04-26 12:51:39 +0200
committerHannes Schulze <contact@haschu.me>2026-04-26 12:51:39 +0200
commitc783a6ca1cb13126f2ff06a92296994b446c8fed (patch)
treeb161316e57fd0a5d38f564400968799a805c9d80 /src/services
parent04f6355bdf1696f6afce46a01a03058c12673496 (diff)
subscribe to sway events
Diffstat (limited to 'src/services')
-rw-r--r--src/services/workspaces/hyprland-workspace-service.c1
-rw-r--r--src/services/workspaces/sway-workspace-service.c339
2 files changed, 298 insertions, 42 deletions
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,41 +103,106 @@ 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:
//
// Header in an i3wm/sway protocol message.
@@ -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 );
@@ -549,10 +622,149 @@ FoobarWorkspaceService* foobar_sway_workspace_service_new( void )
}
// ---------------------------------------------------------------------------------------------------------------------
+// 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.
//
void foobar_sway_workspace_service_initialize( FoobarSwayWorkspaceService* self )
@@ -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 );
+ }
+}