summaryrefslogtreecommitdiff
path: root/src/services/workspaces
diff options
context:
space:
mode:
Diffstat (limited to 'src/services/workspaces')
-rw-r--r--src/services/workspaces/hyprland-workspace-service.c56
-rw-r--r--src/services/workspaces/sway-workspace-service.c402
2 files changed, 425 insertions, 33 deletions
diff --git a/src/services/workspaces/hyprland-workspace-service.c b/src/services/workspaces/hyprland-workspace-service.c
index 3abe152..9a64c49 100644
--- a/src/services/workspaces/hyprland-workspace-service.c
+++ b/src/services/workspaces/hyprland-workspace-service.c
@@ -133,6 +133,9 @@ static gint64 foobar_hyprland_workspace_service_parse_id
static gchar* foobar_hyprland_workspace_service_get_hyprland_base_path ( void );
static gint foobar_hyprland_workspace_service_compare_ids ( gconstpointer a,
gconstpointer b );
+static gint foobar_hyprland_workspace_service_sort_func ( gconstpointer item_a,
+ gconstpointer item_b,
+ gpointer userdata );
G_DEFINE_TYPE_WITH_CODE(
@@ -452,7 +455,7 @@ void foobar_hyprland_workspace_service_init( FoobarHyprlandWorkspaceService* sel
{
self->workspaces = g_list_store_new( FOOBAR_TYPE_WORKSPACE );
- GtkCustomSorter* sorter = gtk_custom_sorter_new( foobar_workspace_sort_func, NULL, NULL );
+ GtkCustomSorter* sorter = gtk_custom_sorter_new( foobar_hyprland_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 ) );
@@ -1283,6 +1286,57 @@ gint foobar_hyprland_workspace_service_compare_ids(
}
//
+// Sorting callback for workspaces. Items are sorted to be in the following order:
+// 1. normal
+// 2. named persistent
+// 3. named
+// 4. special
+// 5. named special
+//
+gint foobar_hyprland_workspace_service_sort_func(
+ gconstpointer item_a,
+ gconstpointer item_b,
+ gpointer userdata )
+{
+ (void)userdata;
+
+ FoobarWorkspace* workspace_a = (FoobarWorkspace*)item_a;
+ FoobarWorkspace* workspace_b = (FoobarWorkspace*)item_b;
+ gint64 id_a = foobar_workspace_get_id( workspace_a );
+ gint64 id_b = foobar_workspace_get_id( workspace_b );
+ gboolean special_a = ( foobar_workspace_get_flags( workspace_a ) & FOOBAR_WORKSPACE_FLAGS_SPECIAL ) != 0;
+ gboolean special_b = ( foobar_workspace_get_flags( workspace_b ) & FOOBAR_WORKSPACE_FLAGS_SPECIAL ) != 0;
+ gboolean named_a = id_a < 0;
+ gboolean named_b = id_b < 0;
+
+ // both normal (includes numbered persistent) => sort by ID
+ if ( !named_a && !named_b )
+ {
+ if ( id_a < id_b ) { return -1; }
+ if ( id_a > id_b ) { return 1; }
+ return 0;
+ }
+
+ // one normal, one special => normal first
+ if ( special_a && !special_b ) { return 1; }
+ if ( !special_a && special_b ) { return -1; }
+
+ // only one normal, one named
+ if ( named_a && !named_b ) { return -1; }
+ if ( !named_a && named_b ) { return 1; }
+
+ // both special
+ if ( special_a && special_b )
+ {
+ // if one is -99 => put it last
+ if ( id_a == -99 && id_b != -99 ) { return 1; }
+ if ( id_a != -99 && id_b == -99 ) { return -1; }
+ }
+
+ return g_strcmp0( foobar_workspace_get_name( workspace_a ), foobar_workspace_get_name( workspace_b ) );
+}
+
+//
// Create a new EventData structure.
//
static EventData* event_data_new(
diff --git a/src/services/workspaces/sway-workspace-service.c b/src/services/workspaces/sway-workspace-service.c
index 5ab9cc4..ebb0167 100644
--- a/src/services/workspaces/sway-workspace-service.c
+++ b/src/services/workspaces/sway-workspace-service.c
@@ -1,6 +1,20 @@
#include "services/workspaces/sway-workspace-service.h"
+#include "gio/gio.h"
+#include "glib-object.h"
+#include "glib.h"
+#include "services/workspace-service.h"
#include <json-glib/json-glib.h>
#include <gtk/gtk.h>
+#include <stdio.h>
+#include <string.h>
+
+static const gchar MAGIC_STRING[] = "i3-ipc";
+
+#define MESSAGE_TYPE_COMMAND 0
+#define MESSAGE_TYPE_WORKSPACES 1
+
+typedef struct _MessageHeader MessageHeader;
+typedef enum _FoobarSwayError FoobarSwayError;
//
// FoobarSwayWorkspace:
@@ -17,6 +31,7 @@ struct _FoobarSwayWorkspace
GObject parent_instance;
FoobarSwayWorkspaceService* service;
gint64 id;
+ gint64 num;
gchar* name;
gchar* monitor;
FoobarWorkspaceFlags flags;
@@ -32,27 +47,30 @@ enum
};
static void foobar_sway_workspace_class_init ( FoobarSwayWorkspaceClass* klass );
-static void foobar_sway_workspace_interface_init( FoobarWorkspaceInterface* iface );
+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 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 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 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 );
+ 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 );
+ gchar const* value );
static void foobar_sway_workspace_set_monitor ( FoobarSwayWorkspace* self,
- gchar const* value );
+ gchar const* value );
static void foobar_sway_workspace_set_flags ( FoobarSwayWorkspace* self,
- FoobarWorkspaceFlags value );
+ FoobarWorkspaceFlags value );
G_DEFINE_TYPE_WITH_CODE(
FoobarSwayWorkspace, foobar_sway_workspace, G_TYPE_OBJECT,
@@ -79,23 +97,68 @@ 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 gchar* foobar_sway_workspace_service_get_socket_path( void );
+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 );
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 ) );
+//
+// 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
// ---------------------------------------------------------------------------------------------------------------------
@@ -199,6 +262,15 @@ gint64 foobar_sway_workspace_get_id( FoobarWorkspace* workspace )
}
//
+// 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 )
@@ -233,6 +305,34 @@ 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;
+ }
+ }
}
//
@@ -240,7 +340,7 @@ void foobar_sway_workspace_activate( FoobarWorkspace* workspace )
//
void foobar_sway_workspace_set_id(
FoobarSwayWorkspace* self,
- gint64 value )
+ gint64 value )
{
g_return_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ) );
@@ -252,11 +352,22 @@ void foobar_sway_workspace_set_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 )
+ gchar const* value )
{
g_return_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ) );
@@ -273,7 +384,7 @@ void foobar_sway_workspace_set_name(
//
void foobar_sway_workspace_set_monitor(
FoobarSwayWorkspace* self,
- gchar const* value )
+ gchar const* value )
{
g_return_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ) );
@@ -290,7 +401,7 @@ void foobar_sway_workspace_set_monitor(
//
void foobar_sway_workspace_set_flags(
FoobarSwayWorkspace* self,
- FoobarWorkspaceFlags value )
+ FoobarWorkspaceFlags value )
{
g_return_if_fail( FOOBAR_IS_SWAY_WORKSPACE( self ) );
@@ -334,7 +445,7 @@ void foobar_sway_workspace_service_init( FoobarSwayWorkspaceService* self )
{
self->workspaces = g_list_store_new( FOOBAR_TYPE_WORKSPACE );
- GtkCustomSorter* sorter = gtk_custom_sorter_new( foobar_workspace_sort_func, NULL, NULL );
+ 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 ) );
@@ -345,6 +456,8 @@ void foobar_sway_workspace_service_init( FoobarSwayWorkspaceService* self )
return;
}
+ foobar_sway_workspace_service_initialize( self );
+
(void)foobar_sway_workspace_set_id;
(void)foobar_sway_workspace_set_name;
(void)foobar_sway_workspace_set_monitor;
@@ -435,6 +548,194 @@ FoobarWorkspaceService* foobar_sway_workspace_service_new( void )
return g_object_new( FOOBAR_TYPE_SWAY_WORKSPACE_SERVICE, NULL );
}
+// ---------------------------------------------------------------------------------------------------------------------
+// Helper Methods
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// 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 );
+ }
+}
+
+//
+// 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.
//
@@ -448,3 +749,40 @@ gchar* foobar_sway_workspace_service_get_socket_path( void )
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 );
+}