diff --git a/data/sm.puri.phosh.osk.gschema.xml b/data/sm.puri.phosh.osk.gschema.xml index 2470063abb3ebbb3182c21cd8d8bfaabab940950..67889a085a3ecdf0b0fb8ece1af8a59dfa2d64b5 100644 --- a/data/sm.puri.phosh.osk.gschema.xml +++ b/data/sm.puri.phosh.osk.gschema.xml @@ -56,6 +56,8 @@ path='/sm/puri/phosh/osk/terminal/'> [ + + '<ctrl>', '<alt>', '<ctrl>r', 'Home', 'End', '<ctrl>w', '<alt>b', '<alt>f', diff --git a/doc/phosh-osk-stub.rst b/doc/phosh-osk-stub.rst index 500de19d44b74fe0adbd2a7ce06d985546dd16a0..5769f9df9affe460eb944448316dde7f8ad4700c 100644 --- a/doc/phosh-osk-stub.rst +++ b/doc/phosh-osk-stub.rst @@ -221,6 +221,8 @@ terminal layout. These are configured via the ``shortcuts`` GSetting gsettings set sm.puri.phosh.osk.Terminal shortcuts "['a', 'e', 'r']" For valid values see documentation of `gtk_accelerator_parse()`: https://docs.gtk.org/gtk3/func.accelerator_parse.html +One can also add plain ```` and ```` keys. These then act as latched keys +until the next regular key is pressed. IGNORING ACTIVATION ^^^^^^^^^^^^^^^^^^^ diff --git a/src/pos-input-surface.c b/src/pos-input-surface.c index 71abd3359ef2daee019fe7b902349d846f3fb540..663d3387b60c6e0c993560826a5cb266cb14e6be 100644 --- a/src/pos-input-surface.c +++ b/src/pos-input-surface.c @@ -116,6 +116,7 @@ struct _PosInputSurface { GtkWidget *last_layout; PosShortcutsBar *shortcuts_bar; PhoshOskFeatures osk_features; + GdkModifierType latched_modifiers; /* TODO: this should be an interface for different keyboard drivers */ PosVkDriver *keyboard_driver; @@ -174,6 +175,14 @@ on_swipe (GtkGestureSwipe *swipe, double velocity_x, double velocity_y, gpointer } } + +static void +pos_input_surface_unlatch_modifiers (PosInputSurface *self) +{ + pos_shortcuts_bar_unlatch_modifiers (self->shortcuts_bar); +} + + static void on_shortcut_activated (PosInputSurface *self, PosShortcut *shortcut, PosShortcutsBar *bar) { @@ -182,7 +191,20 @@ on_shortcut_activated (PosInputSurface *self, PosShortcut *shortcut, PosShortcut pos_vk_driver_key_press_gdk (self->keyboard_driver, pos_shortcut_get_key (shortcut), - pos_shortcut_get_modifiers (shortcut)); + pos_shortcut_get_modifiers (shortcut) | self->latched_modifiers); + pos_input_surface_unlatch_modifiers (self); +} + + +static void +on_latched_modifiers_changed (PosInputSurface *self, GParamSpec *pspec, PosShortcutsBar *bar) +{ + g_return_if_fail (POS_IS_INPUT_SURFACE (self)); + g_return_if_fail (POS_IS_SHORTCUTS_BAR (bar)); + + self->latched_modifiers = pos_shortcuts_bar_get_latched_modifiers (bar); + + g_debug ("Modifiers: 0x%x", self->latched_modifiers); } @@ -395,6 +417,16 @@ on_osk_key_symbol (PosInputSurface *self, const char *symbol, GtkWidget *osk_wid g_debug ("Key: '%s' symbol", symbol); + /* Latched modifiers, send as virtual-keyboard */ + if (self->latched_modifiers) { + PosKeycodeModifier modifier; + + modifier = pos_vk_driver_convert_modifiers (self->keyboard_driver, self->latched_modifiers); + pos_vk_driver_key_down (self->keyboard_driver, symbol, modifier); + pos_vk_driver_key_up (self->keyboard_driver, symbol); + pos_input_surface_unlatch_modifiers (self); + return; + } /* virtual-keyboard, no input method */ if (!pos_input_method_get_active (self->input_method)) { pos_vk_driver_key_down (self->keyboard_driver, symbol, POS_KEYCODE_MODIFIER_NONE); @@ -491,18 +523,6 @@ on_osk_popover_hidden (PosInputSurface *self) } -static void -clipboard_copy_activated (GSimpleAction *action, - GVariant *parameter, - gpointer data) -{ - PosInputSurface *self = POS_INPUT_SURFACE (data); - - pos_vk_driver_key_down (self->keyboard_driver, "c", POS_KEYCODE_MODIFIER_CTRL); - pos_vk_driver_key_up (self->keyboard_driver, "c"); -} - - static void clipboard_paste_activated (GSimpleAction *action, GVariant *parameter, @@ -703,6 +723,9 @@ select_layout_change_state (GSimpleAction *action, /* popdown popover right away to avoid flicker when switching layouts */ gtk_popover_popdown (self->menu_popup); + /* reset all letched modifiers */ + pos_input_surface_unlatch_modifiers (self); + g_variant_get (parameter, "&s", &layout); g_debug ("Layout '%s' selected", layout); @@ -1477,6 +1500,7 @@ pos_input_surface_class_init (PosInputSurfaceClass *klass) gtk_widget_class_bind_template_callback (widget_class, on_emoji_picked); gtk_widget_class_bind_template_callback (widget_class, on_emoji_picker_done); gtk_widget_class_bind_template_callback (widget_class, on_emoji_picker_delete_last); + gtk_widget_class_bind_template_callback (widget_class, on_latched_modifiers_changed); gtk_widget_class_bind_template_callback (widget_class, on_num_shortcuts_changed); gtk_widget_class_bind_template_callback (widget_class, on_osk_key_down); gtk_widget_class_bind_template_callback (widget_class, on_osk_key_symbol); @@ -1819,7 +1843,6 @@ on_completion_mode_changed (PosInputSurface *self, const char *key, GSettings *s static GActionEntry entries[] = { - { .name = "clipboard-copy", .activate = clipboard_copy_activated }, { .name = "clipboard-paste", .activate = clipboard_paste_activated }, { .name = "settings", .activate = settings_activated }, { .name = "select-layout", .parameter_type = "s", .state = "\"terminal\"", diff --git a/src/pos-shortcuts-bar.c b/src/pos-shortcuts-bar.c index 9983f819a0e0b8673caa84ff61dc72c1b0bead7a..332f3a6a5c7e0dc82b3015532d4d857f8fa81b40 100644 --- a/src/pos-shortcuts-bar.c +++ b/src/pos-shortcuts-bar.c @@ -1,7 +1,9 @@ /* - * Copyright (C) 2022 Guido Günther + * Copyright (C) 2022-2024 The Phosh Developers * * SPDX-License-Identifier: GPL-3.0-or-later + * + * Author: Guido Günther */ #define G_LOG_DOMAIN "pos-shortcuts-bar" @@ -14,6 +16,7 @@ enum { PROP_0, + PROP_LATCHED_MODIFIERS, PROP_NUM_SHORTCUTS, PROP_LAST_PROP, }; @@ -36,16 +39,22 @@ G_DEFINE_BOXED_TYPE (PosShortcut, pos_shortcut, pos_shortcut_ref, pos_shortcut_u /** * PosShortcutsBar: * - * ShortcutsBar stored in gsettings + * The shortcuts bar contains buttons that emit key combinations (like `c`). These + * can be configured via GSettings. + * + * If a combination is only a modifier like `` or `` then a toggle button is + * used to allow for latched modifiers. */ + typedef struct _PosShortcutsBar { - GtkBox parent; + GtkBox parent; - GtkFlowBox *shortcuts_box; - guint n_shortcuts; + GtkFlowBox *shortcuts_box; + guint n_shortcuts; + GdkModifierType latched; - GSettings *settings; + GSettings *settings; } PosShortcutsBar; G_DEFINE_TYPE (PosShortcutsBar, pos_shortcuts_bar, GTK_TYPE_BOX); @@ -112,13 +121,44 @@ on_btn_clicked (PosShortcutsBar *self, GtkButton *btn) } +static void +on_toggle_btn_active_changed (PosShortcutsBar *self, GParamSpec *pspec, GtkToggleButton *btn) +{ + PosShortcut *shortcut; + gboolean active; + + g_assert (POS_IS_SHORTCUTS_BAR (self)); + + active = gtk_toggle_button_get_active (btn); + shortcut = g_object_get_data (G_OBJECT (btn), "pos-shortcut"); + g_assert (shortcut); + + if (active) + self->latched |= shortcut->modifiers; + else + self->latched &= ~shortcut->modifiers; + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LATCHED_MODIFIERS]); +} + + static char * pos_accelerator_get_label (PosShortcut *shortcut) { char *label = NULL; + if (shortcut->key == 0) { + if (shortcut->modifiers == GDK_CONTROL_MASK) + label = "Ctrl"; + else if (shortcut->modifiers == GDK_MOD1_MASK) + label = "Alt"; + + if (label) + return g_strdup (label); + } + if (shortcut->modifiers) - return FALSE; + return NULL; switch (shortcut->key) { case GDK_KEY_Down: @@ -180,14 +220,27 @@ on_shortcuts_changed (PosShortcutsBar *self, } } - g_debug ("Adding shortcut: '%s'", shortcut->name); - btn = gtk_button_new_with_label (shortcut->name); child = gtk_flow_box_child_new (); - g_object_set_data_full (G_OBJECT (btn), "pos-shortcut", - g_steal_pointer (&shortcut), - (GDestroyNotify)pos_shortcut_unref); - g_signal_connect_swapped (btn, "clicked", G_CALLBACK (on_btn_clicked), self); - gtk_container_add (GTK_CONTAINER (child), btn); + if (shortcut->key) { + g_debug ("Adding shortcut: '%s'", shortcut->name); + btn = gtk_button_new_with_label (shortcut->name); + g_object_set_data_full (G_OBJECT (btn), "pos-shortcut", + g_steal_pointer (&shortcut), + (GDestroyNotify)pos_shortcut_unref); + g_signal_connect_swapped (btn, "clicked", G_CALLBACK (on_btn_clicked), self); + gtk_container_add (GTK_CONTAINER (child), btn); + } else { + g_debug ("Adding modifier: '%s'", shortcut->name); + btn = gtk_toggle_button_new_with_label (shortcut->name); + g_object_set_data_full (G_OBJECT (btn), "pos-shortcut", + g_steal_pointer (&shortcut), + (GDestroyNotify)pos_shortcut_unref); + g_signal_connect_swapped (btn, + "notify::active", + G_CALLBACK (on_toggle_btn_active_changed), + self); + gtk_container_add (GTK_CONTAINER (child), btn); + } gtk_widget_show_all (child); gtk_flow_box_insert (self->shortcuts_box, child, -1); } @@ -212,6 +265,9 @@ pos_shortcuts_bar_get_property (GObject *object, case PROP_NUM_SHORTCUTS: g_value_set_uint (value, pos_shortcuts_bar_get_num_shortcuts (self)); break; + case PROP_LATCHED_MODIFIERS: + g_value_set_flags (value, self->latched); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -250,6 +306,17 @@ pos_shortcuts_bar_class_init (PosShortcutsBarClass *klass) G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * PosShortcutsBar:latched-modifiers + * + * Currently latched modifiers. This only takes effect when there are modifier keys + * like plain `Ctrl` or `Alt` added to the shortcuts bar. + */ + props[PROP_LATCHED_MODIFIERS] = + g_param_spec_flags ("latched-modifiers", "", "", + GDK_TYPE_MODIFIER_TYPE, + 0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); @@ -304,3 +371,42 @@ pos_shortcuts_bar_get_num_shortcuts (PosShortcutsBar *self) return self->n_shortcuts; } + +GdkModifierType +pos_shortcuts_bar_get_latched_modifiers (PosShortcutsBar *self) +{ + g_return_val_if_fail (POS_IS_SHORTCUTS_BAR (self), 0); + + return self->latched; +} + + +static void +unlatch_modifiers (GtkWidget *widget) +{ + PosShortcut *shortcut; + GtkWidget *child; + + g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (widget)); + + child = gtk_bin_get_child (GTK_BIN (widget)); + if (!GTK_IS_TOGGLE_BUTTON (child)) + return; + + shortcut = g_object_get_data (G_OBJECT (child), "pos-shortcut"); + g_return_if_fail (shortcut); + g_return_if_fail (!shortcut->key); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (child), FALSE); +} + + +void +pos_shortcuts_bar_unlatch_modifiers (PosShortcutsBar *self) +{ + g_return_if_fail (POS_IS_SHORTCUTS_BAR (self)); + + gtk_container_foreach (GTK_CONTAINER (self->shortcuts_box), + (GtkCallback) unlatch_modifiers, + NULL); +} diff --git a/src/pos-shortcuts-bar.h b/src/pos-shortcuts-bar.h index ab0dffb25de973a45d05b1a13fb38271ae1572d4..e992b7bb960bfbd2004f82458e127b8cacd5af54 100644 --- a/src/pos-shortcuts-bar.h +++ b/src/pos-shortcuts-bar.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Guido Günther + * Copyright (C) 2022-2024 The Phosh Developers * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -29,6 +29,7 @@ G_DECLARE_FINAL_TYPE (PosShortcutsBar, pos_shortcuts_bar, POS, SHORTCUTS_BAR, Gt PosShortcutsBar *pos_shortcuts_bar_new (void); guint pos_shortcuts_bar_get_num_shortcuts (PosShortcutsBar *self); - +GdkModifierType pos_shortcuts_bar_get_latched_modifiers (PosShortcutsBar *self); +void pos_shortcuts_bar_unlatch_modifiers (PosShortcutsBar *self); G_END_DECLS diff --git a/src/pos-vk-driver.c b/src/pos-vk-driver.c index f244be1a277ec7e68f969898cba214dafed86bbc..102616b622feff5b5b3c77a99a0d4349110c7e02 100644 --- a/src/pos-vk-driver.c +++ b/src/pos-vk-driver.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2022 Purism SPC + * 2023-2024 The Phosh Developers * * SPDX-License-Identifier: GPL-3.0-or-later * @@ -568,6 +569,8 @@ pos_vk_driver_key_down (PosVkDriver *self, const char *key, PosKeycodeModifier m vk_modifiers |= POS_VIRTUAL_KEYBOARD_MODIFIERS_SHIFT; if (modifiers & POS_KEYCODE_MODIFIER_CTRL) vk_modifiers |= POS_VIRTUAL_KEYBOARD_MODIFIERS_CTRL; + if (modifiers & POS_KEYCODE_MODIFIER_ALT) + vk_modifiers |= POS_VIRTUAL_KEYBOARD_MODIFIERS_ALT; if (modifiers & POS_KEYCODE_MODIFIER_ALTGR) vk_modifiers |= POS_VIRTUAL_KEYBOARD_MODIFIERS_ALTGR; @@ -784,3 +787,18 @@ pos_vk_driver_set_overlay_keymap (PosVkDriver *self, const char *const *symbols) g_clear_pointer (&self->layout_id, g_free); pos_virtual_keyboard_set_keymap (self->virtual_keyboard, keymap_str); } + + +PosKeycodeModifier +pos_vk_driver_convert_modifiers (PosVkDriver *self, GdkModifierType gdk_modifier) +{ + PosKeycodeModifier modifier = POS_KEYCODE_MODIFIER_NONE; + + if (gdk_modifier & GDK_CONTROL_MASK) + modifier |= POS_KEYCODE_MODIFIER_CTRL; + + if (gdk_modifier & GDK_MOD1_MASK) + modifier |= POS_KEYCODE_MODIFIER_ALT; + + return modifier; +} diff --git a/src/pos-vk-driver.h b/src/pos-vk-driver.h index 778a9b0004143069296e15fbee1a30c79f5f574b..6929d32d6333ce2a88d21e60e545cb846c880d94 100644 --- a/src/pos-vk-driver.h +++ b/src/pos-vk-driver.h @@ -20,17 +20,24 @@ typedef enum { POS_KEYCODE_MODIFIER_NONE = 0, POS_KEYCODE_MODIFIER_SHIFT = 1 << 0, POS_KEYCODE_MODIFIER_CTRL = 1 << 1, - POS_KEYCODE_MODIFIER_ALTGR = 1 << 2, + POS_KEYCODE_MODIFIER_ALT = 1 << 2, + POS_KEYCODE_MODIFIER_ALTGR = 1 << 3, } PosKeycodeModifier; PosVkDriver *pos_vk_driver_new (PosVirtualKeyboard *virtual_keyboard); void pos_vk_driver_key_down (PosVkDriver *virtual_keyboard, const char *key, PosKeycodeModifier modifier); -void pos_vk_driver_key_up (PosVkDriver *virtual_keyboard, const char *key); -void pos_vk_driver_key_press_gdk (PosVkDriver *self, guint gdk_keycode, GdkModifierType modifiers); -void pos_vk_driver_set_terminal_keymap (PosVkDriver *self); -void pos_vk_driver_set_keymap_symbols (PosVkDriver *self, const char * layout_id, const char * const *symbols); -void pos_vk_driver_set_overlay_keymap (PosVkDriver *self, const char * const *symbols); +void pos_vk_driver_key_up (PosVkDriver *virtual_keyboard, const char *key); +void pos_vk_driver_key_press_gdk (PosVkDriver *self, + guint gdk_keycode, + GdkModifierType modifiers); +void pos_vk_driver_set_terminal_keymap (PosVkDriver *self); +void pos_vk_driver_set_keymap_symbols (PosVkDriver *self, + const char *layout_id, + const char * const *symbols); +void pos_vk_driver_set_overlay_keymap (PosVkDriver *self, const char * const *symbols); +PosKeycodeModifier + pos_vk_driver_convert_modifiers (PosVkDriver *self, GdkModifierType modifier); G_END_DECLS diff --git a/src/stylesheet/common.css b/src/stylesheet/common.css index a2ebc348afc66572e18638adda8b0ac4f0940882..9a1f1642996490e86aa85f6fb6afacbb2f327684 100644 --- a/src/stylesheet/common.css +++ b/src/stylesheet/common.css @@ -138,6 +138,11 @@ pos-shortcuts-bar button:active { background: shade(@pos_key_bg_color, 0.8); } +/* Modifier button */ +pos-shortcuts-bar button.toggle:checked { + background: shade(@pos_key_bg_color, 0.6); +} + pos-shortcuts-bar scrollbar { background-color: transparent; border: none; diff --git a/src/ui/input-surface.ui b/src/ui/input-surface.ui index 2859603934288f43fe26b323d6b645f444a016bb..2fac0513852adc3ffea733fd072ec6c3b5073e29 100644 --- a/src/ui/input-surface.ui +++ b/src/ui/input-surface.ui @@ -23,6 +23,7 @@ False +