diff options
Diffstat (limited to 'src/control-center.c')
| -rw-r--r-- | src/control-center.c | 821 |
1 files changed, 821 insertions, 0 deletions
diff --git a/src/control-center.c b/src/control-center.c new file mode 100644 index 0000000..23c8a1a --- /dev/null +++ b/src/control-center.c @@ -0,0 +1,821 @@ +#include "control-center.h" +#include "widgets/control-center/control-button.h" +#include "widgets/control-center/control-slider.h" +#include "widgets/control-center/control-details.h" +#include "widgets/control-center/control-details-item.h" +#include "widgets/inset-container.h" +#include "widgets/notification-widget.h" +#include <gtk4-layer-shell.h> + +#define STACK_ITEM_LIST "list" +#define STACK_ITEM_PLACEHOLDER "placeholder" + +// +// FoobarControlCenter: +// +// The control center is made up of two sections: +// - The "controls" section for configuring wi-fi, volume, brightness, etc. +// - The "notifications" section showing all notifications (including dismissed ones) and allowing the user to close +// them. +// + +struct _FoobarControlCenter +{ + GtkWindow parent_instance; + GtkWidget* control_container; + GtkWidget* notification_list; + GtkWidget* notification_container; + GtkWidget* notification_placeholder; + GtkWidget* notification_stack; + GtkWidget* layout; + FoobarBrightnessService* brightness_service; + FoobarAudioService* audio_service; + FoobarNetworkService* network_service; + FoobarBluetoothService* bluetooth_service; + FoobarNotificationService* notification_service; + FoobarConfigurationService* configuration_service; + gchar* notification_time_format; + gint notification_min_height; + gint notification_close_button_inset; + gint padding; + gint spacing; + gulong config_handler_id; +}; + +static void foobar_control_center_class_init ( FoobarControlCenterClass* klass ); +static void foobar_control_center_init ( FoobarControlCenter* self ); +static void foobar_control_center_finalize ( GObject* object ); +static void foobar_control_center_handle_notification_setup ( GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ); +static void foobar_control_center_handle_network_setup ( GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ); +static void foobar_control_center_handle_bluetooth_device_setup ( GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ); +static void foobar_control_center_handle_bluetooth_device_activate ( GtkListView* view, + guint position, + gpointer userdata ); +static void foobar_control_center_handle_audio_device_setup ( GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ); +static void foobar_control_center_handle_audio_device_activate ( GtkListView* view, + guint position, + gpointer userdata ); +static void foobar_control_center_handle_config_change ( GObject* object, + GParamSpec* pspec, + gpointer userdata ); +static FoobarControlDetailsAccessory foobar_control_center_compute_bluetooth_device_accessory ( GtkExpression* expression, + FoobarBluetoothDeviceState state, + gpointer userdata ); +static gchar* foobar_control_center_compute_visible_notification_child_name( GtkExpression* expression, + guint count, + gpointer userdata ); + +G_DEFINE_FINAL_TYPE( FoobarControlCenter, foobar_control_center, GTK_TYPE_WINDOW ) + +// --------------------------------------------------------------------------------------------------------------------- +// Window Implementation +// --------------------------------------------------------------------------------------------------------------------- + +// +// Static initialization for the control center. +// +void foobar_control_center_class_init( FoobarControlCenterClass* klass ) +{ + GObjectClass* object_klass = G_OBJECT_CLASS( klass ); + object_klass->finalize = foobar_control_center_finalize; +} + +// +// Instance initialization for the control center. +// +void foobar_control_center_init( FoobarControlCenter* self ) +{ + self->control_container = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + gtk_widget_add_css_class( self->control_container, "controls" ); + gtk_widget_set_hexpand( self->control_container, TRUE ); + + GtkListItemFactory* notification_factory = gtk_signal_list_item_factory_new( ); + g_signal_connect( + notification_factory, + "setup", + G_CALLBACK( foobar_control_center_handle_notification_setup ), + self ); + self->notification_list = gtk_list_view_new( NULL, notification_factory ); + gtk_widget_add_css_class( self->notification_list, "notifications" ); + + // XXX: Get rid of this container to enable virtualization; currently needed because I haven't figured out how to + // set padding without using css (setting margin on list view causes glitches when scrolling). + self->notification_container = foobar_inset_container_new( ); + foobar_inset_container_set_child( FOOBAR_INSET_CONTAINER( self->notification_container ), self->notification_list ); + + GtkWidget* scrolled_window = gtk_scrolled_window_new( ); + gtk_scrolled_window_set_child( GTK_SCROLLED_WINDOW( scrolled_window ), self->notification_container ); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_window ), GTK_POLICY_NEVER, GTK_POLICY_EXTERNAL ); + + self->notification_placeholder = gtk_label_new( "No Notifications" ); + gtk_widget_add_css_class( self->notification_placeholder, "placeholder" ); + gtk_label_set_ellipsize( GTK_LABEL( self->notification_placeholder ), PANGO_ELLIPSIZE_END ); + gtk_label_set_wrap( GTK_LABEL( self->notification_placeholder ), FALSE ); + + self->notification_stack = gtk_stack_new( ); + gtk_stack_add_named( GTK_STACK( self->notification_stack ), scrolled_window, STACK_ITEM_LIST ); + gtk_stack_add_named( GTK_STACK( self->notification_stack ), self->notification_placeholder, STACK_ITEM_PLACEHOLDER ); + gtk_widget_set_vexpand( self->notification_stack, TRUE ); + gtk_widget_set_hexpand( self->notification_stack, TRUE ); + + self->layout = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + gtk_box_append( GTK_BOX( self->layout ), self->control_container ); + gtk_box_append( GTK_BOX( self->layout ), gtk_separator_new( GTK_ORIENTATION_HORIZONTAL ) ); + gtk_box_append( GTK_BOX( self->layout ), self->notification_stack ); + + g_autoptr( GtkSizeGroup ) size_group = gtk_size_group_new( GTK_SIZE_GROUP_HORIZONTAL ); + gtk_size_group_add_widget( size_group, self->control_container ); + gtk_size_group_add_widget( size_group, self->notification_stack ); + + gtk_window_set_child( GTK_WINDOW( self ), self->layout ); + gtk_window_set_title( GTK_WINDOW( self ), "Foobar Control Center" ); + gtk_widget_add_css_class( GTK_WIDGET( self ), "control-center" ); + gtk_layer_init_for_window( GTK_WINDOW( self ) ); + gtk_layer_set_namespace( GTK_WINDOW( self ), "foobar-control-center" ); +} + +// +// Instance cleanup for the control center. +// +void foobar_control_center_finalize( GObject* object ) +{ + FoobarControlCenter* self = (FoobarControlCenter*)object; + + g_clear_signal_handler( &self->config_handler_id, self->configuration_service ); + g_clear_object( &self->brightness_service ); + g_clear_object( &self->audio_service ); + g_clear_object( &self->network_service ); + g_clear_object( &self->bluetooth_service ); + g_clear_object( &self->configuration_service ); + g_clear_object( &self->notification_service ); + g_clear_pointer( &self->notification_time_format, g_free ); + + G_OBJECT_CLASS( foobar_control_center_parent_class )->finalize( object ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------------------------------------------------- + +// +// Create a new control center instance. +// +FoobarControlCenter* foobar_control_center_new( + FoobarBrightnessService* brightness_service, + FoobarAudioService* audio_service, + FoobarNetworkService* network_service, + FoobarBluetoothService* bluetooth_service, + FoobarNotificationService* notification_service, + FoobarConfigurationService* configuration_service ) +{ + g_return_val_if_fail( FOOBAR_IS_BRIGHTNESS_SERVICE( brightness_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_AUDIO_SERVICE( audio_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_NETWORK_SERVICE( network_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_BLUETOOTH_SERVICE( bluetooth_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_NOTIFICATION_SERVICE( notification_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_CONFIGURATION_SERVICE( configuration_service ), NULL ); + + FoobarControlCenter* self = g_object_new( FOOBAR_TYPE_CONTROL_CENTER, NULL ); + self->brightness_service = g_object_ref( brightness_service ); + self->audio_service = g_object_ref( audio_service ); + self->network_service = g_object_ref( network_service ); + self->bluetooth_service = g_object_ref( bluetooth_service ); + self->notification_service = g_object_ref( notification_service ); + self->configuration_service = g_object_ref( configuration_service ); + + // Apply the configuration and subscribe to changes. + + FoobarConfiguration const* config = foobar_configuration_service_get_current( self->configuration_service ); + foobar_control_center_apply_configuration( + self, + foobar_configuration_get_control_center( config ), + foobar_configuration_get_notifications( config ) ); + self->config_handler_id = g_signal_connect( + self->configuration_service, + "notify::current", + G_CALLBACK( foobar_control_center_handle_config_change ), + self ); + + // Set up the notifications list view. + + GListModel* source_model = foobar_notification_service_get_notifications( self->notification_service ); + GtkNoSelection* selection_model = gtk_no_selection_new( g_object_ref( source_model ) ); + gtk_list_view_set_model( GTK_LIST_VIEW( self->notification_list ), GTK_SELECTION_MODEL( selection_model ) ); + + // Set up bindings. + + { + GtkExpression* count_expr = gtk_property_expression_new( GTK_TYPE_SORT_LIST_MODEL, NULL, "n-items" ); + GtkExpression* child_params[] = { count_expr }; + GtkExpression* child_expr = gtk_cclosure_expression_new( + G_TYPE_STRING, + NULL, + G_N_ELEMENTS( child_params ), + child_params, + G_CALLBACK( foobar_control_center_compute_visible_notification_child_name ), + NULL, + NULL ); + gtk_expression_bind( child_expr, self->notification_stack, "visible-child-name", source_model ); + } + + return self; +} + +// +// Apply the control center configuration provided by the configuration service, additionally using the provided +// notification configuration for some settings. +// +void foobar_control_center_apply_configuration( + FoobarControlCenter* self, + FoobarControlCenterConfiguration const* config, + FoobarNotificationConfiguration const* notification_config ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_CENTER( self ) ); + g_return_if_fail( config != NULL ); + g_return_if_fail( notification_config != NULL ); + + // Copy configuration into member variables. + + g_clear_pointer( &self->notification_time_format, g_free ); + self->notification_time_format = g_strdup( foobar_notification_configuration_get_time_format( notification_config ) ); + self->notification_min_height = foobar_notification_configuration_get_min_height( notification_config ); + self->notification_close_button_inset = foobar_notification_configuration_get_close_button_inset( notification_config ); + self->padding = foobar_control_center_configuration_get_padding( config ); + self->spacing = foobar_control_center_configuration_get_spacing( config ); + + // Configure the window. + + gtk_window_set_default_size( + GTK_WINDOW( self ), + foobar_control_center_configuration_get_width( config ), + foobar_control_center_configuration_get_height( config ) ); + + FoobarScreenEdge position = foobar_control_center_configuration_get_position( config ); + FoobarControlCenterAlignment alignment = foobar_control_center_configuration_get_alignment( config ); + gboolean attach_left = ( position == FOOBAR_SCREEN_EDGE_LEFT ); + gboolean attach_right = ( position == FOOBAR_SCREEN_EDGE_RIGHT ); + gboolean attach_top = ( position == FOOBAR_SCREEN_EDGE_TOP ); + gboolean attach_bottom = ( position == FOOBAR_SCREEN_EDGE_BOTTOM ); + switch ( position ) + { + case FOOBAR_SCREEN_EDGE_LEFT: + case FOOBAR_SCREEN_EDGE_RIGHT: + attach_top = + ( alignment == FOOBAR_CONTROL_CENTER_ALIGNMENT_START ) || + ( alignment == FOOBAR_CONTROL_CENTER_ALIGNMENT_FILL ); + attach_bottom = + ( alignment == FOOBAR_CONTROL_CENTER_ALIGNMENT_END ) || + ( alignment == FOOBAR_CONTROL_CENTER_ALIGNMENT_FILL ); + break; + case FOOBAR_SCREEN_EDGE_TOP: + case FOOBAR_SCREEN_EDGE_BOTTOM: + attach_left = + ( alignment == FOOBAR_CONTROL_CENTER_ALIGNMENT_START ) || + ( alignment == FOOBAR_CONTROL_CENTER_ALIGNMENT_FILL ); + attach_right = + ( alignment == FOOBAR_CONTROL_CENTER_ALIGNMENT_END ) || + ( alignment == FOOBAR_CONTROL_CENTER_ALIGNMENT_FILL ); + break; + default: + g_warn_if_reached( ); + break; + } + + gtk_layer_set_anchor( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_LEFT, attach_left ); + gtk_layer_set_anchor( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_RIGHT, attach_right ); + gtk_layer_set_anchor( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_TOP, attach_top ); + gtk_layer_set_anchor( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_BOTTOM, attach_bottom ); + + gint offset = foobar_control_center_configuration_get_offset( config ); + gtk_layer_set_margin( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_LEFT, attach_left ? offset : 0 ); + gtk_layer_set_margin( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_RIGHT, attach_right ? offset : 0 ); + gtk_layer_set_margin( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_TOP, attach_top ? offset : 0 ); + gtk_layer_set_margin( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_BOTTOM, attach_bottom ? offset : 0 ); + + // Configure the orientation. + + FoobarOrientation orientation = foobar_control_center_configuration_get_orientation( config ); + switch ( orientation ) + { + case FOOBAR_ORIENTATION_HORIZONTAL: + gtk_orientable_set_orientation( GTK_ORIENTABLE( self->layout ), GTK_ORIENTATION_HORIZONTAL ); + break; + case FOOBAR_ORIENTATION_VERTICAL: + gtk_orientable_set_orientation( GTK_ORIENTABLE( self->layout ), GTK_ORIENTATION_VERTICAL ); + break; + default: + g_warn_if_reached( ); + break; + } + + // Adjust spacing for the containers. + + foobar_inset_container_set_inset_vertical( FOOBAR_INSET_CONTAINER( self->notification_container ), -self->spacing / 2 ); + gtk_widget_set_margin_top( self->notification_container, self->padding ); + gtk_widget_set_margin_bottom( self->notification_container, self->padding ); + + gtk_widget_set_margin_start( self->notification_placeholder, self->padding ); + gtk_widget_set_margin_end( self->notification_placeholder, self->padding ); + gtk_widget_set_margin_top( self->notification_placeholder, self->padding ); + gtk_widget_set_margin_bottom( self->notification_placeholder, self->padding ); + + gtk_box_set_spacing( GTK_BOX( self->control_container ), self->spacing ); + gtk_widget_set_margin_top( self->control_container, self->padding ); + gtk_widget_set_margin_bottom( self->control_container, self->padding ); + + // Re-create list items by resetting the factory. + + GtkListItemFactory* factory = gtk_list_view_get_factory( GTK_LIST_VIEW( self->notification_list ) ); + g_object_ref( factory ); + gtk_list_view_set_factory( GTK_LIST_VIEW( self->notification_list ), NULL ); + gtk_list_view_set_factory( GTK_LIST_VIEW( self->notification_list ), factory ); + g_object_unref( factory ); + + // Re-create control rows. + + GtkWidget* row_widget; + while ( ( row_widget = gtk_widget_get_first_child( self->control_container ) ) ) + { + gtk_widget_unparent( row_widget ); + } + + gsize rows_count; + FoobarControlCenterRow const* rows = foobar_control_center_configuration_get_rows( config, &rows_count ); + for ( gsize i = 0; i < rows_count; ++i ) + { + // Add rows. If there are details for a row, add them into another box that is then added to the + // control_container to circumvent the spacing of the box. + + switch ( rows[i] ) + { + case FOOBAR_CONTROL_CENTER_ROW_CONNECTIVITY: + { + // Create the buttons for network and bluetooth. + + GtkWidget* network_button = foobar_control_button_new( ); + + GtkWidget* bluetooth_button = foobar_control_button_new( ); + foobar_control_button_set_icon_name( FOOBAR_CONTROL_BUTTON( bluetooth_button ), "fluent-bluetooth-symbolic" ); + foobar_control_button_set_label( FOOBAR_CONTROL_BUTTON( bluetooth_button ), "Bluetooth" ); + + GtkWidget* button_container = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, self->spacing ); + gtk_box_set_homogeneous( GTK_BOX( button_container ), TRUE ); + gtk_widget_set_margin_start( button_container, self->padding ); + gtk_widget_set_margin_end( button_container, self->padding ); + gtk_box_append( GTK_BOX( button_container ), network_button ); + gtk_box_append( GTK_BOX( button_container ), bluetooth_button ); + + GtkWidget* row = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + gtk_box_append( GTK_BOX( row ), button_container ); + + // Show either a button for managing Wi-Fi or a static element for ethernet. + + FoobarNetworkAdapterWifi* wifi_adapter = foobar_network_service_get_wifi( self->network_service ); + if ( wifi_adapter ) + { + foobar_control_button_set_icon_name( FOOBAR_CONTROL_BUTTON( network_button ), "fluent-wifi-1-symbolic" ); + foobar_control_button_set_label( FOOBAR_CONTROL_BUTTON( network_button ), "Wi-Fi" ); + foobar_control_button_set_can_expand( FOOBAR_CONTROL_BUTTON( network_button ), TRUE ); + foobar_control_button_set_can_toggle( FOOBAR_CONTROL_BUTTON( network_button ), TRUE ); + g_object_bind_property( + wifi_adapter, + "is-enabled", + network_button, + "is-toggled", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL ); + g_object_bind_property( + network_button, + "is-toggled", + network_button, + "can-expand", + G_BINDING_SYNC_CREATE ); + g_object_bind_property( + network_button, + "is-expanded", + wifi_adapter, + "is-scanning", + G_BINDING_SYNC_CREATE ); + + GtkListItemFactory* wifi_item_factory = gtk_signal_list_item_factory_new( ); + g_signal_connect( + wifi_item_factory, + "setup", + G_CALLBACK( foobar_control_center_handle_network_setup ), + self ); + + GListModel* wifi_source_model = g_object_ref( foobar_network_adapter_wifi_get_networks( wifi_adapter ) ); + GtkNoSelection* wifi_selection_model = gtk_no_selection_new( wifi_source_model ); + + GtkWidget* wifi_list_view = gtk_list_view_new( GTK_SELECTION_MODEL( wifi_selection_model ), wifi_item_factory ); + + GtkWidget* wifi_details = foobar_control_details_new( ); + foobar_control_details_set_inset_top( FOOBAR_CONTROL_DETAILS( wifi_details ), self->spacing ); + foobar_control_details_set_child( FOOBAR_CONTROL_DETAILS( wifi_details ), wifi_list_view ); + g_object_bind_property( + network_button, + "is-expanded", + wifi_details, + "is-expanded", + G_BINDING_SYNC_CREATE ); + + gtk_box_append( GTK_BOX( row ), wifi_details ); + } + else + { + foobar_control_button_set_icon_name( FOOBAR_CONTROL_BUTTON( network_button ), "fluent-virtual-network-symbolic" ); + foobar_control_button_set_label( FOOBAR_CONTROL_BUTTON( network_button ), "Ethernet" ); + foobar_control_button_set_can_expand( FOOBAR_CONTROL_BUTTON( network_button ), FALSE ); + foobar_control_button_set_can_toggle( FOOBAR_CONTROL_BUTTON( network_button ), FALSE ); + } + + // Enable interactions for the bluetooth button if available. + + g_object_bind_property( + self->bluetooth_service, + "is-available", + bluetooth_button, + "can-toggle", + G_BINDING_SYNC_CREATE ); + g_object_bind_property( + self->bluetooth_service, + "is-enabled", + bluetooth_button, + "is-toggled", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL ); + g_object_bind_property( + bluetooth_button, + "is-toggled", + bluetooth_button, + "can-expand", + G_BINDING_SYNC_CREATE ); + g_object_bind_property( + bluetooth_button, + "is-expanded", + self->bluetooth_service, + "is-scanning", + G_BINDING_SYNC_CREATE ); + + GtkListItemFactory* bluetooth_item_factory = gtk_signal_list_item_factory_new( ); + g_signal_connect( + bluetooth_item_factory, + "setup", + G_CALLBACK( foobar_control_center_handle_bluetooth_device_setup ), + self ); + + GListModel* bluetooth_source_model = g_object_ref( foobar_bluetooth_service_get_devices( self->bluetooth_service ) ); + GtkNoSelection* bluetooth_selection_model = gtk_no_selection_new( bluetooth_source_model ); + + GtkWidget* bluetooth_list_view = gtk_list_view_new( + GTK_SELECTION_MODEL( bluetooth_selection_model ), + bluetooth_item_factory ); + gtk_list_view_set_single_click_activate( GTK_LIST_VIEW( bluetooth_list_view ), TRUE ); + g_signal_connect( + bluetooth_list_view, + "activate", + G_CALLBACK( foobar_control_center_handle_bluetooth_device_activate ), + NULL ); + + GtkWidget* bluetooth_details = foobar_control_details_new( ); + foobar_control_details_set_inset_top( FOOBAR_CONTROL_DETAILS( bluetooth_details ), self->spacing ); + foobar_control_details_set_child( FOOBAR_CONTROL_DETAILS( bluetooth_details ), bluetooth_list_view ); + g_object_bind_property( + bluetooth_button, + "is-expanded", + bluetooth_details, + "is-expanded", + G_BINDING_SYNC_CREATE ); + + gtk_box_append( GTK_BOX( row ), bluetooth_details ); + + gtk_box_append( GTK_BOX( self->control_container ), row ); + break; + } + case FOOBAR_CONTROL_CENTER_ROW_AUDIO_OUTPUT: + case FOOBAR_CONTROL_CENTER_ROW_AUDIO_INPUT: + { + FoobarAudioDevice* default_device; + GListModel* device_list; + gchar const* icon_name; + gchar const* label; + if ( rows[i] == FOOBAR_CONTROL_CENTER_ROW_AUDIO_OUTPUT ) + { + default_device = foobar_audio_service_get_default_output( self->audio_service ); + device_list = foobar_audio_service_get_outputs( self->audio_service ); + icon_name = "fluent-speaker-2-symbolic"; + label = "Audio Output"; + } + else + { + default_device = foobar_audio_service_get_default_input( self->audio_service ); + device_list = foobar_audio_service_get_inputs( self->audio_service ); + icon_name = "fluent-microphone-symbolic"; + label = "Audio Input"; + } + + // Create the slider for controlling the volume. + + GtkWidget* slider = foobar_control_slider_new( ); + gtk_widget_set_margin_start( slider, self->padding ); + gtk_widget_set_margin_end( slider, self->padding ); + foobar_control_slider_set_icon_name( FOOBAR_CONTROL_SLIDER( slider ), icon_name ); + foobar_control_slider_set_label( FOOBAR_CONTROL_SLIDER( slider ), label ); + foobar_control_slider_set_can_expand( FOOBAR_CONTROL_SLIDER( slider ), TRUE ); + g_object_bind_property( + default_device, + "volume", + slider, + "percentage", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL ); + + // Create the details view for selecting the audio device. + + GtkListItemFactory* item_factory = gtk_signal_list_item_factory_new( ); + g_signal_connect( item_factory, "setup", G_CALLBACK( foobar_control_center_handle_audio_device_setup ), self ); + + GListModel* source_model = g_object_ref( device_list ); + GtkNoSelection* selection_model = gtk_no_selection_new( source_model ); + + GtkWidget* list_view = gtk_list_view_new( GTK_SELECTION_MODEL( selection_model ), item_factory ); + gtk_list_view_set_single_click_activate( GTK_LIST_VIEW( list_view ), TRUE ); + g_signal_connect( + list_view, + "activate", + G_CALLBACK( foobar_control_center_handle_audio_device_activate ), + NULL ); + + GtkWidget* details = foobar_control_details_new( ); + foobar_control_details_set_inset_top( FOOBAR_CONTROL_DETAILS( details ), self->spacing ); + foobar_control_details_set_child( FOOBAR_CONTROL_DETAILS( details ), list_view ); + g_object_bind_property( + slider, + "is-expanded", + details, + "is-expanded", + G_BINDING_SYNC_CREATE ); + + GtkWidget* row = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + gtk_box_append( GTK_BOX( row ), slider ); + gtk_box_append( GTK_BOX( row ), details ); + + gtk_box_append( GTK_BOX( self->control_container ), row ); + break; + } + case FOOBAR_CONTROL_CENTER_ROW_BRIGHTNESS: + { + // Create the slider for adjusting the brightness. + + GtkWidget* slider = foobar_control_slider_new( ); + gtk_widget_set_margin_start( slider, self->padding ); + gtk_widget_set_margin_end( slider, self->padding ); + foobar_control_slider_set_icon_name( FOOBAR_CONTROL_SLIDER( slider ), "fluent-brightness-high-symbolic" ); + foobar_control_slider_set_label( FOOBAR_CONTROL_SLIDER( slider ), "Brightness" ); + g_object_bind_property( + self->brightness_service, + "percentage", + slider, + "percentage", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL ); + + gtk_box_append( GTK_BOX( self->control_container ), slider ); + break; + } + default: + { + g_warn_if_reached( ); + break; + } + } + } +} + +// --------------------------------------------------------------------------------------------------------------------- +// Signal Handlers +// --------------------------------------------------------------------------------------------------------------------- + +// +// Called by the notification list view to create a widget for displaying a notification. +// +void foobar_control_center_handle_notification_setup( + GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ) +{ + (void)factory; + FoobarControlCenter* self = (FoobarControlCenter*)userdata; + + GtkWidget* widget = foobar_notification_widget_new( ); + foobar_notification_widget_set_close_action( FOOBAR_NOTIFICATION_WIDGET( widget ), FOOBAR_TYPE_NOTIFICATION_CLOSE_ACTION_REMOVE ); + foobar_notification_widget_set_time_format( FOOBAR_NOTIFICATION_WIDGET( widget ), self->notification_time_format ); + foobar_notification_widget_set_min_height( FOOBAR_NOTIFICATION_WIDGET( widget ), self->notification_min_height ); + foobar_notification_widget_set_close_button_inset( FOOBAR_NOTIFICATION_WIDGET( widget ), self->notification_close_button_inset ); + foobar_notification_widget_set_inset_start( FOOBAR_NOTIFICATION_WIDGET( widget ), self->padding ); + foobar_notification_widget_set_inset_end( FOOBAR_NOTIFICATION_WIDGET( widget ), self->padding ); + foobar_notification_widget_set_inset_top( FOOBAR_NOTIFICATION_WIDGET( widget ), self->spacing / 2 ); + foobar_notification_widget_set_inset_bottom( FOOBAR_NOTIFICATION_WIDGET( widget ), self->spacing / 2 ); + gtk_list_item_set_child( list_item, widget ); + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + gtk_expression_bind( item_expr, widget, "notification", list_item ); + } +} + +// +// Called by the wi-fi details list view to create a widget for displaying a network. +// +void foobar_control_center_handle_network_setup( + GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ) +{ + (void)factory; + FoobarControlCenter* self = (FoobarControlCenter*)userdata; + + GtkWidget* item = foobar_control_details_item_new( ); + gtk_widget_set_margin_start( item, self->padding ); + gtk_widget_set_margin_end( item, self->padding ); + gtk_list_item_set_child( list_item, item ); + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + GtkExpression* label_expr = gtk_property_expression_new( FOOBAR_TYPE_NETWORK, item_expr, "name" ); + gtk_expression_bind( label_expr, item, "label", list_item ); + } + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + GtkExpression* checked_expr = gtk_property_expression_new( FOOBAR_TYPE_NETWORK, item_expr, "is-active" ); + gtk_expression_bind( checked_expr, item, "is-checked", list_item ); + } +} + +// +// Called by the bluetooth details list view to create a widget for displaying a device. +// +void foobar_control_center_handle_bluetooth_device_setup( + GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ) +{ + (void)factory; + FoobarControlCenter* self = (FoobarControlCenter*)userdata; + + GtkWidget* item = foobar_control_details_item_new( ); + gtk_widget_set_margin_start( item, self->padding ); + gtk_widget_set_margin_end( item, self->padding ); + gtk_list_item_set_child( list_item, item ); + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + GtkExpression* label_expr = gtk_property_expression_new( FOOBAR_TYPE_BLUETOOTH_DEVICE, item_expr, "name" ); + gtk_expression_bind( label_expr, item, "label", list_item ); + } + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + GtkExpression* state_expr = gtk_property_expression_new( FOOBAR_TYPE_BLUETOOTH_DEVICE, item_expr, "state" ); + GtkExpression* accessory_params[] = { state_expr }; + GtkExpression* accessory_expr = gtk_cclosure_expression_new( + FOOBAR_TYPE_CONTROL_DETAILS_ACCESSORY, + NULL, + G_N_ELEMENTS( accessory_params ), + accessory_params, + G_CALLBACK( foobar_control_center_compute_bluetooth_device_accessory ), + NULL, + NULL ); + gtk_expression_bind( accessory_expr, item, "accessory", list_item ); + } +} + +// +// Called by the bluetooth details list view when a device was selected. +// +void foobar_control_center_handle_bluetooth_device_activate( + GtkListView* view, + guint position, + gpointer userdata ) +{ + (void)view; + (void)userdata; + + GListModel* device_list = G_LIST_MODEL( gtk_list_view_get_model( view ) ); + FoobarBluetoothDevice* device = g_list_model_get_item( device_list, position ); + foobar_bluetooth_device_toggle_connection( device ); +} + +// +// Called by the audio details list view to create a widget for displaying a device. +// +void foobar_control_center_handle_audio_device_setup( + GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ) +{ + (void)factory; + FoobarControlCenter* self = (FoobarControlCenter*)userdata; + + GtkWidget* item = foobar_control_details_item_new( ); + gtk_widget_set_margin_start( item, self->padding ); + gtk_widget_set_margin_end( item, self->padding ); + gtk_list_item_set_child( list_item, item ); + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + GtkExpression* label_expr = gtk_property_expression_new( FOOBAR_TYPE_AUDIO_DEVICE, item_expr, "description" ); + gtk_expression_bind( label_expr, item, "label", list_item ); + } + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + GtkExpression* checked_expr = gtk_property_expression_new( FOOBAR_TYPE_AUDIO_DEVICE, item_expr, "is-default" ); + gtk_expression_bind( checked_expr, item, "is-checked", list_item ); + } +} + +// +// Called by the audio details list view when a device was selected. +// +void foobar_control_center_handle_audio_device_activate( + GtkListView* view, + guint position, + gpointer userdata ) +{ + (void)view; + (void)userdata; + + GListModel* device_list = G_LIST_MODEL( gtk_list_view_get_model( view ) ); + FoobarAudioDevice* device = g_list_model_get_item( device_list, position ); + foobar_audio_device_make_default( device ); +} + +// +// Signal handler called when the global configuration file has changed. +// +void foobar_control_center_handle_config_change( + GObject* object, + GParamSpec* pspec, + gpointer userdata ) +{ + (void)object; + (void)pspec; + FoobarControlCenter* self = (FoobarControlCenter*)userdata; + + FoobarConfiguration const* config = foobar_configuration_service_get_current( self->configuration_service ); + foobar_control_center_apply_configuration( + self, + foobar_configuration_get_control_center( config ), + foobar_configuration_get_notifications( config ) ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Value Converters +// --------------------------------------------------------------------------------------------------------------------- + +// +// Derive the item accessory from a bluetooth device's connection state. +// +FoobarControlDetailsAccessory foobar_control_center_compute_bluetooth_device_accessory( + GtkExpression* expression, + FoobarBluetoothDeviceState state, + gpointer userdata ) +{ + (void)expression; + (void)userdata; + + switch ( state ) + { + case FOOBAR_BLUETOOTH_DEVICE_STATE_DISCONNECTED: + return FOOBAR_CONTROL_DETAILS_ACCESSORY_NONE; + case FOOBAR_BLUETOOTH_DEVICE_STATE_CONNECTING: + return FOOBAR_CONTROL_DETAILS_ACCESSORY_PROGRESS; + case FOOBAR_BLUETOOTH_DEVICE_STATE_CONNECTED: + return FOOBAR_CONTROL_DETAILS_ACCESSORY_CHECKED; + default: + g_warn_if_reached( ); + return FOOBAR_CONTROL_DETAILS_ACCESSORY_NONE; + } +} + +// +// Derive the visible child in the "notifications" stack from the number of notifications. +// +// If there are no notifications, a placeholder is shown. +// +gchar* foobar_control_center_compute_visible_notification_child_name( + GtkExpression* expression, + guint count, + gpointer userdata ) +{ + (void)expression; + (void)userdata; + + return count > 0 ? g_strdup( STACK_ITEM_LIST ) : g_strdup( STACK_ITEM_PLACEHOLDER ); +}
\ No newline at end of file |
