summaryrefslogtreecommitdiff
path: root/LibGUI
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-03-02 10:04:49 +0100
committerAndreas Kling <awesomekling@gmail.com>2019-03-02 10:10:06 +0100
commit596a5ce5a4dbd9e42d99d576a05a8958f04eb920 (patch)
tree71aa7cf0544232baaedf4ea4e8a7dea7a8e7e0fd /LibGUI
parent5c0fca0a955c91c41683941a9de159fa87e98446 (diff)
downloadserenity-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.cpp12
-rw-r--r--LibGUI/GAction.h8
-rw-r--r--LibGUI/GEventLoop.cpp27
-rw-r--r--LibGUI/GEventLoop.h8
-rw-r--r--LibGUI/GMenu.cpp10
-rw-r--r--LibGUI/GShortcut.cpp137
-rw-r--r--LibGUI/GShortcut.h42
-rw-r--r--LibGUI/Makefile1
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)