Skip to content

Commit cb5c961

Browse files
authored
Add Plover HID support based on plover-machine-hid plugin (#1777)
1 parent 28d5425 commit cb5c961

File tree

28 files changed

+988
-27
lines changed

28 files changed

+988
-27
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ jobs:
656656
key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/dist.txt', 'reqs/dist_extra_gui_qt.txt', 'reqs/test.txt') }}
657657

658658
- name: Install system dependencies
659-
run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev libxkbcommon-x11-0
659+
run: apt_get_install cmake libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev libxkbcommon-x11-0
660660

661661
- name: Setup Python environment
662662
run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/dist_extra_gui_qt.txt -r reqs/test.txt
@@ -859,7 +859,7 @@ jobs:
859859
key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/build.txt', 'reqs/setup.txt', 'reqs/dist_*.txt', 'linux/appimage/deps.sh') }}
860860

861861
- name: Install system dependencies
862-
run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev libxkbcommon-x11-0
862+
run: apt_get_install cmake libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev libxkbcommon-x11-0
863863

864864
- name: Setup Python environment
865865
run: setup_python_env -c reqs/constraints.txt -r reqs/build.txt -r reqs/setup.txt

.github/workflows/ci/workflow_template.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ jobs:
146146
<% endif %>
147147
<% if j.type in ['build', 'test_gui_qt'] and j.os == 'Linux' %>
148148
- name: Install system dependencies
149-
run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev libxkbcommon-x11-0
149+
run: apt_get_install cmake libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev libxkbcommon-x11-0
150150

151151
<% endif %>
152152
<% if j.type != 'notarize' %>

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ include test/*.py
3333
include test/gui_qt/*.py
3434
include tox.ini
3535
include windows/*
36+
exclude dev/*
3637
exclude .pre-commit-config.yaml
3738
# without first including it, exluding .readthedocs.yml results in a warning when running locally
3839
include .readthedocs.yml

dev/build_hidapi.sh

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Build hidapi library locally for development.
5+
#
6+
# Usage:
7+
# ./dev/macos_dev_setup.sh
8+
# MACOS_UNIVERSAL2=0 ./dev/build_hidapi.sh # macOS: don't build universal2
9+
#
10+
# Resulting libs:
11+
# macOS: build/local-hidapi/macos/lib/libhidapi*.dylib
12+
# Linux: build/local-hidapi/linux/lib/libhidapi-hidraw.so
13+
# Windows: build/local-hidapi/windows/bin/hidapi.dll
14+
15+
. ./plover_build_utils/deps.sh
16+
. ./plover_build_utils/functions.sh
17+
18+
python='python'
19+
20+
MACOS_UNIVERSAL2="${MACOS_UNIVERSAL2:-1}"
21+
22+
# Resolve repo root relative to this script
23+
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
24+
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
25+
26+
WORK_DIR="${REPO_ROOT}/build/local-hidapi"
27+
SRC_DIR="${WORK_DIR}/src"
28+
BUILD_DIR="${WORK_DIR}/build"
29+
OUT_LIB_DIR_MAC="${WORK_DIR}/macos/lib"
30+
OUT_LIB_DIR_LNX="${WORK_DIR}/linux/lib"
31+
OUT_BIN_DIR_WIN="${WORK_DIR}/windows/bin"
32+
33+
# Clean and prep
34+
# Prepare directories
35+
mkdir -p "${SRC_DIR}" "${BUILD_DIR}"
36+
37+
OS="$(uname -s || true)"
38+
# If outputs already exist for the current platform, skip rebuilding.
39+
if [[ "${OS}" == "Darwin" ]]; then
40+
if ls "${OUT_LIB_DIR_MAC}"/libhidapi*.dylib >/dev/null 2>&1; then
41+
echo "hidapi found at ${OUT_LIB_DIR_MAC}; skipping build."
42+
exit 0
43+
fi
44+
elif [[ "${OS}" == "Linux" ]]; then
45+
if ls "${OUT_LIB_DIR_LNX}"/libhidapi-hidraw.so* >/dev/null 2>&1; then
46+
echo "hidapi found at ${OUT_LIB_DIR_LNX}; skipping build."
47+
exit 0
48+
fi
49+
elif [[ "${OS}" == MINGW* || "${OS}" == MSYS* || "${OS}" == CYGWIN* || "${OS}" == "Windows_NT" ]]; then
50+
if [ -f "${OUT_BIN_DIR_WIN}/hidapi.dll" ]; then
51+
echo "hidapi found at ${OUT_BIN_DIR_WIN}/hidapi.dll; skipping build."
52+
exit 0
53+
fi
54+
fi
55+
56+
# Clean and prep for a fresh build
57+
rm -rf "${SRC_DIR}" "${BUILD_DIR}"
58+
mkdir -p "${SRC_DIR}" "${BUILD_DIR}"
59+
60+
echo "==> Downloading & unpacking hidapi ${hidapi_version}"
61+
fetch_hidapi "${SRC_DIR}" "${WORK_DIR}"
62+
63+
64+
if [[ "${OS}" == "Darwin" ]]; then
65+
echo "==> Configuring (macOS, IOHIDManager backend)"
66+
67+
# Shared fetch/build helpers (macOS)
68+
# shellcheck disable=SC1091
69+
. "${REPO_ROOT}/osx/build_hidapi.sh"
70+
71+
mkdir -p "${OUT_LIB_DIR_MAC}"
72+
# Architectures
73+
if [[ "${MACOS_UNIVERSAL2}" == "1" ]]; then
74+
OSX_ARCHES="x86_64;arm64"
75+
else
76+
ARCH="$(uname -m || true)"
77+
if [[ "${ARCH}" == "arm64" || "${ARCH}" == "aarch64" ]]; then
78+
OSX_ARCHES="arm64"
79+
else
80+
OSX_ARCHES="x86_64"
81+
fi
82+
fi
83+
84+
cmake_build_macos "${SRC_DIR}" "${BUILD_DIR}" "${OSX_ARCHES}" "RelWithDebInfo"
85+
86+
# Find produced dylib
87+
DYLIB="$(/usr/bin/find "${BUILD_DIR}" -type f -name 'libhidapi*.dylib' -print -quit || true)"
88+
if [[ -z "${DYLIB}" ]]; then
89+
echo "Error: libhidapi*.dylib not found in build output." >&2
90+
exit 3
91+
fi
92+
93+
BASENAME="$(basename "${DYLIB}")"
94+
echo "==> Staging ${BASENAME}"
95+
cp -f "${DYLIB}" "${OUT_LIB_DIR_MAC}/${BASENAME}"
96+
97+
pushd "${OUT_LIB_DIR_MAC}" >/dev/null
98+
ln -sf "${BASENAME}" libhidapi.dylib || true
99+
popd >/dev/null
100+
101+
echo
102+
echo "✅ Built macOS hidapi at: ${OUT_LIB_DIR_MAC}/${BASENAME}"
103+
104+
elif [[ "${OS}" == "Linux" ]]; then
105+
echo "==> Configuring (Linux, hidraw backend)"
106+
mkdir -p "${OUT_LIB_DIR_LNX}"
107+
108+
# Shared fetch/build helpers (Linux)
109+
# shellcheck disable=SC1091
110+
. "${REPO_ROOT}/linux/build_hidapi.sh"
111+
112+
cmake_build_linux "${SRC_DIR}" "${BUILD_DIR}" "RelWithDebInfo"
113+
114+
SO="$(/usr/bin/find "${BUILD_DIR}" -type f -name 'libhidapi-hidraw.so*' -print -quit || true)"
115+
if [[ -z "${SO}" ]]; then
116+
echo "Error: libhidapi-hidraw.so not found in build output." >&2
117+
exit 3
118+
fi
119+
120+
BASENAME="$(basename "${SO}")"
121+
echo "==> Staging ${BASENAME}"
122+
cp -f "${SO}" "${OUT_LIB_DIR_LNX}/${BASENAME}"
123+
124+
pushd "${OUT_LIB_DIR_LNX}" >/dev/null
125+
[[ -e libhidapi-hidraw.so ]] || ln -sf "${BASENAME}" libhidapi-hidraw.so || true
126+
popd >/dev/null
127+
128+
echo
129+
echo "✅ Built Linux hidapi at: ${OUT_LIB_DIR_LNX}/${BASENAME}"
130+
131+
elif [[ "${OS}" == MINGW* || "${OS}" == MSYS* || "${OS}" == CYGWIN* || "${OS}" == "Windows_NT" ]]; then
132+
echo "==> Configuring (Windows, WinAPI backend)"
133+
mkdir -p "${OUT_BIN_DIR_WIN}"
134+
135+
# Shared fetch/build helpers (Windows)
136+
# shellcheck disable=SC1091
137+
. "${REPO_ROOT}/windows/build_hidapi.sh"
138+
139+
# Build shared DLL
140+
cmake_build_windows "${SRC_DIR}" "${BUILD_DIR}" "Release"
141+
142+
# Find the produced DLL (varies by generator)
143+
DLL="$(/usr/bin/find "${BUILD_DIR}" -type f \( -iname 'hidapi*.dll' -o -iname 'libhidapi*.dll' \) -print -quit 2>/dev/null || true)"
144+
if [[ -z "${DLL}" ]]; then
145+
echo "Error: hidapi DLL not found in build output." >&2
146+
exit 3
147+
fi
148+
149+
BASENAME="$(basename "${DLL}")"
150+
echo "==> Staging ${BASENAME}"
151+
cp -f "${DLL}" "${OUT_BIN_DIR_WIN}/hidapi.dll"
152+
153+
echo
154+
echo "✅ Built Windows hidapi at: ${OUT_BIN_DIR_WIN}/hidapi.dll"
155+
else
156+
echo "Unsupported OS: ${OS}. This helper currently supports macOS, Linux, and Windows." >&2
157+
exit 4
158+
fi

dev/write_hidapi_pth.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Write a .pth into site-packages that adds the local hidapi bin dir
4+
to the DLL search path on Windows via os.add_dll_directory.
5+
6+
Usage (optional): write_hidapi_pth.py <repo_dir> <env_sitepackages_dir>
7+
"""
8+
9+
from __future__ import annotations
10+
import sys
11+
from pathlib import Path
12+
13+
14+
def main(argv: list[str]) -> int:
15+
if sys.platform != "win32":
16+
# No-op outside Windows.
17+
return 0
18+
19+
if len(argv) >= 3:
20+
repo_dir = Path(argv[1]).resolve()
21+
site_packages = Path(argv[2]).resolve()
22+
else:
23+
repo_dir = Path.cwd()
24+
site_packages = Path(sys.prefix, "Lib", "site-packages")
25+
26+
dll_dir = repo_dir / "build" / "local-hidapi" / "windows" / "bin"
27+
if not dll_dir.is_dir():
28+
print(f"[hidapi .pth] skip: {dll_dir} does not exist", file=sys.stderr)
29+
return 0
30+
31+
site_packages.mkdir(parents=True, exist_ok=True)
32+
pth_path = site_packages / "plover_hidapi_add_dll_dir.pth"
33+
34+
# Keep it one line: .pth executes arbitrary Python on import.
35+
# Include defensive checks for directory existence and DLL loading
36+
code = (
37+
"import os,sys;"
38+
"p=r%r;"
39+
"os.add_dll_directory(p) if sys.platform=='win32' and hasattr(os,'add_dll_directory') and os.path.isdir(p) else None\n"
40+
% (str(dll_dir),)
41+
)
42+
43+
try:
44+
pth_path.write_text(code, encoding="utf-8")
45+
print(f"[hidapi .pth] wrote {pth_path}")
46+
return 0
47+
except Exception as e:
48+
print(f"[hidapi .pth] failed to write {pth_path}: {e}", file=sys.stderr)
49+
return 2
50+
51+
52+
if __name__ == "__main__":
53+
sys.exit(main(sys.argv))

doc/plugins.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ There are two ways to install plugins:
3939

4040
Plover comes with the following machine plugins installed:
4141

42-
- Keyboard
4342
- Gemini PR
44-
- TX Bolt
43+
- Keyboard
4544
- Passport
45+
- Plover HID
4646
- ProCAT
4747
- Stentura
48+
- TX Bolt
4849

4950
- **Systems**: Define new key layouts and theories. This lets Plover support
5051
stenographic layouts other than the standard American Stenotype system,

linux/appimage/build.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
set -e
44

5+
. ./plover_build_utils/deps.sh
56
. ./plover_build_utils/functions.sh
67

78
topdir="$PWD"
@@ -196,6 +197,34 @@ run "$linuxdeploy" \
196197
# Install Plover and dependencies.
197198
bootstrap_dist "$wheel"
198199

200+
# ------- Start: Build & bundle hidapi from source -------
201+
hidapi_src="$builddir/hidapi-src"
202+
hidapi_bld="$builddir/hidapi-build"
203+
204+
. ./linux/build_hidapi.sh
205+
206+
echo "Downloading and unpacking hidapi ${hidapi_version}"
207+
fetch_hidapi "$hidapi_src" "$builddir"
208+
209+
echo "Building hidapi…"
210+
cmake_build_linux "$hidapi_src" "$hidapi_bld" "Release"
211+
212+
# Locate the produced .so
213+
hidapi_so="$(find "$hidapi_bld" -name 'libhidapi-hidraw.so*' -type f -print -quit)"
214+
if [ -z "$hidapi_so" ] || [ ! -f "$hidapi_so" ]; then
215+
echo "Error: built libhidapi-hidraw.so not found." >&2
216+
exit 3
217+
fi
218+
219+
# Bundle into the AppDir's lib directory
220+
run cp -v "$hidapi_so" "$appdir/usr/lib/"
221+
base="$(basename "$hidapi_so")"
222+
# Add symlink for unversioned .so if needed
223+
if [ ! -e "$appdir/usr/lib/libhidapi-hidraw.so" ]; then
224+
ln -s "$base" "$appdir/usr/lib/libhidapi-hidraw.so"
225+
fi
226+
# ------- End: Build & bundle hidapi from source -------
227+
199228
# Trim the fat, second pass.
200229
run "$python" -m plover_build_utils.trim "$appdir" "$builddir/blacklist.txt"
201230

linux/build_hidapi.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
cmake_build_linux() {
2+
local src="$1" bld="$2" config="${3:-Release}"
3+
rm -rf "$bld"
4+
cmake -S "$src" -B "$bld" \
5+
-DBUILD_SHARED_LIBS=ON \
6+
-DCMAKE_BUILD_TYPE="$config"
7+
cmake --build "$bld" --config "$config" --parallel
8+
}

news.d/feature/1777.core.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for Plover HID protocol based on plover-machine-hid plugin by dnaq.

osx/build_hidapi.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
cmake_build_macos() {
2+
local src="$1" bld="$2" arches="$3" config="${4:-Release}"
3+
rm -rf "$bld"
4+
cmake -S "$src" -B "$bld" \
5+
-DBUILD_SHARED_LIBS=ON \
6+
-DCMAKE_BUILD_TYPE="$config" \
7+
-DCMAKE_OSX_ARCHITECTURES="$arches"
8+
cmake --build "$bld" --config "$config" --parallel
9+
}

0 commit comments

Comments
 (0)