diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-03-02 10:04:49 +0100 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-03-02 10:10:06 +0100 |
commit | 596a5ce5a4dbd9e42d99d576a05a8958f04eb920 (patch) | |
tree | 71aa7cf0544232baaedf4ea4e8a7dea7a8e7e0fd /LibGUI | |
parent | 5c0fca0a955c91c41683941a9de159fa87e98446 (diff) | |
download | serenity-596a5ce5a4dbd9e42d99d576a05a8958f04eb920.zip |
LibGUI+WindowServer: Add app-global keyboard shortcuts.
This patch adds a GShortcut class. Each GAction can have a GShortcut which
will cause the event loop to listen for that key combination app-globally
and activate the event in case it's pressed.
The shortcut will also be displayed when the action is added to a menu.
Use this to hook up Alt+Up with the "open parent directory" action in the
FileManager app. :^)
Diffstat (limited to 'LibGUI')
-rw-r--r-- | LibGUI/GAction.cpp | 12 | ||||
-rw-r--r-- | LibGUI/GAction.h | 8 | ||||
-rw-r--r-- | LibGUI/GEventLoop.cpp | 27 | ||||
-rw-r--r-- | LibGUI/GEventLoop.h | 8 | ||||
-rw-r--r-- | LibGUI/GMenu.cpp | 10 | ||||
-rw-r--r-- | LibGUI/GShortcut.cpp | 137 | ||||
-rw-r--r-- | LibGUI/GShortcut.h | 42 | ||||
-rw-r--r-- | LibGUI/Makefile | 1 |
8 files changed, 237 insertions, 8 deletions
diff --git a/LibGUI/GAction.cpp b/LibGUI/GAction.cpp index aa6e52c5df..27321c2ff4 100644 --- a/LibGUI/GAction.cpp +++ b/LibGUI/GAction.cpp @@ -1,4 +1,5 @@ #include <LibGUI/GAction.h> +#include <LibGUI/GEventLoop.h> GAction::GAction(const String& text, const String& custom_data, Function<void(const GAction&)> on_activation_callback) : on_activation(move(on_activation_callback)) @@ -19,8 +20,19 @@ GAction::GAction(const String& text, RetainPtr<GraphicsBitmap>&& icon, Function< { } +GAction::GAction(const String& text, const GShortcut& shortcut, RetainPtr<GraphicsBitmap>&& icon, Function<void(const GAction&)> on_activation_callback) + : on_activation(move(on_activation_callback)) + , m_text(text) + , m_icon(move(icon)) + , m_shortcut(shortcut) +{ + GEventLoop::register_action_with_shortcut(Badge<GAction>(), *this); +} + GAction::~GAction() { + if (m_shortcut.is_valid()) + GEventLoop::unregister_action_with_shortcut(Badge<GAction>(), *this); } void GAction::activate() diff --git a/LibGUI/GAction.h b/LibGUI/GAction.h index 305abc59e3..959995d38b 100644 --- a/LibGUI/GAction.h +++ b/LibGUI/GAction.h @@ -5,6 +5,7 @@ #include <AK/Retainable.h> #include <AK/Retained.h> #include <SharedGraphics/GraphicsBitmap.h> +#include <LibGUI/GShortcut.h> class GAction : public Retainable<GAction> { public: @@ -20,9 +21,14 @@ public: { return adopt(*new GAction(text, move(icon), move(callback))); } + static Retained<GAction> create(const String& text, const GShortcut& shortcut, RetainPtr<GraphicsBitmap>&& icon, Function<void(const GAction&)> callback) + { + return adopt(*new GAction(text, shortcut, move(icon), move(callback))); + } ~GAction(); String text() const { return m_text; } + GShortcut shortcut() const { return m_shortcut; } String custom_data() const { return m_custom_data; } const GraphicsBitmap* icon() const { return m_icon.ptr(); } @@ -32,11 +38,13 @@ public: private: GAction(const String& text, Function<void(const GAction&)> = nullptr); + GAction(const String& text, const GShortcut&, RetainPtr<GraphicsBitmap>&& icon, Function<void(const GAction&)> = nullptr); GAction(const String& text, RetainPtr<GraphicsBitmap>&& icon, Function<void(const GAction&)> = nullptr); GAction(const String& text, const String& custom_data = String(), Function<void(const GAction&)> = nullptr); String m_text; String m_custom_data; RetainPtr<GraphicsBitmap> m_icon; + GShortcut m_shortcut; }; diff --git a/LibGUI/GEventLoop.cpp b/LibGUI/GEventLoop.cpp index c8aa0b2bf1..7e671e7ab7 100644 --- a/LibGUI/GEventLoop.cpp +++ b/LibGUI/GEventLoop.cpp @@ -18,18 +18,17 @@ //#define GEVENTLOOP_DEBUG +static HashMap<GShortcut, GAction*>* g_actions; static GEventLoop* s_mainGEventLoop; -void GEventLoop::initialize() -{ - s_mainGEventLoop = nullptr; -} - GEventLoop::GEventLoop() { if (!s_mainGEventLoop) s_mainGEventLoop = this; + if (!g_actions) + g_actions = new HashMap<GShortcut, GAction*>; + m_event_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); if (m_event_fd < 0) { perror("socket"); @@ -154,6 +153,14 @@ void GEventLoop::handle_key_event(const WSAPI_ServerMessage& event, GWindow& win #ifdef GEVENTLOOP_DEBUG dbgprintf("WID=%x KeyEvent character=0x%b\n", event.window_id, event.key.character); #endif + + unsigned modifiers = (event.key.alt * Mod_Alt) + (event.key.ctrl * Mod_Ctrl) + (event.key.shift * Mod_Shift); + auto it = g_actions->find(GShortcut(modifiers, (KeyCode)event.key.key)); + if (it != g_actions->end()) { + (*it).value->activate(); + return; + } + auto key_event = make<GKeyEvent>(event.type == WSAPI_ServerMessage::Type::KeyDown ? GEvent::KeyDown : GEvent::KeyUp, event.key.key); key_event->m_alt = event.key.alt; key_event->m_ctrl = event.key.ctrl; @@ -448,3 +455,13 @@ WSAPI_ServerMessage GEventLoop::sync_request(const WSAPI_ClientMessage& request, ASSERT(success); return response; } + +void GEventLoop::register_action_with_shortcut(Badge<GAction>, GAction& action) +{ + g_actions->set(action.shortcut(), &action); +} + +void GEventLoop::unregister_action_with_shortcut(Badge<GAction>, GAction& action) +{ + g_actions->remove(action.shortcut()); +} diff --git a/LibGUI/GEventLoop.h b/LibGUI/GEventLoop.h index 152c8b52f9..ab79a5ba4b 100644 --- a/LibGUI/GEventLoop.h +++ b/LibGUI/GEventLoop.h @@ -1,13 +1,14 @@ #pragma once -#include "GEvent.h" #include <AK/Badge.h> #include <AK/HashMap.h> #include <AK/OwnPtr.h> #include <AK/Vector.h> #include <AK/WeakPtr.h> #include <WindowServer/WSAPITypes.h> +#include <LibGUI/GEvent.h> +class GAction; class GObject; class GNotifier; class GWindow; @@ -23,8 +24,6 @@ public: static GEventLoop& main(); - static void initialize(); - bool running() const { return m_running; } int register_timer(GObject&, int milliseconds, bool should_reload); @@ -42,6 +41,9 @@ public: pid_t server_pid() const { return m_server_pid; } + static void register_action_with_shortcut(Badge<GAction>, GAction&); + static void unregister_action_with_shortcut(Badge<GAction>, GAction&); + private: void wait_for_event(); bool drain_messages_from_server(); diff --git a/LibGUI/GMenu.cpp b/LibGUI/GMenu.cpp index cb5ae39c1e..91a22655f9 100644 --- a/LibGUI/GMenu.cpp +++ b/LibGUI/GMenu.cpp @@ -68,6 +68,16 @@ int GMenu::realize_menu() ASSERT(action.text().length() < (ssize_t)sizeof(request.text)); strcpy(request.text, action.text().characters()); request.text_length = action.text().length(); + + if (action.shortcut().is_valid()) { + auto shortcut_text = action.shortcut().to_string(); + ASSERT(shortcut_text.length() < (ssize_t)sizeof(request.menu.shortcut_text)); + strcpy(request.menu.shortcut_text, shortcut_text.characters()); + request.menu.shortcut_text_length = shortcut_text.length(); + } else { + request.menu.shortcut_text_length = 0; + } + GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuItem); } } diff --git a/LibGUI/GShortcut.cpp b/LibGUI/GShortcut.cpp new file mode 100644 index 0000000000..7673133820 --- /dev/null +++ b/LibGUI/GShortcut.cpp @@ -0,0 +1,137 @@ +#include <LibGUI/GShortcut.h> +#include <AK/StringBuilder.h> + +static String to_string(KeyCode key) +{ + switch (key) { + case Key_Escape: return "Escape"; + case Key_Tab: return "Tab"; + case Key_Backspace: return "Backspace"; + case Key_Return: return "Return"; + case Key_Insert: return "Insert"; + case Key_Delete: return "Delete"; + case Key_PrintScreen: return "PrintScreen"; + case Key_SysRq: return "SysRq"; + case Key_Home: return "Home"; + case Key_End: return "End"; + case Key_Left: return "Left"; + case Key_Up: return "Up"; + case Key_Right: return "Right"; + case Key_Down: return "Down"; + case Key_PageUp: return "PageUp"; + case Key_PageDown: return "PageDown"; + case Key_Shift: return "Shift"; + case Key_Control: return "Control"; + case Key_Alt: return "Alt"; + case Key_CapsLock: return "CapsLock"; + case Key_NumLock: return "NumLock"; + case Key_ScrollLock: return "ScrollLock"; + case Key_F1: return "F1"; + case Key_F2: return "F2"; + case Key_F3: return "F3"; + case Key_F4: return "F4"; + case Key_F5: return "F5"; + case Key_F6: return "F6"; + case Key_F7: return "F7"; + case Key_F8: return "F8"; + case Key_F9: return "F9"; + case Key_F10: return "F10"; + case Key_F11: return "F11"; + case Key_F12: return "F12"; + case Key_Space: return "Space"; + case Key_ExclamationPoint: return "!"; + case Key_DoubleQuote: return "\""; + case Key_Hashtag: return "#"; + case Key_Dollar: return "$"; + case Key_Percent: return "%"; + case Key_Ampersand: return "&"; + case Key_Apostrophe: return "'"; + case Key_LeftParen: return "("; + case Key_RightParen: return ")"; + case Key_Asterisk: return "*"; + case Key_Plus: return "+"; + case Key_Comma: return ","; + case Key_Minus: return "-"; + case Key_Period: return ","; + case Key_Slash: return "/"; + case Key_0: return "0"; + case Key_1: return "1"; + case Key_2: return "2"; + case Key_3: return "3"; + case Key_4: return "4"; + case Key_5: return "5"; + case Key_6: return "6"; + case Key_7: return "7"; + case Key_8: return "8"; + case Key_9: return "9"; + case Key_Colon: return ":"; + case Key_Semicolon: return ";"; + case Key_LessThan: return "<"; + case Key_Equal: return "="; + case Key_GreaterThan: return ">"; + case Key_QuestionMark: return "?"; + case Key_AtSign: return "@"; + case Key_A: return "A"; + case Key_B: return "B"; + case Key_C: return "C"; + case Key_D: return "D"; + case Key_E: return "E"; + case Key_F: return "F"; + case Key_G: return "G"; + case Key_H: return "H"; + case Key_I: return "I"; + case Key_J: return "J"; + case Key_K: return "K"; + case Key_L: return "L"; + case Key_M: return "M"; + case Key_N: return "N"; + case Key_O: return "O"; + case Key_P: return "P"; + case Key_Q: return "Q"; + case Key_R: return "R"; + case Key_S: return "S"; + case Key_T: return "T"; + case Key_U: return "U"; + case Key_V: return "V"; + case Key_W: return "W"; + case Key_X: return "X"; + case Key_Y: return "Y"; + case Key_Z: return "Z"; + case Key_LeftBracket: return "["; + case Key_RightBracket: return "]"; + case Key_Backslash: return "\\"; + case Key_Circumflex: return "^"; + case Key_Underscore: return "_"; + case Key_LeftBrace: return "{"; + case Key_RightBrace: return "}"; + case Key_Pipe: return "|"; + case Key_Tilde: return "~"; + case Key_Backtick: return "`"; + + case Key_Invalid: return "Invalid"; + default: + ASSERT_NOT_REACHED(); + } +} + +String GShortcut::to_string() const +{ + Vector<String> parts; + + if (m_modifiers & Mod_Ctrl) + parts.append("Ctrl"); + if (m_modifiers & Mod_Shift) + parts.append("Shift"); + if (m_modifiers & Mod_Alt) + parts.append("Alt"); + + parts.append(::to_string(m_key)); + + StringBuilder builder; + for (int i = 0; i < parts.size(); ++i) { + builder.append(parts[i]); + if (i != parts.size() - 1) + builder.append('+'); + } + return builder.to_string(); +} diff --git a/LibGUI/GShortcut.h b/LibGUI/GShortcut.h new file mode 100644 index 0000000000..7675a6ef30 --- /dev/null +++ b/LibGUI/GShortcut.h @@ -0,0 +1,42 @@ +#pragma once + +#include <Kernel/KeyCode.h> +#include <AK/AKString.h> +#include <AK/Traits.h> + +class GShortcut { +public: + GShortcut() { } + GShortcut(unsigned modifiers, KeyCode key) + : m_modifiers(modifiers) + , m_key(key) + { + } + + bool is_valid() const { return m_key != KeyCode::Key_Invalid; } + unsigned modifiers() const { return m_modifiers; } + KeyCode key() const { return m_key; } + String to_string() const; + + bool operator==(const GShortcut& other) const + { + return m_modifiers == other.m_modifiers + && m_key == other.m_key; + } + +private: + unsigned m_modifiers { 0 }; + KeyCode m_key { KeyCode::Key_Invalid }; +}; + +namespace AK { + +template<> +struct Traits<GShortcut> { + static unsigned hash(const GShortcut& shortcut) + { + return pair_int_hash(shortcut.modifiers(), shortcut.key()); + } +}; + +} diff --git a/LibGUI/Makefile b/LibGUI/Makefile index 11a0bd563e..29e8b33006 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -31,6 +31,7 @@ LIBGUI_OBJS = \ GTableView.o \ GTableModel.o \ GVariant.o \ + GShortcut.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) |