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
48 changes: 48 additions & 0 deletions .github/scripts/update-readme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python3

import copy
import json
import re

README_FILES = [
"README.md",
]

VARIABLE_JSON_FILES = [
"src/libs/Karabiner-DriverKit-VirtualHIDDevice/version.json",
"src/libs/Karabiner-DriverKit-VirtualHIDDevice/elements-version.json",
]

REPLACEMENTS = {
"package_version": r"\d+\.\d+\.\d+",
"elements_version": r"\d+\.\d+\.\d+",
}

TAG_REGEX_FORMAT = r"(<!--\s*replace:\s*{name}\s*-->)(`[^`]+`|\[[^]]+\]\([^)]+\))"

variables = {}
for var_file in VARIABLE_JSON_FILES:
with open(var_file) as f:
variables.update(json.load(f))

for readme_file in README_FILES:
with open(readme_file) as f:
content = f.read()
original_content = copy.copy(content)

for var_name, var_pattern in REPLACEMENTS.items():
pattern = TAG_REGEX_FORMAT.format(name=re.escape(var_name))
def replacer(m: re.Match) -> str:
tag = m[1]
tag_content = m[2]
try:
tag_content = re.sub(var_pattern, variables[var_name], tag_content)
except KeyError as e:
raise ValueError(f"REPLACEMENTS variable {var_name} does not appear in any of the JSON files: {VARIABLE_JSON_FILES!r}") from e
return tag + tag_content

content = re.sub(pattern, replacer, content)

if content != original_content:
with open(readme_file, "w") as f:
f.write(content)
42 changes: 42 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Push

on:
push:
branches:
- main
- ci_test
paths:
- 'README.md'
- 'src/libs/Karabiner-DriverKit-VirtualHIDDevice/version.json'
- 'src/libs/Karabiner-DriverKit-VirtualHIDDevice/elements-version.json'

jobs:
update_readme:
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive

- name: Update README
id: update_readme
run: |
./.github/scripts/update-readme.py
if git diff --quiet; then echo worktree=UNCHANGED; else echo worktree=CHANGED; fi >> "$GITHUB_OUTPUT"

- name: Create PR if README is changed
if: steps.update_readme.outputs.worktree == 'CHANGED'
# email and name via https://api.github.com/users/github-actions%5Bbot%5D
# See https://github.com/actions/checkout?tab=readme-ov-file#push-a-commit-using-the-built-in-token
run: |
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
# Comment out `git switch` and `gh pr create` to push a commit
# directly to the branch that triggered this job (usually main)
git switch --create "$GITHUB_JOB/$GITHUB_RUN_ID"
git commit --all --message "Update variables in README"
git push --set-upstream origin
gh pr create --fill
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,13 @@ The values for the `device-id` context filters are obtained by looking for symli
### MacOS

The MacOS build depends on [Karabiner-Element's](https://karabiner-elements.pqrs.org) virtual device driver.
One can install it either directly from [Karabiner-DriverKit-VirtualHIDDevice](https://github.com/pqrs-org/Karabiner-DriverKit-VirtualHIDDevice/releases), or along with [Karabiner Elements](https://github.com/pqrs-org/Karabiner-Elements/releases).
One can install it either directly from <!--replace: package_version-->[Karabiner-DriverKit-VirtualHIDDevice](https://github.com/pqrs-org/Karabiner-DriverKit-VirtualHIDDevice/releases#release-v99.99.99), or along with <!--replace: elements_version-->[Karabiner Elements](https://github.com/pqrs-org/Karabiner-Elements/releases#release-v99.99.99).

The maximum supported versions of Karabiner-Element currently are:
| Package | Version |
|--------------------------------------|---------------------------------------------|
| Karabiner-DriverKit-VirtualHIDDevice | <!--replace: package_version-->`99.99.99` |
| Karabiner-Elements | <!--replace: elements_version-->`99.99.99` |

A [Homebrew](https://brew.sh) formula is provided for building and installing keymapper:
```ini
Expand Down
43 changes: 25 additions & 18 deletions keymapper.conf
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ T >> G
Y >> J
U >> L
I >> U
O >> Z
Z >> Y
Shift{O} >> 'Y'
O >> 'y'
Shift{Z} >> 'Z'
Z >> 'z'
Shift{P} >> ':'
P >> ';'
S >> R
Expand All @@ -136,11 +138,11 @@ Shift{BracketLeft} >> '{'
BracketLeft >> '['
Shift{BracketRight} >> '}'
BracketRight >> ']'
Shift{Backquote} >> Shift{Equal} Space # dead key '`'
Shift{Backquote} >> '`'
Backquote >> '~'
Shift{2} >> '@'
Shift{3} >> '#'
Shift{6} >> Backquote Space # dead key '^'
Shift{6} >> '^'
Shift{7} >> '&'
Shift{8} >> '*'
Shift{9} >> '('
Expand Down Expand Up @@ -171,8 +173,8 @@ prev_tab >> Control{PageDown}
edit_copy >> Control{C}
edit_cut >> Control{X}
edit_paste >> Control{V}
edit_undo >> Control{Y}
edit_redo >> Control{Z}
edit_undo >> Control{'z'}
edit_redo >> (Control Shift){'z'}
find >> Control{F}
build >> Control{B}
debug_step_over >> F10
Expand All @@ -191,10 +193,16 @@ edit_rename >> F2
edit_cut >> Command{X}
edit_copy >> Command{C}
edit_paste >> Command{V}
edit_undo >> Command{Y}
edit_redo >> (Command Shift){Y}
edit_undo >> Command{'z'}
edit_redo >> (Command Shift){'z'}
find >> Command{F}

[system="Linux", title = "VncViewer"]
edit_paste >> $(keymapperctl --type "$(xclip -o -selection clipboard)")

[system="Linux", title = "Terminator"]
edit_paste >> Shift{Insert}

[system="Linux"]
lower_window >> (Alt Shift){PageDown}

Expand Down Expand Up @@ -230,25 +238,24 @@ edit_rename >> F2
[title="Qt Creator"]
open_file >> Control{K}
go_to_definition >> F2
edit_redo >> (Control Shift){Y}
edit_rename >> (Control Shift){R}

[title=/Code - OSS|Visual Studio Code/]
go_to_definition >> F12
navigate_back >> (Control Alt){Slash}
navigate_fore >> (Control Shift){Slash}
build >> (Control Shift){B}

[system="MacOS" class="Code"]
go_to_definition >> F12
navigate_back >> (Control){Slash}
navigate_fore >> (Control Shift){Slash}
navigate_back >> (Control){'-'}
navigate_fore >> (Control Shift){'-'}
build >> (Control Shift){B}

[title="Visual Studio"]
go_to_definition >> F12
navigate_back >> Control{Slash}
navigate_fore >> (Control Shift){Slash}
navigate_fore >> (Control Shift){'-'}
build >> (Control Shift){B}

[title=/Code - OSS|Visual Studio Code/]
go_to_definition >> F12
navigate_back >> (Control Alt){'-'}
navigate_fore >> (Control Shift){'-'}
build >> (Control Shift){B}

[title="Geany"]
Expand Down
2 changes: 1 addition & 1 deletion src/client/windows/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"elements_version": "15.9.0"
}
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