Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "subprojects/qrcodegen"]
path = subprojects/qrcodegen
url = https://github.com/nayuki/QR-Code-generator
[submodule "subprojects/libuiohook"]
path = subprojects/libuiohook
url = https://github.com/kwhat/libuiohook
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ endif ()
find_package(spdlog REQUIRED)
target_link_libraries(abaddon spdlog::spdlog)

find_package(uiohook QUIET)
if (NOT uiohook_FOUND)
message("uiohook was not found and will be included as a submodule")
add_subdirectory(subprojects/libuiohook)
target_link_libraries(abaddon uiohook)
else ()
target_link_libraries(abaddon uiohook)
endif ()

target_link_libraries(abaddon ${SQLite3_LIBRARIES})
target_link_libraries(abaddon ${GTKMM_LIBRARIES})
target_link_libraries(abaddon ${ZLIB_LIBRARY})
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Current features:
* Emojis<sup>2</sup>
* Thread support<sup>3</sup>
* Animated avatars, server icons, emojis (can be turned off)
* Global hotkeys support

1 - Abaddon tries its best (though is not perfect) to make Discord think it's a legitimate web client. Some of the
things done to do this
Expand Down Expand Up @@ -173,6 +174,7 @@ spam filter's wrath:
* [libopus](https://opus-codec.org/) (optional, required for voice)
* [libsodium](https://doc.libsodium.org/) (optional, required for voice)
* [rnnoise](https://gitlab.xiph.org/xiph/rnnoise) (optional, provided as submodule, noise suppression and improved VAD)
* [libuiohook](https://github.com/kwhat/libuiohook) (provided as submodule, global hotkeys)

### TODO:

Expand Down
46 changes: 46 additions & 0 deletions cmake/Finduiohook.cmake
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()
74 changes: 74 additions & 0 deletions src/misc/GlobalHotkeyManager.cpp
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;
Copy link
Copy Markdown
Member

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

}
}).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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the .a = b syntax is c++20 but this is a c++17 project

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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(),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
since the callbacks are doing stuff to the ui from the wrong thread there will invariably be crashes. Glib::Dispatcher exists to synchronize back to the main thread. heres an example of using dispatcher+queue+mutex to synchronize from websocket thread back to main thread
https://github.com/uowuo/abaddon/blob/ba15622a751b8e67b8b62d512ad713bc408f090d/src/remoteauth/remoteauthclient.cpp

}
}
}
40 changes: 40 additions & 0 deletions src/misc/GlobalHotkeyManager.hpp
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
18 changes: 18 additions & 0 deletions src/windows/voice/voicewindow.cpp
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

Expand All @@ -11,6 +13,7 @@
#include "voicewindowaudiencelistentry.hpp"
#include "voicewindowspeakerlistentry.hpp"
#include "windows/voicesettingswindow.hpp"
#include "misc/GlobalHotkeyManager.hpp"

// clang-format on

Expand Down Expand Up @@ -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() );
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 Abaddon singleton, but theres gonna need to be some other changes to actually propagate that to the right places) but here definitely wont work. for example this window is intended to be able to be closed without disconnecting from vc in which case the hotkey is unregistered

m_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
m_scroll.set_hexpand(true);
m_scroll.set_vexpand(true);
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/windows/voice/voicewindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
class VoiceWindow : public Gtk::Window {
public:
VoiceWindow(Snowflake channel_id);
~VoiceWindow();

private:
void SetUsers(const std::unordered_set<Snowflake> &user_ids);
Expand Down Expand Up @@ -49,7 +50,9 @@ class VoiceWindow : public Gtk::Window {
Gtk::Box m_controls;

Gtk::CheckButton m_mute;
int m_mute_hotkey;
Gtk::CheckButton m_deafen;
int m_deafen_hotkey;

Gtk::ScrolledWindow m_scroll;
Gtk::VBox m_listing;
Expand Down
1 change: 1 addition & 0 deletions subprojects/libuiohook
Submodule libuiohook added at 2a2d40