#include "services/workspaces/sway-workspace-service.h" #include "gio/gio.h" #include "glib-object.h" #include "glib.h" #include "services/workspace-service.h" #include #include #include #include static const gchar MAGIC_STRING[] = "i3-ipc"; #define MESSAGE_TYPE_COMMAND 0x0 #define MESSAGE_TYPE_WORKSPACES 0x1 #define MESSAGE_TYPE_SUBSCRIBE 0x2 #define EVENT_TYPE_WORKSPACE 0x80000000 typedef struct _EventData EventData; typedef struct _WorkspaceEventHandler WorkspaceEventHandler; typedef struct _MessageHeader MessageHeader; typedef enum _FoobarSwayError FoobarSwayError; // // FoobarSwayWorkspace: // // Representation of a workspace in Sway. // #define FOOBAR_TYPE_SWAY_WORKSPACE foobar_sway_workspace_get_type( ) G_DECLARE_FINAL_TYPE( FoobarSwayWorkspace, foobar_sway_workspace, FOOBAR, SWAY_WORKSPACE, GObject ) struct _FoobarSwayWorkspace { GObject parent_instance; FoobarSwayWorkspaceService* service; gint64 id; gint64 num; gchar* name; gchar* monitor; FoobarWorkspaceFlags flags; }; enum { WORKSPACE_PROP_ID = 1, WORKSPACE_PROP_NAME, WORKSPACE_PROP_MONITOR, WORKSPACE_PROP_FLAGS, N_WORKSPACE_PROPS, }; static void foobar_sway_workspace_class_init ( FoobarSwayWorkspaceClass* klass ); static void foobar_sway_workspace_interface_init( FoobarWorkspaceInterface* iface ); static void foobar_sway_workspace_init ( FoobarSwayWorkspace* self ); static void foobar_sway_workspace_get_property ( GObject* object, guint prop_id, GValue* value, GParamSpec* pspec ); static void foobar_sway_workspace_finalize ( GObject* object ); static FoobarSwayWorkspace* foobar_sway_workspace_new ( FoobarSwayWorkspaceService* service ); static gint64 foobar_sway_workspace_get_id ( FoobarWorkspace* self ); static gint64 foobar_sway_workspace_get_num ( FoobarSwayWorkspace* self ); static gchar const* foobar_sway_workspace_get_name ( FoobarWorkspace* workspace ); static gchar const* foobar_sway_workspace_get_monitor ( FoobarWorkspace* workspace ); static FoobarWorkspaceFlags foobar_sway_workspace_get_flags ( FoobarWorkspace* workspace ); static void foobar_sway_workspace_activate ( FoobarWorkspace* workspace ); static void foobar_sway_workspace_set_id ( FoobarSwayWorkspace* self, gint64 value ); static void foobar_sway_workspace_set_num ( FoobarSwayWorkspace* self, gint64 value ); static void foobar_sway_workspace_set_name ( FoobarSwayWorkspace* self, gchar const* value ); static void foobar_sway_workspace_set_monitor ( FoobarSwayWorkspace* self, gchar const* value ); static void foobar_sway_workspace_set_flags ( FoobarSwayWorkspace* self, FoobarWorkspaceFlags value ); G_DEFINE_TYPE_WITH_CODE( FoobarSwayWorkspace, foobar_sway_workspace, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE( FOOBAR_TYPE_WORKSPACE, foobar_sway_workspace_interface_init ) ); // // FoobarSwayWorkspaceService: // // Service monitoring the workspaces provided by the window manager. This is implemented by communicating directly with // Sway through its IPC socket. // struct _FoobarSwayWorkspaceService { GObject parent_instance; GListStore* workspaces; GtkSortListModel* sorted_workspaces; gchar* socket_path; GCancellable* event_cancellable; GThread* event_thread; }; enum { PROP_WORKSPACES = 1, 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_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. // struct __attribute__((__packed__)) _MessageHeader { gchar magic_string[sizeof(MAGIC_STRING) - 1]; guint32 message_length; guint32 message_type; }; // // FoobarSwayError: // // Custom error domain for sway-related errors. // enum _FoobarSwayError { FOOBAR_SWAY_ERROR_INVALID_MAGIC_STRING, FOOBAR_SWAY_ERROR_MISMATCHING_TYPE, }; G_DEFINE_QUARK( foobar-sway-error, foobar_sway_error ); #define FOOBAR_SWAY_ERROR ( foobar_sway_error_quark( ) ) // --------------------------------------------------------------------------------------------------------------------- // Workspace // --------------------------------------------------------------------------------------------------------------------- // // Static initialization for workspaces. // void foobar_sway_workspace_class_init( FoobarSwayWorkspaceClass* klass ) { GObjectClass* object_klass = G_OBJECT_CLASS( klass ); object_klass->get_property = foobar_sway_workspace_get_property; object_klass->finalize = foobar_sway_workspace_finalize; g_object_class_override_property( object_klass, WORKSPACE_PROP_ID, "id" ); g_object_class_override_property( object_klass, WORKSPACE_PROP_NAME, "name" ); g_object_class_override_property( object_klass, WORKSPACE_PROP_MONITOR, "monitor" ); g_object_class_override_property( object_klass, WORKSPACE_PROP_FLAGS, "flags" ); } // // Interface initialization for workspaces. // void foobar_sway_workspace_interface_init( FoobarWorkspaceInterface* iface ) { iface->get_id = foobar_sway_workspace_get_id; iface->get_name = foobar_sway_workspace_get_name; iface->get_monitor = foobar_sway_workspace_get_monitor; iface->get_flags = foobar_sway_workspace_get_flags; iface->activate = foobar_sway_workspace_activate; } // // Instance initialization for workspaces. // void foobar_sway_workspace_init( FoobarSwayWorkspace* self ) { (void)self; } // // Property getter implementation, mapping a property id to a method. // void foobar_sway_workspace_get_property( GObject* object, guint prop_id, GValue* value, GParamSpec* pspec ) { FoobarSwayWorkspace* self = (FoobarSwayWorkspace*)object; switch ( prop_id ) { case WORKSPACE_PROP_ID: g_value_set_int64( value, foobar_workspace_get_id( FOOBAR_WORKSPACE( self ) ) ); break; case WORKSPACE_PROP_NAME: g_value_set_string( value, foobar_workspace_get_name( FOOBAR_WORKSPACE( self ) ) ); break; case WORKSPACE_PROP_MONITOR: g_value_set_string( value, foobar_workspace_get_monitor( FOOBAR_WORKSPACE( self ) ) ); break; case WORKSPACE_PROP_FLAGS: g_value_set_flags( value, foobar_workspace_get_flags( FOOBAR_WORKSPACE( self ) ) ); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); break; } } // // Instance cleanup for workspaces. // void foobar_sway_workspace_finalize( GObject* object ) { FoobarSwayWorkspace* self = (FoobarSwayWorkspace*)object; g_clear_pointer( &self->name, g_free ); g_clear_pointer( &self->monitor, g_free ); G_OBJECT_CLASS( foobar_sway_workspace_parent_class )->finalize( object ); } // // Create a new workspace that is owned by the given service (captured as an unowned reference). // FoobarSwayWorkspace* foobar_sway_workspace_new( FoobarSwayWorkspaceService* service ) { FoobarSwayWorkspace* self = g_object_new( FOOBAR_TYPE_SWAY_WORKSPACE, NULL ); self->service = service; return self; } // // Numeric ID of the workspace. // gint64 foobar_sway_workspace_get_id( FoobarWorkspace* workspace ) { FoobarSwayWorkspace* self = (FoobarSwayWorkspace*)workspace; return self->id; } // // Logical number of the workspace. // gint64 foobar_sway_workspace_get_num( FoobarSwayWorkspace* self ) { g_return_val_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ), -1 ); return self->num; } // // Optional human-readable workspace name. // gchar const* foobar_sway_workspace_get_name( FoobarWorkspace* workspace ) { FoobarSwayWorkspace* self = (FoobarSwayWorkspace*)workspace; return self->name; } // // Connector ID of the workspace's monitor. // gchar const* foobar_sway_workspace_get_monitor( FoobarWorkspace* workspace ) { FoobarSwayWorkspace* self = (FoobarSwayWorkspace*)workspace; return self->monitor; } // // Flags indicating the current state of the workspace. // FoobarWorkspaceFlags foobar_sway_workspace_get_flags( FoobarWorkspace* workspace ) { FoobarSwayWorkspace* self = (FoobarSwayWorkspace*)workspace; return self->flags; } // // Let this workspace become the active one. // void foobar_sway_workspace_activate( FoobarWorkspace* workspace ) { FoobarSwayWorkspace* self = (FoobarSwayWorkspace*)workspace; if ( !self->service ) { return; } gchar const* name = foobar_workspace_get_name( FOOBAR_WORKSPACE( self ) ); g_autofree gchar* request = g_strdup_printf( "workspace --no-auto-back-and-forth \"%s\"", name ); g_autoptr( GError ) error = NULL; g_autoptr( JsonNode ) response = foobar_sway_workspace_service_send_request( self->service, MESSAGE_TYPE_COMMAND, request, &error ); if ( !response ) { g_warning( "Unable to send workspace activation command: %s", error->message ); return; } JsonArray* array = json_node_get_array( response ); for ( guint i = 0; i < json_array_get_length( array ); ++i ) { JsonObject* status_object = json_array_get_object_element( array, i ); gboolean success = json_object_get_boolean_member( status_object, "success" ); if ( !success ) { g_warning( "Could not activate workspace." ); return; } } } // // Numeric ID of the workspace. // void foobar_sway_workspace_set_id( FoobarSwayWorkspace* self, gint64 value ) { g_return_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ) ); if ( self->id != value ) { self->id = value; g_object_notify( G_OBJECT( self ), "id" ); } } // // Logical number of the workspace. // void foobar_sway_workspace_set_num( FoobarSwayWorkspace* self, gint64 value ) { g_return_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ) ); self->num = value; } // // Optional human-readable workspace name. // void foobar_sway_workspace_set_name( FoobarSwayWorkspace* self, gchar const* value ) { g_return_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ) ); if ( g_strcmp0( self->name, value ) ) { g_clear_pointer( &self->name, g_free ); self->name = g_strdup( value ); g_object_notify( G_OBJECT( self ), "name" ); } } // // Connector ID of the workspace's monitor. // void foobar_sway_workspace_set_monitor( FoobarSwayWorkspace* self, gchar const* value ) { g_return_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ) ); if ( g_strcmp0( self->monitor, value ) ) { g_clear_pointer( &self->monitor, g_free ); self->monitor = g_strdup( value ); g_object_notify( G_OBJECT( self ), "monitor" ); } } // // Flags indicating the current state of the workspace. // void foobar_sway_workspace_set_flags( FoobarSwayWorkspace* self, FoobarWorkspaceFlags value ) { g_return_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ) ); if ( self->flags != value ) { self->flags = value; g_object_notify( G_OBJECT( self ), "flags" ); } } // --------------------------------------------------------------------------------------------------------------------- // Service Implementation // --------------------------------------------------------------------------------------------------------------------- // // Static initialization for the workspace service. // void foobar_sway_workspace_service_class_init( FoobarSwayWorkspaceServiceClass* klass ) { GObjectClass* object_klass = G_OBJECT_CLASS( klass ); object_klass->get_property = foobar_sway_workspace_service_get_property; object_klass->dispose = foobar_sway_workspace_service_dispose; object_klass->finalize = foobar_sway_workspace_service_finalize; g_object_class_override_property( object_klass, PROP_WORKSPACES, "workspaces" ); } // // Interface initialization for the workspace service. // void foobar_sway_workspace_service_interface_init( FoobarWorkspaceServiceInterface* iface ) { iface->get_workspaces = foobar_sway_workspace_service_get_workspaces; iface->is_available = foobar_sway_workspace_service_is_available; } // // Instance initialization for the workspace service. // void foobar_sway_workspace_service_init( FoobarSwayWorkspaceService* self ) { self->workspaces = g_list_store_new( FOOBAR_TYPE_WORKSPACE ); GtkCustomSorter* sorter = gtk_custom_sorter_new( foobar_sway_workspace_service_sort_func, NULL, NULL ); self->sorted_workspaces = gtk_sort_list_model_new( G_LIST_MODEL( g_object_ref( self->workspaces ) ), GTK_SORTER( sorter ) ); self->socket_path = foobar_sway_workspace_service_get_socket_path( ); if ( !self->socket_path ) { return; } self->event_cancellable = g_cancellable_new( ); self->event_thread = g_thread_new( "workspace-event-listener", foobar_sway_workspace_service_event_thread_func, self ); foobar_sway_workspace_service_initialize( self ); } // // Property getter implementation, mapping a property id to a method. // void foobar_sway_workspace_service_get_property( GObject* object, guint prop_id, GValue* value, GParamSpec* pspec ) { FoobarSwayWorkspaceService* self = (FoobarSwayWorkspaceService*)object; switch ( prop_id ) { case PROP_WORKSPACES: g_value_set_object( value, foobar_workspace_service_get_workspaces( FOOBAR_WORKSPACE_SERVICE( self ) ) ); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); break; } } // // Instance de-initialization for the workspace service. // // This is where the event thread is joined because it might still create new object references which is a bad thing to // do in finalize. // void foobar_sway_workspace_service_dispose( GObject* object ) { FoobarSwayWorkspaceService* self = (FoobarSwayWorkspaceService*)object; 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 ); } // // Instance cleanup for the workspace service. // void foobar_sway_workspace_service_finalize( GObject* object ) { FoobarSwayWorkspaceService* self = (FoobarSwayWorkspaceService*)object; for ( guint i = 0; i < g_list_model_get_n_items( G_LIST_MODEL( self->workspaces ) ); ++i ) { 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->sorted_workspaces ); g_clear_object( &self->workspaces ); g_clear_pointer( &self->socket_path, g_free ); G_OBJECT_CLASS( foobar_sway_workspace_service_parent_class )->finalize( object ); } // // Get a sorted list of all workspaces. // GListModel* foobar_sway_workspace_service_get_workspaces( FoobarWorkspaceService* service ) { FoobarSwayWorkspaceService* self = (FoobarSwayWorkspaceService*)service; return G_LIST_MODEL( self->sorted_workspaces ); } // // Check whether this service is connected to Sway. // gboolean foobar_sway_workspace_service_is_available( FoobarWorkspaceService* service ) { FoobarSwayWorkspaceService* self = (FoobarSwayWorkspaceService*)service; return self->socket_path != NULL; } // --------------------------------------------------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------------------------------------------------- // // Create a new Sway workspace service instance. // 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. // void foobar_sway_workspace_service_initialize( FoobarSwayWorkspaceService* self ) { for ( guint i = 0; i < g_list_model_get_n_items( G_LIST_MODEL( self->workspaces ) ); ++i ) { FoobarSwayWorkspace* workspace = g_list_model_get_item( G_LIST_MODEL( self->workspaces ), i ); workspace->service = NULL; } g_list_store_remove_all( self->workspaces ); g_autoptr( GListStore ) workspaces = g_list_store_new( FOOBAR_TYPE_WORKSPACE ); { g_autoptr( GError ) error = NULL; g_autoptr( JsonNode ) workspaces_node = foobar_sway_workspace_service_send_request( self, MESSAGE_TYPE_WORKSPACES, "", &error ); if ( !workspaces_node ) { g_warning( "Unable to load workspaces: %s", error->message ); return; } JsonArray* workspaces_array = json_node_get_array( workspaces_node ); for ( guint i = 0; i < json_array_get_length( workspaces_array ); ++i ) { JsonObject* workspace_object = json_array_get_object_element( workspaces_array, i ); gchar const* name = json_object_get_string_member( workspace_object, "name" ); gchar const* output = json_object_get_string_member( workspace_object, "output" ); gint64 id = json_object_get_int_member( workspace_object, "id" ); gint64 num = json_object_get_int_member( workspace_object, "num" ); gboolean visible = json_object_get_boolean_member( workspace_object, "visible" ); gboolean focused = json_object_get_boolean_member( workspace_object, "focused" ); gboolean urgent = json_object_get_boolean_member( workspace_object, "urgent" ); FoobarWorkspaceFlags flags = FOOBAR_WORKSPACE_FLAGS_NONE; flags = visible ? flags | FOOBAR_WORKSPACE_FLAGS_VISIBLE : flags; flags = focused ? flags | FOOBAR_WORKSPACE_FLAGS_ACTIVE : flags; flags = urgent ? flags | FOOBAR_WORKSPACE_FLAGS_URGENT : flags; g_autoptr(FoobarSwayWorkspace) workspace = foobar_sway_workspace_new( self ); foobar_sway_workspace_set_id( workspace, id ); foobar_sway_workspace_set_num( workspace, num ); foobar_sway_workspace_set_name( workspace, name ); foobar_sway_workspace_set_monitor( workspace, output ); foobar_sway_workspace_set_flags( workspace, flags ); g_list_store_append( workspaces, workspace ); } } for ( guint i = 0; i < g_list_model_get_n_items( G_LIST_MODEL( workspaces ) ); ++i ) { FoobarWorkspace* workspace = g_list_model_get_item( G_LIST_MODEL( workspaces ), i ); g_list_store_append( self->workspaces, workspace ); } } 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. // JsonNode* foobar_sway_workspace_service_send_request( FoobarSwayWorkspaceService* self, guint type, gchar const* payload, GError** error ) { g_autoptr( GSocket ) sock = g_socket_new( G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, error ); if ( !sock ) { return NULL; } g_autoptr( GSocketAddress ) addr = g_unix_socket_address_new( self->socket_path ); if ( !g_socket_connect( sock, addr, NULL, error ) ) { return NULL; } if ( !foobar_sway_workspace_service_write( sock, type, payload, NULL, error ) ) { return NULL; } guint reply_type; JsonNode* reply = foobar_sway_workspace_service_read( sock, &reply_type, NULL, error ); if ( !reply ) { return NULL; } if ( reply_type != type ) { g_set_error( error, FOOBAR_SWAY_ERROR, FOOBAR_SWAY_ERROR_MISMATCHING_TYPE, "Mismatching message type in reply: Sent %u, but received %u.", type, reply_type ); json_node_unref( reply ); return NULL; } return reply; } // // Write a request onto a Sway socket. // gboolean foobar_sway_workspace_service_write( GSocket* socket, guint32 type, gchar const* payload, GCancellable* cancellable, GError** out_error ) { guint32 payload_length = strlen( payload ); MessageHeader header = { 0 }; memcpy( header.magic_string, MAGIC_STRING, sizeof( header.magic_string ) ); header.message_length = payload_length; header.message_type = type; gsize buffer_length = sizeof( MessageHeader ) + payload_length; g_autofree gchar* buffer = g_new0( gchar, buffer_length ); memcpy( buffer + 0, &header, sizeof( MessageHeader ) ); memcpy( buffer + sizeof( MessageHeader ), payload, payload_length ); return g_socket_send( socket, buffer, buffer_length, cancellable, out_error ) != -1; } // // Read a response or event from a Sway socket. // JsonNode* foobar_sway_workspace_service_read( GSocket* socket, guint32* out_type, GCancellable* cancellable, GError** out_error ) { gchar header_buffer[ sizeof( MessageHeader ) ]; gsize header_received = 0; while ( header_received < sizeof( header_buffer ) ) { gssize received = g_socket_receive( socket, &header_buffer[ header_received ], sizeof( header_buffer ) - header_received, cancellable, out_error ); if ( received < 0 ) { return NULL; } header_received += received; } MessageHeader header = { 0 }; memcpy( &header, header_buffer, sizeof( MessageHeader ) ); if ( memcmp( header.magic_string, MAGIC_STRING, sizeof( header.magic_string ) ) != 0 ) { g_set_error( out_error, FOOBAR_SWAY_ERROR, FOOBAR_SWAY_ERROR_INVALID_MAGIC_STRING, "Received invalid magic string from Sway." ); return NULL; } g_autofree gchar* buffer = g_new0( gchar, header.message_length + 1 ); gsize buffer_received = 0; while ( buffer_received < header.message_length ) { gssize received = g_socket_receive( socket, &buffer[ buffer_received ], header.message_length - buffer_received, cancellable, out_error ); if ( received < 0 ) { return NULL; } buffer_received += received; } g_autoptr( JsonParser ) parser = json_parser_new( ); if ( !json_parser_load_from_data( parser, buffer, (gssize)header.message_length, out_error ) ) { return NULL; } if ( out_type ) { *out_type = header.message_type; } return json_parser_steal_root( parser ); } // // Get the sway socket path. // gchar* foobar_sway_workspace_service_get_socket_path( void ) { gchar const* sock = g_getenv( "SWAYSOCK" ); if ( !sock ) { return NULL; } return g_strdup( sock ); } // // Sorting callback for workspaces. Items are sorted to be in the following order: // 1. named // 2. numbered // gint foobar_sway_workspace_service_sort_func( gconstpointer item_a, gconstpointer item_b, gpointer userdata ) { (void)userdata; FoobarSwayWorkspace* workspace_a = (FoobarSwayWorkspace*)item_a; FoobarSwayWorkspace* workspace_b = (FoobarSwayWorkspace*)item_b; gint64 num_a = foobar_sway_workspace_get_num( workspace_a ); gint64 num_b = foobar_sway_workspace_get_num( workspace_b ); gboolean named_a = num_a < 0; gboolean named_b = num_b < 0; gchar const* name_a = foobar_workspace_get_name( FOOBAR_WORKSPACE( workspace_a ) ); gchar const* name_b = foobar_workspace_get_name( FOOBAR_WORKSPACE( workspace_b ) ); // both numbered => sort by number if ( !named_a && !named_b ) { if ( num_a < num_b ) { return -1; } if ( num_a > num_b ) { return 1; } return 0; } // one numbered, one named if ( named_a && !named_b ) { return -1; } if ( !named_a && named_b ) { return 1; } // 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 ); } }