-
Notifications
You must be signed in to change notification settings - Fork 83
Global hotkeys #354
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Global hotkeys #354
Changes from 2 commits
77d3357
397eb1b
e9565ed
fdeb8ee
38febea
d0369d4
8cab24e
71841bf
e02402d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| function(add_imported_library library headers) | ||
| add_library(uiohook::uiohook UNKNOWN IMPORTED) | ||
| set_target_properties(uiohook::uiohook PROPERTIES | ||
| IMPORTED_LOCATION "${library}" | ||
| INTERFACE_INCLUDE_DIRECTORIES "${headers}" | ||
| ) | ||
|
|
||
| set(uiohook_FOUND 1 CACHE INTERNAL "uiohook found" FORCE) | ||
| set(uiohook_LIBRARIES "${library}" CACHE STRING "Path to uiohook library" FORCE) | ||
| set(uiohook_INCLUDES "${headers}" CACHE STRING "Path to uiohook headers" FORCE) | ||
| mark_as_advanced(FORCE uiohook_LIBRARIES) | ||
| mark_as_advanced(FORCE uiohook_INCLUDES) | ||
| endfunction() | ||
|
|
||
| if(uiohook_LIBRARIES AND uiohook_INCLUDES) | ||
| add_imported_library(${uiohook_LIBRARIES} ${uiohook_INCLUDES}) | ||
| return() | ||
| endif() | ||
|
|
||
| set(_uiohook_DIR "${CMAKE_CURRENT_SOURCE_DIR}/subprojects/libuiohook") | ||
|
|
||
| find_library(uiohook_LIBRARY_PATH | ||
| NAMES libuiohook uiohook | ||
| PATHS | ||
| "${_uiohook_DIR}/lib" | ||
| /usr/lib | ||
| ) | ||
|
|
||
| find_path(uiohook_HEADER_PATH | ||
| NAMES uiohook.h | ||
| PATHS | ||
| "${_uiohook_DIR}/include" | ||
| /usr/include | ||
| ) | ||
|
|
||
| include(FindPackageHandleStandardArgs) | ||
| find_package_handle_standard_args( | ||
| uiohook DEFAULT_MSG uiohook_LIBRARY_PATH uiohook_HEADER_PATH | ||
| ) | ||
|
|
||
| if(uiohook_FOUND) | ||
| add_imported_library( | ||
| "${uiohook_LIBRARY_PATH}" | ||
| "${uiohook_HEADER_PATH}" | ||
| ) | ||
| endif() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| #include "GlobalHotkeyManager.hpp" | ||
| #include <iostream> | ||
|
|
||
| // Singleton instance | ||
| GlobalHotkeyManager& GlobalHotkeyManager::instance() { | ||
| static GlobalHotkeyManager instance; | ||
| return instance; | ||
| } | ||
|
|
||
| GlobalHotkeyManager::GlobalHotkeyManager() : m_nextId(1) { | ||
| hook_set_dispatch_proc(&GlobalHotkeyManager::hook_callback); | ||
|
|
||
| // Run hook in separate thread to not block gtk | ||
| std::thread([this]() { | ||
| if (hook_run() != UIOHOOK_SUCCESS) { | ||
| std::cerr << "Failed to start libuiohook" << std::endl; | ||
| } | ||
| }).detach(); | ||
| } | ||
|
|
||
| GlobalHotkeyManager::~GlobalHotkeyManager() | ||
| { | ||
| hook_stop(); | ||
| } | ||
|
|
||
| struct GlobalHotkeyManager::Hotkey { | ||
| uint16_t keycode; | ||
| uint32_t modifiers; | ||
| HotkeyCallback callback; | ||
| }; | ||
|
|
||
| int GlobalHotkeyManager::registerHotkey(uint16_t keycode, uint32_t modifiers, HotkeyCallback callback) { | ||
| std::lock_guard<std::mutex> lock(m_mutex); | ||
| int id = m_nextId++; | ||
| m_callbacks[id] = { | ||
| .keycode = keycode, | ||
| .modifiers = modifiers, | ||
| .callback = callback | ||
| }; | ||
|
Comment on lines
+35
to
+69
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. C++20 sneaked in while I wasn’t looking! |
||
|
|
||
| return id; | ||
| } | ||
|
|
||
| GlobalHotkeyManager::Hotkey* GlobalHotkeyManager::find_hotkey(uint16_t keycode, uint32_t modifiers) { | ||
| auto it = std::find_if(m_callbacks.begin(), m_callbacks.end(), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldnt m_mutex be locked here too ? |
||
| [keycode, modifiers](const auto& pair) { | ||
| return pair.second.keycode == keycode && (modifiers & pair.second.modifiers); | ||
| }); | ||
|
|
||
| if (it != m_callbacks.end()) { | ||
| return &it->second; | ||
| } | ||
|
|
||
| return nullptr; | ||
| } | ||
|
|
||
| void GlobalHotkeyManager::unregisterHotkey(int id) { | ||
| std::lock_guard<std::mutex> lock(m_mutex); | ||
| m_callbacks.erase(id); | ||
| } | ||
|
|
||
| void GlobalHotkeyManager::hook_callback(uiohook_event* const event) { | ||
| GlobalHotkeyManager::instance().handleEvent(event); | ||
| } | ||
|
|
||
| void GlobalHotkeyManager::handleEvent(uiohook_event* const event) { | ||
| if (event->type == EVENT_KEY_PRESSED) { | ||
| Hotkey *hk = find_hotkey(event->data.keyboard.keycode, event->mask); | ||
| if (hk != nullptr) { | ||
| std::lock_guard<std::mutex> lock(m_mutex); | ||
| hk->callback(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i assume this will still execute the callback on the thread created above which does the hook stuff |
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| #ifndef GLOBALHOTKEYMANAGER_H | ||
| #define GLOBALHOTKEYMANAGER_H | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <map> | ||
| #include <functional> | ||
| #include <mutex> | ||
| #include "uiohook.h" | ||
|
|
||
| // hotkey callback type | ||
| using HotkeyCallback = std::function<void(void)>; | ||
|
|
||
| class GlobalHotkeyManager | ||
| { | ||
| public: | ||
| static GlobalHotkeyManager& instance(); | ||
| int registerHotkey(uint16_t keycode, uint32_t modifiers, HotkeyCallback callback); | ||
| void unregisterHotkey(int id); | ||
|
|
||
| private: | ||
| GlobalHotkeyManager(); | ||
| ~GlobalHotkeyManager(); | ||
|
|
||
| // no copy/move | ||
| GlobalHotkeyManager(const GlobalHotkeyManager&) = delete; | ||
| GlobalHotkeyManager& operator=(const GlobalHotkeyManager&) = delete; | ||
|
|
||
| struct Hotkey; | ||
| Hotkey* find_hotkey(uint16_t keycode, uint32_t modifiers); | ||
|
|
||
| static void hook_callback(uiohook_event* const event); | ||
| void handleEvent(uiohook_event* const event); | ||
|
|
||
| std::mutex m_mutex; | ||
| std::map<int, Hotkey> m_callbacks; | ||
| int m_nextId; | ||
| }; | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| #include "misc/GlobalHotkeyManager.hpp" | ||
| #include "uiohook.h" | ||
| #include "util.hpp" | ||
| #ifdef WITH_VOICE | ||
|
|
||
|
|
@@ -11,6 +13,7 @@ | |
| #include "voicewindowaudiencelistentry.hpp" | ||
| #include "voicewindowspeakerlistentry.hpp" | ||
| #include "windows/voicesettingswindow.hpp" | ||
| #include "misc/GlobalHotkeyManager.hpp" | ||
|
|
||
| // clang-format on | ||
|
|
||
|
|
@@ -54,6 +57,16 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) | |
| m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged)); | ||
| m_deafen.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnDeafenChanged)); | ||
|
|
||
| // TODO: Load shortcuts from config file | ||
| m_mute_hotkey = GlobalHotkeyManager::instance().registerHotkey(VC_M, MASK_ALT, [this]() { | ||
| // This is probably stupid there is for sure some way to call event | ||
| // but I'm not really familiar with gtk and this works well. | ||
| m_mute.set_active( !m_mute.get_active() ); | ||
| }); | ||
| m_deafen_hotkey = GlobalHotkeyManager::instance().registerHotkey(VC_D, MASK_ALT, [this]() { | ||
| m_deafen.set_active( !m_deafen.get_active() ); | ||
| }); | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. im not entirely sure where the best place to do this would be (maybe just in the main |
||
| m_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); | ||
| m_scroll.set_hexpand(true); | ||
| m_scroll.set_vexpand(true); | ||
|
|
@@ -271,6 +284,11 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) | |
| UpdateStageCommand(); | ||
| } | ||
|
|
||
| VoiceWindow::~VoiceWindow() { | ||
| GlobalHotkeyManager::instance().unregisterHotkey(m_mute_hotkey); | ||
| GlobalHotkeyManager::instance().unregisterHotkey(m_deafen_hotkey); | ||
| } | ||
|
|
||
| void VoiceWindow::SetUsers(const std::unordered_set<Snowflake> &user_ids) { | ||
| auto &discord = Abaddon::Get().GetDiscordClient(); | ||
| const auto me = discord.GetUserData().ID; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should use spdlog. the "ui" logger is probably fine for this