diff options
| author | Hannes Schulze <haschu0103@outlook.de> | 2025-06-19 23:17:58 +0200 |
|---|---|---|
| committer | Hannes Schulze <haschu0103@outlook.de> | 2025-06-19 23:17:58 +0200 |
| commit | 1af3d14f3f995645a0de08a54b244c19840709e9 (patch) | |
| tree | 3c4098863d8a8fd004a2acc8912a23b6036ab4dd /csrc/launcher.c | |
| parent | 40efcf60a5f39674b5137f11de7bcfe37fb121b5 (diff) | |
init hs project
Diffstat (limited to 'csrc/launcher.c')
| -rw-r--r-- | csrc/launcher.c | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/csrc/launcher.c b/csrc/launcher.c new file mode 100644 index 0000000..f4b34d8 --- /dev/null +++ b/csrc/launcher.c @@ -0,0 +1,648 @@ +#include "launcher.h" +#include "launcher-item.h" +#include "widgets/limit-container.h" +#include <gtk4-layer-shell.h> +#include <gdk/gdkkeysyms.h> +#include <string.h> + +// +// FoobarLauncher: +// +// A window allowing the user to quickly search for applications and launch them. +// +// Note that it should be possible to continue typing, even when the results list view is focused. Conversely, the arrow +// keys can be used to select an item, even when the search input is focused. +// + +struct _FoobarLauncher +{ + GtkWindow parent_instance; + gchar** search_terms; + GtkWidget* search_text; + GtkWidget* list_view; + GtkWidget* limit_container; + GtkFilterListModel* filter_model; + GListStore* quick_answer_model; + GtkFlattenListModel* flatten_model; + GtkSingleSelection* selection_model; + FoobarApplicationService* application_service; + FoobarQuickAnswerService* quick_answer_service; + FoobarConfigurationService* configuration_service; + gulong config_handler_id; +}; + +static void foobar_launcher_class_init ( FoobarLauncherClass* klass ); +static void foobar_launcher_init ( FoobarLauncher* self ); +static void foobar_launcher_finalize ( GObject* object ); +static void foobar_launcher_handle_search_changed ( GtkEditable* editable, + gpointer userdata ); +static void foobar_launcher_handle_search_activate ( GtkText* text, + gpointer userdata ); +static gboolean foobar_launcher_handle_search_key ( GtkEventControllerKey* controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer userdata ); +static gboolean foobar_launcher_handle_list_key ( GtkEventControllerKey* controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer userdata ); +static gboolean foobar_launcher_handle_window_key ( GtkEventControllerKey* controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer userdata ); +static void foobar_launcher_handle_item_setup ( GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ); +static void foobar_launcher_handle_item_activate ( GtkListView* view, + guint position, + gpointer userdata ); +static void foobar_launcher_handle_config_change ( GObject* object, + GParamSpec* pspec, + gpointer userdata ); +static void foobar_launcher_handle_show ( GtkWidget* widget, + gpointer userdata ); +static gboolean foobar_launcher_compute_icon_visible ( GtkExpression* expression, + GIcon* icon, + gpointer userdata ); +static gboolean foobar_launcher_compute_label_visible ( GtkExpression* expression, + gchar const* label, + gpointer userdata ); +static gboolean foobar_launcher_compute_separator_visible( GtkExpression* expression, + guint item_count, + gpointer userdata ); +static gboolean foobar_launcher_filter_func ( gpointer item, + gpointer userdata ); +static gboolean foobar_launcher_is_navigation_key ( guint keyval ); + +G_DEFINE_FINAL_TYPE( FoobarLauncher, foobar_launcher, GTK_TYPE_WINDOW ) + +// --------------------------------------------------------------------------------------------------------------------- +// Window Implementation +// --------------------------------------------------------------------------------------------------------------------- + +// +// Static initialization for the launcher. +// +void foobar_launcher_class_init( FoobarLauncherClass* klass ) +{ + GObjectClass* object_klass = G_OBJECT_CLASS( klass ); + object_klass->finalize = foobar_launcher_finalize; +} + +// +// Instance initialization for the launcher. +// +void foobar_launcher_init( FoobarLauncher* self ) +{ + self->search_terms = g_new0( gchar*, 1 ); + + // Set up the search input and an event controller for auto-switching focus to the result list. + + GtkEventController* search_controller = gtk_event_controller_key_new( ); + g_signal_connect( search_controller, "key-pressed", G_CALLBACK( foobar_launcher_handle_search_key ), self ); + g_signal_connect( search_controller, "key-released", G_CALLBACK( foobar_launcher_handle_search_key ), self ); + + self->search_text = gtk_text_new( ); + gtk_text_set_placeholder_text( GTK_TEXT( self->search_text ), "Search…" ); + gtk_widget_add_controller( self->search_text, search_controller ); + gtk_widget_set_hexpand( self->search_text, TRUE ); + g_signal_connect( self->search_text, "changed", G_CALLBACK( foobar_launcher_handle_search_changed ), self ); + g_signal_connect( self->search_text, "activate", G_CALLBACK( foobar_launcher_handle_search_activate ), self ); + + GtkWidget* search_icon = gtk_image_new_from_icon_name( "fluent-search-symbolic" ); + + GtkWidget* search = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); + gtk_box_append( GTK_BOX( search ), search_icon ); + gtk_box_append( GTK_BOX( search ), self->search_text ); + gtk_widget_add_css_class( search, "search" ); + + // Set up the separator (only shown if the results are not empty). + + GtkWidget *separator = gtk_separator_new( GTK_ORIENTATION_HORIZONTAL ); + + // Set up the results list view and an event controller for auto-switching focus to the input. + + GtkListItemFactory* item_factory = gtk_signal_list_item_factory_new( ); + g_signal_connect( item_factory, "setup", G_CALLBACK( foobar_launcher_handle_item_setup ), NULL ); + + GtkCustomFilter* filter = gtk_custom_filter_new( foobar_launcher_filter_func, self, NULL ); + + self->filter_model = gtk_filter_list_model_new( NULL, GTK_FILTER( filter ) ); + + self->quick_answer_model = g_list_store_new( FOOBAR_TYPE_LAUNCHER_ITEM ); + + GListStore* flattened_models = g_list_store_new( G_TYPE_LIST_MODEL ); + g_list_store_append( flattened_models, self->quick_answer_model ); + g_list_store_append( flattened_models, self->filter_model ); + + self->flatten_model = gtk_flatten_list_model_new( G_LIST_MODEL( flattened_models ) ); + + self->selection_model = gtk_single_selection_new( G_LIST_MODEL( g_object_ref( self->flatten_model ) ) ); + + { + GtkExpression* list_expr = gtk_constant_expression_new( GTK_TYPE_FLATTEN_LIST_MODEL, self->flatten_model ); + GtkExpression* count_expr = gtk_property_expression_new( GTK_TYPE_FLATTEN_LIST_MODEL, list_expr, "n-items" ); + GtkExpression* visible_params[] = { count_expr }; + GtkExpression* visible_expr = gtk_cclosure_expression_new( + G_TYPE_BOOLEAN, + NULL, + G_N_ELEMENTS( visible_params ), + visible_params, + G_CALLBACK( foobar_launcher_compute_separator_visible ), + NULL, + NULL ); + gtk_expression_bind( visible_expr, separator, "visible", NULL ); + } + + GtkEventController* list_controller = gtk_event_controller_key_new( ); + g_signal_connect( list_controller, "key-pressed", G_CALLBACK( foobar_launcher_handle_list_key ), self ); + g_signal_connect( list_controller, "key-released", G_CALLBACK( foobar_launcher_handle_list_key ), self ); + + self->list_view = gtk_list_view_new( GTK_SELECTION_MODEL( g_object_ref( self->selection_model ) ), item_factory ); + gtk_list_view_set_single_click_activate( GTK_LIST_VIEW( self->list_view ), TRUE ); + gtk_scrollable_set_vscroll_policy( GTK_SCROLLABLE( self->list_view ), GTK_SCROLL_MINIMUM ); + gtk_widget_add_controller( self->list_view, list_controller ); + g_signal_connect( self->list_view, "activate", G_CALLBACK( foobar_launcher_handle_item_activate ), self ); + + GtkWidget* scrolled_window = gtk_scrolled_window_new( ); + // XXX: Use GTK_POLICY_AUTOMATIC instead of GTK_POLICY_EXTERNAL once I figured out how to remove the minimum + // height in this case. For now, no scrollbar is shown. + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_window ), GTK_POLICY_NEVER, GTK_POLICY_EXTERNAL ); + gtk_scrolled_window_set_child( GTK_SCROLLED_WINDOW( scrolled_window ), self->list_view ); + gtk_scrolled_window_set_propagate_natural_height( GTK_SCROLLED_WINDOW( scrolled_window ), TRUE ); + gtk_widget_set_vexpand( scrolled_window, TRUE ); + + // Set up the layout. + + GtkWidget* layout = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + gtk_box_append( GTK_BOX( layout ), search ); + gtk_box_append( GTK_BOX( layout ), separator ); + gtk_box_append( GTK_BOX( layout ), scrolled_window ); + + // Let the launcher take up space to accommodate the result list's natural height until the configured maximum + // height. + + self->limit_container = foobar_limit_container_new( ); + foobar_limit_container_set_child( FOOBAR_LIMIT_CONTAINER( self->limit_container ), layout ); + + // Set up the window, listening for the "Escape" key. + + GtkEventController* window_controller = gtk_event_controller_key_new( ); + g_signal_connect( window_controller, "key-released", G_CALLBACK( foobar_launcher_handle_window_key ), self ); + + gtk_window_set_child( GTK_WINDOW( self ), self->limit_container ); + gtk_widget_add_controller( GTK_WIDGET( self ), window_controller ); + gtk_window_set_title( GTK_WINDOW( self ), "Foobar Launcher" ); + gtk_widget_add_css_class( GTK_WIDGET( self ), "launcher" ); + gtk_layer_init_for_window( GTK_WINDOW( self ) ); + gtk_layer_set_keyboard_mode( GTK_WINDOW( self ), GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE ); + gtk_layer_set_namespace( GTK_WINDOW( self ), "foobar-launcher" ); + gtk_layer_set_anchor( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_TOP, TRUE ); + g_signal_connect( self, "show", G_CALLBACK( foobar_launcher_handle_show ), self ); +} + +// +// Instance cleanup for the launcher. +// +void foobar_launcher_finalize( GObject* object ) +{ + FoobarLauncher* self = (FoobarLauncher*)object; + + g_clear_signal_handler( &self->config_handler_id, self->configuration_service ); + g_clear_object( &self->filter_model ); + g_clear_object( &self->quick_answer_model ); + g_clear_object( &self->flatten_model ); + g_clear_object( &self->selection_model ); + g_clear_object( &self->application_service ); + g_clear_object( &self->quick_answer_service ); + g_clear_object( &self->configuration_service ); + g_clear_pointer( &self->search_terms, g_strfreev ); + + G_OBJECT_CLASS( foobar_launcher_parent_class )->finalize( object ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------------------------------------------------- + +// +// Create a new launcher instance. +// +FoobarLauncher* foobar_launcher_new( + FoobarApplicationService* application_service, + FoobarQuickAnswerService* quick_answer_service, + FoobarConfigurationService* configuration_service ) +{ + g_return_val_if_fail( FOOBAR_IS_APPLICATION_SERVICE( application_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_QUICK_ANSWER_SERVICE( quick_answer_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_CONFIGURATION_SERVICE( configuration_service ), NULL ); + + FoobarLauncher* self = g_object_new( FOOBAR_TYPE_LAUNCHER, NULL ); + self->application_service = g_object_ref( application_service ); + self->quick_answer_service = g_object_ref( quick_answer_service ); + self->configuration_service = g_object_ref( configuration_service ); + + // Set up the result list view's source model. + + GListModel* source_model = foobar_application_service_get_items( self->application_service ); + gtk_filter_list_model_set_model( self->filter_model, g_object_ref( source_model ) ); + + // Apply the configuration and subscribe to changes. + + FoobarConfiguration const* config = foobar_configuration_service_get_current( self->configuration_service ); + foobar_launcher_apply_configuration( self, foobar_configuration_get_launcher( config ) ); + self->config_handler_id = g_signal_connect( + self->configuration_service, + "notify::current", + G_CALLBACK( foobar_launcher_handle_config_change ), + self ); + + return self; +} + +// +// Apply the launcher configuration provided by the configuration service. +// +void foobar_launcher_apply_configuration( + FoobarLauncher* self, + FoobarLauncherConfiguration const* config ) +{ + g_return_if_fail( FOOBAR_IS_LAUNCHER( self ) ); + g_return_if_fail( config != NULL ); + + gtk_window_set_default_size( GTK_WINDOW( self ), foobar_launcher_configuration_get_width( config ), 2 ); + gtk_layer_set_margin( + GTK_WINDOW( self ), + GTK_LAYER_SHELL_EDGE_TOP, + foobar_launcher_configuration_get_position( config ) ); + foobar_limit_container_set_max_height( + FOOBAR_LIMIT_CONTAINER( self->limit_container ), + foobar_launcher_configuration_get_max_height( config ) ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Signal Handlers +// --------------------------------------------------------------------------------------------------------------------- + +// +// Called when the user confirms the selection while the search input is focused. +// +// We forward the activation to the results list view. +// +void foobar_launcher_handle_search_activate( + GtkText* text, + gpointer userdata ) +{ + (void)text; + FoobarLauncher* self = (FoobarLauncher*)userdata; + + guint selection = gtk_single_selection_get_selected( self->selection_model ); + if ( selection != GTK_INVALID_LIST_POSITION ) + { + gtk_widget_activate_action( self->list_view, "list.activate-item", "u", selection ); + } +} + +// +// Called when the search query has changed. +// +// We update the tokenized search terms and then the result filter. +// +void foobar_launcher_handle_search_changed( + GtkEditable* editable, + gpointer userdata ) +{ + FoobarLauncher* self = (FoobarLauncher*)userdata; + + // Update quick answer. + + g_autoptr(FoobarQuickAnswer) answer = foobar_quick_answer_service_query( self->quick_answer_service, gtk_editable_get_text( editable ) ); + g_list_store_remove_all( self->quick_answer_model ); + if (answer) + { + g_list_store_append(self->quick_answer_model, answer); + } + + // Update filter. + + GStrvBuilder* terms_builder = g_strv_builder_new( ); + g_autofree gchar* query = g_strdup( gtk_editable_get_text( editable ) ); + gchar* query_start = query; + gchar* save; + gchar* token; + while ( ( token = strtok_r( query_start, " \t", &save ) ) ) + { + if ( *token ) { g_strv_builder_add( terms_builder, token ); } + query_start = NULL; + } + + g_clear_pointer( &self->search_terms, g_strfreev ); + self->search_terms = g_strv_builder_end( terms_builder ); + + GtkFilter* filter = gtk_filter_list_model_get_filter( self->filter_model ); + gtk_filter_changed( filter, GTK_FILTER_CHANGE_DIFFERENT ); +} + +// +// Handle keyboard events while the search input is focused. +// +// If this is a navigational key, the event is forwarded to the results list view. +// +gboolean foobar_launcher_handle_search_key( + GtkEventControllerKey* controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer userdata ) +{ + (void)keycode; + (void)state; + FoobarLauncher* self = (FoobarLauncher*)userdata; + + if ( foobar_launcher_is_navigation_key( keyval ) ) + { + gtk_widget_grab_focus( self->list_view ); + return gtk_event_controller_key_forward( controller, self->list_view ); + } + else + { + return FALSE; + } +} + +// +// Handle keyboard events while the list view is focused. +// +// If this is not a navigational key, the event is forwarded to the search input, which is also focused. +// +gboolean foobar_launcher_handle_list_key( + GtkEventControllerKey* controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer userdata ) +{ + (void)keycode; + (void)state; + FoobarLauncher* self = (FoobarLauncher*)userdata; + + if ( foobar_launcher_is_navigation_key( keyval ) ) + { + return FALSE; + } + else + { + gtk_text_grab_focus_without_selecting( GTK_TEXT( self->search_text ) ); + return gtk_event_controller_key_forward( controller, self->search_text ); + } +} + +// +// Handle general keyboard events for the window. +// +// This is used to allow the user to close the launcher by pressing the "Escape" key. +// +gboolean foobar_launcher_handle_window_key( + GtkEventControllerKey* controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer userdata ) +{ + (void)controller; + (void)keycode; + (void)state; + FoobarLauncher* self = (FoobarLauncher*)userdata; + + if ( keyval == GDK_KEY_Escape ) + { + gtk_widget_set_visible( GTK_WIDGET( self ), FALSE ); + return TRUE; + } + else + { + return FALSE; + } +} + +// +// Called by the result list view to create a widget for displaying an application item. +// +void foobar_launcher_handle_item_setup( + GtkListItemFactory* factory, + GtkListItem* list_item, + gpointer userdata ) +{ + (void)factory; + (void)userdata; + + // Set up layout. + + GtkWidget* icon = gtk_image_new( ); + gtk_widget_set_valign( icon, GTK_ALIGN_CENTER ); + gtk_widget_add_css_class( icon, "icon" ); + + GtkWidget* title = gtk_label_new( NULL ); + gtk_label_set_justify( GTK_LABEL( title ), GTK_JUSTIFY_LEFT ); + gtk_label_set_xalign( GTK_LABEL( title ), 0 ); + gtk_label_set_wrap( GTK_LABEL( title ), FALSE ); + gtk_label_set_ellipsize( GTK_LABEL( title ), PANGO_ELLIPSIZE_END ); + gtk_widget_add_css_class( title, "name" ); + + GtkWidget* description = gtk_label_new( NULL ); + gtk_label_set_justify( GTK_LABEL( description ), GTK_JUSTIFY_LEFT ); + gtk_label_set_xalign( GTK_LABEL( description ), 0 ); + gtk_label_set_wrap( GTK_LABEL( description ), FALSE ); + gtk_label_set_ellipsize( GTK_LABEL( description ), PANGO_ELLIPSIZE_END ); + gtk_widget_add_css_class( description, "description" ); + + GtkWidget* column = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + gtk_widget_set_hexpand( column, TRUE ); + gtk_widget_set_valign( column, GTK_ALIGN_CENTER ); + gtk_box_append( GTK_BOX( column ), title ); + gtk_box_append( GTK_BOX( column ), description ); + + GtkWidget* row = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); + gtk_widget_add_css_class( row, "item" ); + gtk_box_append( GTK_BOX( row ), icon ); + gtk_box_append( GTK_BOX( row ), column ); + + gtk_list_item_set_child( list_item, row ); + + // Set up bindings. + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + GtkExpression* icon_expr = gtk_property_expression_new( FOOBAR_TYPE_LAUNCHER_ITEM, item_expr, "icon" ); + gtk_expression_bind( gtk_expression_ref( icon_expr ), icon, "gicon", list_item ); + GtkExpression* visible_params[] = { icon_expr }; + GtkExpression* visible_expr = gtk_cclosure_expression_new( + G_TYPE_BOOLEAN, + NULL, + G_N_ELEMENTS( visible_params ), + visible_params, + G_CALLBACK( foobar_launcher_compute_icon_visible ), + NULL, + NULL ); + gtk_expression_bind( visible_expr, icon, "visible", list_item ); + } + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + GtkExpression* title_expr = gtk_property_expression_new( FOOBAR_TYPE_LAUNCHER_ITEM, item_expr, "title" ); + gtk_expression_bind( title_expr, title, "label", list_item ); + } + + { + GtkExpression* item_expr = gtk_property_expression_new( GTK_TYPE_LIST_ITEM, NULL, "item" ); + GtkExpression* description_expr = gtk_property_expression_new( FOOBAR_TYPE_LAUNCHER_ITEM, item_expr, "description" ); + gtk_expression_bind( gtk_expression_ref( description_expr ), description, "label", list_item ); + GtkExpression* visible_params[] = { description_expr }; + GtkExpression* visible_expr = gtk_cclosure_expression_new( + G_TYPE_BOOLEAN, + NULL, + G_N_ELEMENTS( visible_params ), + visible_params, + G_CALLBACK( foobar_launcher_compute_label_visible ), + NULL, + NULL ); + gtk_expression_bind( visible_expr, description, "visible", list_item ); + } +} + +// +// Called by the result list view when an application was selected. +// +void foobar_launcher_handle_item_activate( + GtkListView* view, + guint position, + gpointer userdata ) +{ + (void)view; + FoobarLauncher* self = (FoobarLauncher*)userdata; + + GListModel* list = G_LIST_MODEL( gtk_list_view_get_model( view ) ); + FoobarLauncherItem* item = g_list_model_get_item( list, position ); + foobar_launcher_item_activate( item ); + gtk_widget_set_visible( GTK_WIDGET( self ), FALSE ); +} + +// +// Signal handler called when the global configuration file has changed. +// +void foobar_launcher_handle_config_change( + GObject* object, + GParamSpec* pspec, + gpointer userdata ) +{ + (void)object; + (void)pspec; + FoobarLauncher* self = (FoobarLauncher*)userdata; + + FoobarConfiguration const* config = foobar_configuration_service_get_current( self->configuration_service ); + foobar_launcher_apply_configuration( self, foobar_configuration_get_launcher( config ) ); +} + +// +// Called before the launcher is presented. +// +// This is used to reset the UI state. +// +void foobar_launcher_handle_show( + GtkWidget* widget, + gpointer userdata ) +{ + (void)widget; + FoobarLauncher* self = (FoobarLauncher*)userdata; + + gtk_editable_set_text( GTK_EDITABLE( self->search_text ), "" ); + if ( g_list_model_get_n_items( G_LIST_MODEL( self->selection_model ) ) > 0 ) + { + gtk_list_view_scroll_to( + GTK_LIST_VIEW( self->list_view ), + 0, + GTK_LIST_SCROLL_FOCUS | GTK_LIST_SCROLL_SELECT, + NULL ); + } +} + +// --------------------------------------------------------------------------------------------------------------------- +// Value Converters +// --------------------------------------------------------------------------------------------------------------------- + +// +// Derive the visibility of an application icon from its value. +// +gboolean foobar_launcher_compute_icon_visible( + GtkExpression* expression, + GIcon* icon, + gpointer userdata ) +{ + (void)expression; + (void)userdata; + + return icon != NULL; +} + +// +// Derive the visibility of an application label from its value. +// +gboolean foobar_launcher_compute_label_visible( + GtkExpression* expression, + gchar const* label, + gpointer userdata ) +{ + (void)expression; + (void)userdata; + + return label != NULL; +} + +// +// Derive the visibility of the separator between search input and result list view from the number of results. +// +gboolean foobar_launcher_compute_separator_visible( + GtkExpression* expression, + guint item_count, + gpointer userdata ) +{ + (void)expression; + (void)userdata; + + return item_count > 0; +} + +// --------------------------------------------------------------------------------------------------------------------- +// Helper Methods +// --------------------------------------------------------------------------------------------------------------------- + +// +// Match a result item against the current search query. +// +gboolean foobar_launcher_filter_func( gpointer item, gpointer userdata ) +{ + FoobarLauncher* self = (FoobarLauncher*)userdata; + FoobarApplicationItem* app = (FoobarApplicationItem*)item; + + return foobar_application_item_match( app, (gchar const* const*)self->search_terms ); +} + +// +// Check if a key value is that of a navigation key (i.e., an arrow key). +// +gboolean foobar_launcher_is_navigation_key( guint keyval ) +{ + switch ( keyval ) + { + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_Left: + case GDK_KEY_Right: + return TRUE; + default: + return FALSE; + } +} |
