Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Windows")
set(SOURCES_SERVER ${SOURCES_SERVER}
src/server/windows/Devices.cpp
src/server/windows/Devices.h
src/server/windows/HookThread.cpp
src/server/windows/HookThread.h
src/server/windows/main.cpp
src/server/windows/interception.h
)
Expand Down
18 changes: 18 additions & 0 deletions src/client/ClientState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include <sstream>
#include <utility>

#if defined(_WIN32)
#include <WinSock2.h>
#endif

namespace {
KeySequence replace_logical_keys(KeySequence sequence) {
for (auto& event : sequence) {
Expand Down Expand Up @@ -158,6 +162,9 @@ bool ClientState::send_config() {
update_active_contexts(true);
send_active_contexts();
}
#if defined(_WIN32)
update_cursor_visibility();
#endif
return true;
}

Expand Down Expand Up @@ -303,3 +310,14 @@ bool ClientState::on_notify_message(const std::string& string) {
notify("%s", string.c_str());
return true;
}

#if defined(_WIN32)
void ClientState::update_cursor_visibility() {
CURSORINFO ci = { sizeof(CURSORINFO) };
if (GetCursorInfo(&ci))
{
m_server.send_set_virtual_key_state(Key::CursorVisible,
(ci.flags & CURSOR_SHOWING) ? KeyState::Down : KeyState::Up);
}
}
#endif
1 change: 1 addition & 0 deletions src/client/ClientState.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ClientState : public ServerPort::MessageHandler,
std::optional<Socket> accept_control_connection();
void read_control_messages();
void request_next_key_info();
void update_cursor_visibility();

protected:
// server messages
Expand Down
23 changes: 20 additions & 3 deletions src/client/windows/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ namespace {
bool g_session_changed;
HINSTANCE g_instance;
HWND g_window;
HWINEVENTHOOK g_cursor_event;
NOTIFYICONDATAW g_tray_icon;

void show_notification(const char* text) {
Expand Down Expand Up @@ -143,7 +144,7 @@ namespace {
socket && WSAAsyncSelect(*socket, g_window,
WM_APP_CONTROL_MESSAGE, (FD_READ | FD_CLOSE)) == 0)
return true;
error("Accepting keymapperctl connection failed");
verbose("Accepting keymapperctl connection failed");
return false;
}

Expand Down Expand Up @@ -197,11 +198,19 @@ namespace {
cursor_pos.x + 7, cursor_pos.y, 0, g_window, nullptr);
}

void CALLBACK cursor_event_proc(HWINEVENTHOOK hook, DWORD event,
HWND hwnd, LONG idObject, LONG idChild,
DWORD dwEventThread, DWORD dwmsEventTime) {
if (idObject == OBJID_CURSOR)
g_state.update_cursor_visibility();
}

LRESULT CALLBACK window_proc(HWND window, UINT message,
WPARAM wparam, LPARAM lparam) {

switch(message) {
case WM_DESTROY:
UnhookWinEvent(g_cursor_event);
PostQuitMessage(0);
return 0;

Expand Down Expand Up @@ -278,8 +287,10 @@ namespace {

case WM_TIMER: {
if (wparam == TIMER_UPDATE_CONTEXT) {
if (g_state.update_active_contexts())
g_state.send_active_contexts();
if (g_state.update_active_contexts()) {
g_state.send_active_contexts();
g_state.update_cursor_visibility();
}
validate_state();
}
else if (wparam == TIMER_UPDATE_CONFIG) {
Expand Down Expand Up @@ -399,6 +410,12 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, LPWSTR, int) {
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
HWND_MESSAGE, NULL, NULL, NULL);

g_cursor_event = SetWinEventHook(
EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE,
nullptr, cursor_event_proc,
0, 0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);

if (!connect())
return 1;

Expand Down
13 changes: 13 additions & 0 deletions src/config/ParseKeySequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,18 @@ void ParseKeySequence::check_ContextActive_usage() {
}
}

void ParseKeySequence::check_CursorVisible_usage() {
const auto it = std::find_if(m_sequence.begin(), m_sequence.end(),
[](const KeyEvent& e) { return e.key == Key::CursorVisible; });
if (it != m_sequence.end()) {
if (!m_is_input)
throw ParseError("CursorVisible is only allowed in input");
if (m_sequence.size() == 2)
throw ParseError("CursorVisible cannot be used alone");
m_sequence.pop_back();
}
}

void ParseKeySequence::parse(It it, const It end) {
auto is_no_might_match = false;
auto output_on_release = false;
Expand Down Expand Up @@ -520,4 +532,5 @@ void ParseKeySequence::parse(It it, const It end) {
throw ParseError("Virtual keys not allowed in no-might-match sequence");

check_ContextActive_usage();
check_CursorVisible_usage();
}
1 change: 1 addition & 0 deletions src/config/ParseKeySequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class ParseKeySequence {
void remove_all_from_end(KeyState state);
void convert_final_not_to_up();
void check_ContextActive_usage();
void check_CursorVisible_usage();

std::string_view m_string;
bool m_is_input{ };
Expand Down
1 change: 1 addition & 0 deletions src/config/get_key_name.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ const char* get_key_name(Key key) {

case Key::any: return "Any";
case Key::ContextActive: return "ContextActive";
case Key::CursorVisible: return "CursorVisible";

case Key::none:
case Key::timeout:
Expand Down
8 changes: 5 additions & 3 deletions src/runtime/Key.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ enum class Key : uint16_t {
first_auto_virtual = 0xF700, // 1024
last_virtual = 0xFAFF,

CursorVisible = 0xFB00,

first_mouse_button = ButtonLeft,
last_mouse_button = ButtonForward,

Expand Down Expand Up @@ -509,15 +511,15 @@ constexpr Key get_logical_key(size_t index) {
}

constexpr bool is_virtual_key(Key key) {
return (key >= Key::first_virtual && key <= Key::last_virtual);
return ((key >= Key::first_virtual && key <= Key::last_virtual) || key == Key::CursorVisible);
}

constexpr int get_virtual_key_index(Key key) {
return (*key - *Key::first_virtual);
return (*key - *Key::first_virtual);
}

constexpr int get_virtual_key_count() {
return (*Key::last_virtual - *Key::first_virtual + 1);
return (*Key::last_virtual - *Key::first_virtual + 2); // + 2 to include CursorVisible
}

constexpr Key get_virtual_key(int index) {
Expand Down
7 changes: 0 additions & 7 deletions src/server/ServerState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,6 @@ bool is_control_up(const KeyEvent& event) {
event.key == Key::ControlRight));
}

bool ServerState::send_buffer_has_mouse_events() const {
return std::any_of(m_send_buffer.begin(), m_send_buffer.end(),
[](const KeyEvent& event) {
return is_mouse_button(event.key) || is_mouse_wheel(event.key);
});
}

bool ServerState::flush_send_buffer() {
if (m_sending_key)
return true;
Expand Down
1 change: 0 additions & 1 deletion src/server/ServerState.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class ServerState : public ClientPort::MessageHandler {
void set_device_descs(std::vector<DeviceDesc> device_descs);
bool should_exit() const;
bool translate_input(KeyEvent input, int device_index);
bool send_buffer_has_mouse_events() const;
bool flush_send_buffer();
bool sending_key() const { return m_sending_key; }
const std::vector<StagePtr>& stages() const { return m_stage->stages(); }
Expand Down
141 changes: 141 additions & 0 deletions src/server/windows/HookThread.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

#include "HookThread.h"
#include "common/output.h"
#include <thread>
#include <future>
#include <mutex>
#include <utility>

namespace {
constexpr UINT WM_APP_CONFIGURE_HOOKS = WM_APP + 0;

KeyboardHookCallback g_keyboard_hook_callback;
MouseHookCallback g_mouse_hook_callback;
HHOOK g_keyboard_hook;
HHOOK g_mouse_hook;
std::mutex g_hook_thread_mutex;
std::thread g_hook_thread;
DWORD g_hook_thread_id;

struct HookConfig {
HINSTANCE instance;
KeyboardHookCallback keyboard_callback;
MouseHookCallback mouse_callback;
std::promise<void> completed;
};

LRESULT CALLBACK keyboard_hook_proc(int code, WPARAM wparam, LPARAM lparam) {
if (code == HC_ACTION) {
const auto& kbd = *reinterpret_cast<const KBDLLHOOKSTRUCT*>(lparam);
if (g_keyboard_hook_callback && g_keyboard_hook_callback(wparam, kbd))
return 1;
}
return CallNextHookEx(g_keyboard_hook, code, wparam, lparam);
}

LRESULT CALLBACK mouse_hook_proc(int code, WPARAM wparam, LPARAM lparam) {
if (code == HC_ACTION) {
const auto& ms = *reinterpret_cast<const MSLLHOOKSTRUCT*>(lparam);
if (g_mouse_hook_callback && g_mouse_hook_callback(wparam, ms))
return 1;
}
return CallNextHookEx(g_mouse_hook, code, wparam, lparam);
}

void unhook_devices_on_hook_thread() {
if (g_keyboard_hook)
UnhookWindowsHookEx(g_keyboard_hook);
g_keyboard_hook = nullptr;
g_keyboard_hook_callback = nullptr;

if (g_mouse_hook)
UnhookWindowsHookEx(g_mouse_hook);
g_mouse_hook = nullptr;
g_mouse_hook_callback = nullptr;
}

void hook_devices_on_hook_thread(const HookConfig& config) {
const auto keyboard_was_hooked = (g_keyboard_hook_callback != nullptr);
const auto mouse_was_hooked = (g_mouse_hook_callback != nullptr);

unhook_devices_on_hook_thread();

g_keyboard_hook_callback = config.keyboard_callback;
if (g_keyboard_hook_callback)
g_keyboard_hook = SetWindowsHookExW(
WH_KEYBOARD_LL, keyboard_hook_proc, config.instance, 0);

g_mouse_hook_callback = config.mouse_callback;
if (g_mouse_hook_callback)
g_mouse_hook = SetWindowsHookExW(
WH_MOUSE_LL, mouse_hook_proc, config.instance, 0);

const auto keyboard_is_hooked = (g_keyboard_hook != nullptr);
const auto mouse_is_hooked = (g_mouse_hook != nullptr);
if (keyboard_is_hooked != keyboard_was_hooked)
verbose(keyboard_is_hooked ? "Hooked keyboard" : "Unhooked keyboard");
if (mouse_is_hooked != mouse_was_hooked)
verbose(mouse_is_hooked ? "Hooked mouse" : "Unhooked mouse");
}

void hook_thread_main(std::promise<void> ready) {
g_hook_thread_id = GetCurrentThreadId();

// create message queue
auto message = MSG{ };
PeekMessageW(&message, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
ready.set_value();

while (GetMessageW(&message, nullptr, 0, 0) > 0)
if (message.message == WM_APP_CONFIGURE_HOOKS) {
auto& config = *reinterpret_cast<HookConfig*>(message.lParam);
hook_devices_on_hook_thread(config);
config.completed.set_value();
}

unhook_devices_on_hook_thread();
}

void start_hook_thread() {
if (g_hook_thread.joinable())
return;

auto ready = std::promise<void>();
auto ready_future = ready.get_future();
g_hook_thread = std::thread(hook_thread_main, std::move(ready));
ready_future.get();
}

void configure_devices(HookConfig config) {
auto completed = config.completed.get_future();
if (!PostThreadMessageW(g_hook_thread_id, WM_APP_CONFIGURE_HOOKS, 0,
reinterpret_cast<LPARAM>(&config)))
return;

completed.get();
}
} // namespace

void unhook_devices() {
const auto lock = std::lock_guard(g_hook_thread_mutex);
if (g_hook_thread.joinable())
configure_devices({ });
}

void hook_devices(HINSTANCE instance,
KeyboardHookCallback keyboard_hook_callback,
MouseHookCallback mouse_hook_callback) {
const auto lock = std::lock_guard(g_hook_thread_mutex);
start_hook_thread();
configure_devices({ instance, keyboard_hook_callback, mouse_hook_callback });
}

void shutdown_hook_thread() {
const auto lock = std::lock_guard(g_hook_thread_mutex);
if (!g_hook_thread.joinable())
return;

PostThreadMessageW(g_hook_thread_id, WM_QUIT, 0, 0);
g_hook_thread.join();
g_hook_thread_id = 0;
}
11 changes: 11 additions & 0 deletions src/server/windows/HookThread.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

#include "common/windows/win.h"

using KeyboardHookCallback = bool(*)(WPARAM, const KBDLLHOOKSTRUCT& kbd);
using MouseHookCallback = bool(*)(WPARAM, const MSLLHOOKSTRUCT& ms);

void hook_devices(HINSTANCE instance,
KeyboardHookCallback keyboard_hook_callback,
MouseHookCallback mouse_hook_callback);
void unhook_devices();
void shutdown_hook_thread();
Loading