Merge pull request #473 from spencerbull/cua-agent-keymap-remaps

Preserve user keyboard remaps in the Cua Hyprland plugin
This commit is contained in:
Spencer Bull authored and GitHub committed 2026-09-16 00:08:36 -05:00
commit 1f5b7a0a30
7 files changed
+1433 -108

No files matched your search

@@ -0,0 +1,57 @@
{
"files": {
"CMakeLists.txt": "7e874a595e1abd708cb0626bd9f0f58d79b4b4bbc6d99b45a0cbe1c5c53b43bc",
"LICENSE.md": "c0779290c1d4783169aa3dbfb55feb505e563ef8a004bbf55298ceffcfbda8d9",
"SOURCE-PROVENANCE.json": "54f514664c84e1358a435f29cd6befd5661b0b133d76997191c000b10f021a75",
"cmake/DetectHyprlandAPI.cmake": "216133ec0eb141c3696bf3770a23e63e46521c9e91a0245a7cb75c20a75ba2c5",
"cmake/VerifyRuntime.cmake": "5869f79a418e7aa6178d2b9166c36cd01c3093c2579b647624968244db57b761",
"include/cua_hyprland/protocol.hpp": "7051463b3c61a2dc93c388e66b6136b7bb524f8694350f249f9d2b5c55826493",
"include/cua_hyprland/session.hpp": "e6a968e4f2ac28122cb7413a0e318f6222d2a0a1b7b0c45f4ab419a78639ebea",
"include/cua_hyprland/status.hpp": "56a9656647c0f4eeb0c588cd4b98a77df198d1f90421f973e9a80e6802f61495",
"src/drag_geometry.hpp": "c5b783d15ff197f22938f08f8d176bab5d45544fe989d150f15cb99295acedb8",
"src/foreground_route.hpp": "4aa016c237b33c15e352a9f5f64bbbca7e1a9c1671ffd593a95a0d87994fb519",
"src/inject_server.cpp": "0935283580c50fcf0e4ad956f858885700536002b1d86d2f078da9c4404ee9e8",
"src/inject_server.hpp": "67de008b4d6983371207bb22a57bab154b1dedda9b38d1207d3ac8cb379382eb",
"src/input_client_deadline.hpp": "00a91a789ff698820607449ff7152e2fb50d0f315e0fa9c5c3855752bfffe2ef",
"src/input_experiment.cpp": "7017748c782b64b0bc1d257f4ade1a8d46fd19ce940ecc21f22d628614fcef37",
"src/input_experiment.hpp": "9da2ddab7f0de6e9cf02aea53e9119acef17ef8513af849bb5aa3d137ebd12c3",
"src/input_grant.hpp": "90b544b2f559bacd920b85ab5915f09ff201de3c05a052ce1b71ff71f767e6a6",
"src/keyboard_layout.hpp": "bc2ec039ac1974caebbb66fe4acb7a2a81832c9054b3aa644a9adcc8954a2467",
"src/owned_socket_path.hpp": "8e784656d944c700f3ded383c93a8cadf846dabdaa4cc12673bcb637c26d9fd1",
"src/passive_pointer_target.hpp": "ede36fd9fd6e95ae5923c751f12591084392be9d2270eb06ad64eb4f245169fa",
"src/plugin.cpp": "712fd73ef8e9046e0fd91531b7bf5da50ce37ccca9df174160137e1924a74b09",
"src/primary_trace.cpp": "e9468d1a3f3be2a90d47bf8c4a638ad8a60fe10b6297582ab7825751cb707aa9",
"src/primary_trace.hpp": "8d62535fb0b24a02bb80d9a8dcd540b39204afb2f3b4bcb5cabd5275c3b5eaa7",
"src/protocol.cpp": "bd083d65efb05e80946566dc535b1a6fadaa581c66ec327eff41796feba795b1",
"src/seat_lifetime.hpp": "386cf5c72c178f8eec0824f2a7d46fa755b0bb000861f9a6e00802f6b81fb779",
"src/session.cpp": "0105c7ba5f9e2dbdd9a21f48be0bc1f2bde930f6aa19f77e2216403d7790e4df",
"src/status.cpp": "e46e81e5e8ae3b1f50af5dcaa6c1776e236321c788fe61b402c9923c797b1270",
"tests/agent_keymap_test.py": "9b112f520a97a77a0d55f1009cd2594988d3c1e8e7bcaa39f213ffad2644dc56",
"tests/cmake-api/CMakeLists.txt": "6a66c8f98023f029998af917fcd3ec6b1388607bdf7acaf5bdfaef9141962685",
"tests/cmake-api/include/src/plugins/PluginAPI.hpp": "86c8ad51e668908d18928cef1b04e8e6d32a33894525640b52f0b31769c870a0",
"tests/desktop_fault_policy_fixture.cpp": "ac24d675ebc64cc98148e852eb5aba5858bffc06332678d14276d04b317aaf15",
"tests/desktop_fault_policy_test.py": "c3f624c0239036babd23eaaf1bb6b722f3a0c3321a5d3d6668ae7ab4910ca95c",
"tests/drag_geometry_test.cpp": "d32ea649d008fc051fe18555d6fbc54ba5d057b61880b648c5df8aa076a53fbc",
"tests/foreground_modifiers_test.py": "abf0ddde2d51c6639c8bdca8fdda51cdc8f922b57575a00fc8925c683ccc3bb2",
"tests/foreground_route_test.cpp": "1024168828b13ee6abd8242941e73c042e9381b39108e3ada74823039c7e7932",
"tests/input_client_deadline_test.cpp": "d62373a7815d531f1269c9a838773595f43e8bcef6482fa140edb162e59a6cac",
"tests/input_grant_test.cpp": "1f327b7ee678189ebad6a50bb1b9bd06767521cebc9cfb478d92de4a8bf7e7fe",
"tests/keyboard_layout_test.cpp": "3bb0fade4675d7ad92a81eb4a1c5201dd1d01bcc418b7ea59a4284dc235e5fb2",
"tests/mock-hyprland/mock.hpp": "3aeb1a4b9d6b83506b66c129d3fa812330fad4509fe218a0dbb99dd2bb5b6319",
"tests/mock-hyprland/src/config/values/types/BoolValue.hpp": "47cf2cca89f71a273573968cb9b8ba46a1496841d6c892756ebf123500a7ecb3",
"tests/mock-hyprland/src/plugins/PluginAPI.hpp": "5654d90ec9090a88bea3d31f8a79617d4c79742b09068648e64448319d395110",
"tests/owned_socket_path_test.cpp": "eaff6b5c6f148eca6c8650ee3dc212a5e892f002ee4f68b2a1290c7205ee7e42",
"tests/passive_pointer_target_test.cpp": "2aeef1de1dc8932b26ab8c41b83fb16a4289ff96c177a5816088f07b9a168948",
"tests/plugin_api_test.cpp": "1e7e200c309996ee945c88e172273ae942be2837e24564d422dee79d8b77d8a2",
"tests/plugin_input_lifetime_test.cpp": "82e57b335ea1216ea24cca07fe4feebafebdebb779785fde20b8dab6ee222e1b",
"tests/protocol_test.cpp": "119cfe0df81c0c00036a2d181764eda7601d6ee72459c2275a96226d4f670447",
"tests/seat_lifetime_test.cpp": "b07570edbe0a142f97c54560eeb93e8327c435ab3b8cbc7496d55175e387b78a",
"tests/status_test.cpp": "b8990efc53ec3820cfe498c920b9220c4b70615ad585468558e032e7619e32f3",
"tests/transport_test.cpp": "deef114a950a27eaff0a530165ddf7db0bfc0fe7e8fb55bdbb66135c8e0c04c9",
"verify.py": "fb35d62313ff4661f892f88666919b33b160f8b6d4fb2d5d52610708bf7f4a54"
},
"patch_sha256": "e74d9021111a5c217b12f3e13c1bbdd260e829c60db5fc2f7293ba4846e1a7d7",
"schema": 1,
"upstream_manifest_sha256": "54f514664c84e1358a435f29cd6befd5661b0b133d76997191c000b10f021a75",
"upstream_revision": "cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7"
}
+47 -18
View File
@@ -1,21 +1,21 @@
# Verified upstream kit with a local package-revision profile; source/tooling are unchanged.
# Verified upstream kit plus a separately pinned Omarchy keyboard-remap patch.
# Normal reruns need a fresh build directory; makepkg -e reuses verified extracted trees.
# Profile kit: original source bytes and separately committed packaging tooling.
# shellcheck shell=bash disable=SC2034,SC2154
pkgname=cua-hyprland-plugin
pkgver=0.26.1
pkgrel=3
pkgdesc='Cua input candidate for reviewed profile omarchy-hyprland-0562r3'
pkgrel=5
pkgdesc='Cua input candidate for reviewed profile omarchy-hyprland-0562r3-remaps'
arch=('x86_64')
url='https://github.com/trycua/cua'
license=('MIT')
depends=('hyprland=0.56.2-3' 'aquamarine=0.15.0-2' 'glibc=2.44+r24+g16be1518495f-1' 'hyprcursor=0.1.13-7' 'hyprgraphics=0.5.1-4' 'hyprlang=0.6.8-5' 'hyprutils=0.14.2-1' 'libgcc=16.2.1+r23+gd564253eb6c8-1' 'libstdc++=16.2.1+r23+gd564253eb6c8-1' 'libxkbcommon=1.13.2-1' 'wayland=1.26.0-1' 'python>=3.11' 'binutils')
makedepends=('cmake>=3.30' 'ninja' 'pkgconf' 'gcc')
makedepends=('cmake>=3.30' 'ninja' 'pkgconf' 'gcc' 'patch')
options=('!strip' '!debug' '!lto')
_stem='cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7'
_archive_sha256='47bca9e018f32f4fcfe683f91c7475c60368f3b65d318cc35c1f2de88a4ee9ab'
_kit_sha256='1391084a903254fb16d251522dba41ae34a06cfa4b2f6b7ba91a3cceeb895921'
_profile_sha256='eebba812a7513b471cf8969364334a4bde1eaa9c4519157701c771c1b6cd1b07'
_kit_sha256='089f447e11cacd8c2d3d6cd56528776417c51d353b9677c47c42bba1ef79c9f9'
_profile_sha256='fc3034649af98f7f81178ef575660143c249664b9ff5d0e1ccaf79d7580df17b'
_verifier_sha256='480f649cbd35a0ddc7f49dc0a3a44785402c9e0ec9653b0f8ea4965d52d7f900'
_cxx="${CUA_RELEASE_CXX:-/usr/bin/g++}"
_download_name='cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7-profile-omarchy-stable-20260910-kit-1.1.0-5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a-67f89ceb47edd973aa748820eadd02ddbfb3d3e4.tar.gz'
@@ -24,7 +24,17 @@ source=('https://github.com/trycua/cua/releases/download/cua-hyprland-kit-v1.1.0
'PROFILE.json')
noextract=("$_download_name")
sha256sums=('a89bfa7f6490f598719dfabdd2a5badc137e27b0764fd25e6a05e1f969433520'
'eebba812a7513b471cf8969364334a4bde1eaa9c4519157701c771c1b6cd1b07')
'fc3034649af98f7f81178ef575660143c249664b9ff5d0e1ccaf79d7580df17b')
# Downstream inputs are also checked explicitly when makepkg integrity is skipped.
declare -gA _downstream_sha256=(
['independent-keymaps.patch']='e74d9021111a5c217b12f3e13c1bbdd260e829c60db5fc2f7293ba4846e1a7d7'
['DOWNSTREAM-PROVENANCE.json']='e0c95350ec3ff2dd54a05e9377d79ddffffdb1cf494d05553a90207a3a919f7e'
['downstream.py']='7c9725805ad038737bd4d346c85f27991ce9ddfff436a32ebf515cad1bf98ca1'
['downstream_test.py']='7b9a992979b22a5e061fdf011ac6be59a3f1ae6e0d7c0a8ff631f74e71984c5a'
)
source+=('independent-keymaps.patch' 'DOWNSTREAM-PROVENANCE.json' 'downstream.py' 'downstream_test.py')
sha256sums+=('e74d9021111a5c217b12f3e13c1bbdd260e829c60db5fc2f7293ba4846e1a7d7' 'e0c95350ec3ff2dd54a05e9377d79ddffffdb1cf494d05553a90207a3a919f7e' '7c9725805ad038737bd4d346c85f27991ce9ddfff436a32ebf515cad1bf98ca1' '7b9a992979b22a5e061fdf011ac6be59a3f1ae6e0d7c0a8ff631f74e71984c5a')
_verify_download() {
python3 -I - "$SRCDEST/$_download_name" "$_download_sha256" "$srcdir" "$1" "$SRCDEST/PROFILE.json" <<'CUA_DOWNLOAD_PY'
@@ -65,10 +75,10 @@ require(payload.keys() == expected.keys(), 'outer kit inventory mismatch')
# files as -2. Derive a version-only profile with the original source/tooling
# and byte checks intact; record its own profile and kit provenance digests.
profile_data = Path(profile_path).read_bytes()
require(digest(profile_data) == 'eebba812a7513b471cf8969364334a4bde1eaa9c4519157701c771c1b6cd1b07',
require(digest(profile_data) == 'fc3034649af98f7f81178ef575660143c249664b9ff5d0e1ccaf79d7580df17b',
'local profile checksum mismatch')
profile = json.loads(payload['PROFILE.json'])
profile.update(profile_id='omarchy-hyprland-0562r3', package_release=3)
profile.update(profile_id='omarchy-hyprland-0562r3-remaps', package_release=5)
profile['hyprland']['package_version'] = '0.56.2-3'
require(json.loads(profile_data) == profile, 'local profile changes more than the reviewed package revision')
payload['PROFILE.json'] = profile_data
@@ -76,7 +86,7 @@ provenance = json.loads(payload['KIT-PROVENANCE.json'])
provenance['profile_sha256'] = digest(profile_data)
payload['KIT-PROVENANCE.json'] = (json.dumps(provenance, sort_keys=True, indent=2) + '\n').encode()
recipe = payload['PKGBUILD'].decode()
for old, new in [('pkgrel=2\n', 'pkgrel=3\n'), ('omarchy-stable-20260910', profile['profile_id']),
for old, new in [('pkgrel=2\n', 'pkgrel=5\n'), ('omarchy-stable-20260910', profile['profile_id']),
('hyprland=0.56.2-2', 'hyprland=0.56.2-3'),
('5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a', digest(profile_data)),
('7beb736adfd334eed52e84070177634269e3a09f8bb25971b38606933ff4c997', digest(payload['KIT-PROVENANCE.json']))]:
@@ -84,10 +94,10 @@ for old, new in [('pkgrel=2\n', 'pkgrel=3\n'), ('omarchy-stable-20260910', profi
payload['PKGBUILD'] = recipe.encode()
payload['SHA256SUMS'] = ''.join(f'{digest(body)} {name}\n' for name, body in sorted(payload.items())
if name != 'SHA256SUMS').encode()
expected.update({'PROFILE.json': 'eebba812a7513b471cf8969364334a4bde1eaa9c4519157701c771c1b6cd1b07',
'KIT-PROVENANCE.json': '1391084a903254fb16d251522dba41ae34a06cfa4b2f6b7ba91a3cceeb895921',
'PKGBUILD': '4c5ef50c5a8d556d461afe283b13b228fb09d2558b37fb76257e13e25846325b',
'SHA256SUMS': '2ea22af60f8c86df588fd7db103d9f444d9ac4018b704a48ddff2545ff7ce999'})
expected.update({'PROFILE.json': 'fc3034649af98f7f81178ef575660143c249664b9ff5d0e1ccaf79d7580df17b',
'KIT-PROVENANCE.json': '089f447e11cacd8c2d3d6cd56528776417c51d353b9677c47c42bba1ef79c9f9',
'PKGBUILD': '0cbf2cd34c3c5038a5ed51e6bf84acd81844e4c2bb08ad7203959201bba61da9',
'SHA256SUMS': 'd01b9e0be4c5bcf84cc2ecef9f11f44cedfc1ca5b31afbfcf52aa2efbd636a09'})
for name, content in payload.items():
require(digest(content) == expected[name], 'derived kit checksum mismatch: ' + name)
require(srcdir.is_dir() and not srcdir.is_symlink(), 'srcdir must be a real directory')
@@ -132,17 +142,31 @@ _verify() {
printf '%s %s\n' "$_profile_sha256" "$srcdir/cua-profile-kit/PROFILE.json" | sha256sum -c - || return 1
printf '%s %s\n' "$_verifier_sha256" "$srcdir/cua-profile-kit/profile_verify.py" | sha256sum -c - || return 1
python3 "$srcdir/cua-profile-kit/profile_verify.py" --kit "$srcdir/cua-profile-kit" --kit-sha256 "$_kit_sha256" \
--archive "$srcdir/cua-profile-kit/${_stem}.tar.gz" --source "$srcdir/$_stem" --cxx "$_cxx" "$@"
--archive "$srcdir/cua-profile-kit/${_stem}.tar.gz" --source "$srcdir/$_stem" --cxx "$_cxx"
}
_downstream() {
local name
for name in independent-keymaps.patch DOWNSTREAM-PROVENANCE.json downstream.py downstream_test.py; do
printf '%s %s\n' "${_downstream_sha256[$name]}" "$SRCDEST/$name" | sha256sum -c - || return 1
done
python3 -B "$SRCDEST/downstream.py" "$1" \
--pristine "$srcdir/$_stem" --source "$srcdir/omarchy-source" \
--patch "$SRCDEST/independent-keymaps.patch" --manifest "$SRCDEST/DOWNSTREAM-PROVENANCE.json" \
--kit "$srcdir/cua-profile-kit" --kit-sha256 "$_kit_sha256" \
--archive "$srcdir/cua-profile-kit/${_stem}.tar.gz" --cxx "$_cxx" "${@:2}"
}
prepare() {
_verify_download extract || return 1
_verify
_verify || return 1
_downstream prepare
}
build() {
_verify || return 1
cmake -S "$srcdir/$_stem" -B "$srcdir/build" -G Ninja \
_downstream check || return 1
cmake -S "$srcdir/omarchy-source" -B "$srcdir/build" -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER="$_cxx" \
-DPKG_CONFIG_EXECUTABLE=/usr/bin/pkgconf -DPKG_CONFIG_ARGN= \
-DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=OFF -DCMAKE_PREFIX_PATH= \
@@ -155,6 +179,8 @@ build() {
check() {
_verify || return 1
_downstream check || return 1
python3 -B "$SRCDEST/downstream_test.py" || return 1
(
unset LD_PRELOAD FAKEROOTKEY FAKED_MODE
ctest --test-dir "$srcdir/build" --output-on-failure --no-tests=error
@@ -163,7 +189,7 @@ check() {
package() {
check || return 1
_verify --build "$srcdir/build" --output "$srcdir/BUILD-PROVENANCE.json" || return 1
_downstream build --build "$srcdir/build" --output "$srcdir/BUILD-PROVENANCE.json" || return 1
install -Dm755 "$srcdir/build/cua-hyprland-plugin.so" \
"$pkgdir/usr/lib/cua/hyprland/cua-hyprland-plugin.so" || return 1
install -Dm644 "$srcdir/$_stem/LICENSE.md" \
@@ -175,4 +201,7 @@ package() {
for name in KIT-PROVENANCE.json PROFILE.json profile_verify.py; do
install -Dm644 "$srcdir/cua-profile-kit/$name" "$pkgdir/usr/share/$pkgname/$name" || return 1
done
for name in DOWNSTREAM-PROVENANCE.json independent-keymaps.patch; do
install -Dm644 "$SRCDEST/$name" "$pkgdir/usr/share/$pkgname/$name" || return 1
done
}
+2 -2
View File
@@ -12,8 +12,8 @@
"sha256": "da8fcacf347bcbed83edc40108c6e2298da095e22246bd764e9bb382786cebb2"
},
"kit_version": "1.1.0",
"package_release": 3,
"profile_id": "omarchy-hyprland-0562r3",
"package_release": 5,
"profile_id": "omarchy-hyprland-0562r3-remaps",
"runtime": {
"basename": "libstdc++.so.6.0.36",
"packages": {
+29 -88
View File
@@ -1,6 +1,6 @@
# Optional Cua Hyprland plugin
This package targets **Omarchy stable x86_64**, with Inkscape `1.4.4-6` and two independent background-input lanes. Cua's native qualification is recorded in [the kit's qualification record](https://github.com/trycua/cua/releases/download/cua-hyprland-kit-v1.1.0-omarchy-stable-20260910/QUALIFICATION.md) and [Cua #3698](https://github.com/trycua/cua/pull/3698). Omabot replay and Omarchy's merge decision are recorded in [omarchy-pkgs #346](https://github.com/omacom/omarchy-pkgs/pull/346). Scheduling the recipe does not expand the qualified stable target.
This package targets **Omarchy stable x86_64**, with Inkscape `1.4.4-6` and two independent background-input lanes. Package release `5` includes the Omarchy patch for independent agent keymaps, operation-specific foreground checks, and compatible Num Lock state; the upstream native qualification below covers the unpatched source, not this change. Cua's native qualification is recorded in [the kit's qualification record](https://github.com/trycua/cua/releases/download/cua-hyprland-kit-v1.1.0-omarchy-stable-20260910/QUALIFICATION.md) and [Cua #3698](https://github.com/trycua/cua/pull/3698). Omabot replay and Omarchy's merge decision are recorded in [omarchy-pkgs #346](https://github.com/omacom/omarchy-pkgs/pull/346). Scheduling the recipe does not expand the qualified stable target.
The plugin is optional. Cua Driver works independently, and installation does not load the plugin or enable input. The package follows the normal edge-to-RC-to-stable promotion path instead of the fast release ring. Its PKGBUILD limits builds to x86_64; only stable x86_64 is a qualified target.
@@ -10,9 +10,9 @@ The package uses the [Driver 0.26.1 plugin source](https://github.com/trycua/cua
including the [desktop-fault cleanup repair](https://github.com/trycua/cua/pull/3702).
It is not a repackaging of the unmodified 0.24.0 plugin.
The qualified Driver pairing is `cua-driver-bin 0.27.0-1`, with input protocol v3. Driver 0.27.0 contains the bounded stale-geometry retry validated with this pinned module; its production plugin source is unchanged from the source used here. Discovery protocol v2 is separate. A newer Driver release is a changed pairing and requires affected replay before promotion.
The qualified upstream Driver pairing is `cua-driver-bin 0.27.0-1`, with input protocol v3. Driver 0.27.0 contains the bounded stale-geometry retry validated with the upstream module; its production plugin source is the base for the downstream patch used here. Discovery protocol v2 is separate. A newer Driver release is a changed pairing and requires affected replay before promotion.
Profile `omarchy-hyprland-0562r3`, kit tooling `1.1.0`, and package release `3` pin:
Profile `omarchy-hyprland-0562r3-remaps`, kit tooling `1.1.0`, and package release `5` pin:
- Hyprland `0.56.2-3`, headers `0.56.2`, and measured executable/header hashes.
- GCC `16.2.1 20260810`, including compiler bytes and emitted ELF identity.
@@ -24,14 +24,13 @@ its compositor executable and all 498 header/pkg-config files are byte-identical
to `-2`. Both executables have SHA-256
`da8fcacf347bcbed83edc40108c6e2298da095e22246bd764e9bb382786cebb2`.
The checked-in `PROFILE.json` changes only the profile name, package release,
and exact Hyprland package version. Compiler, runtime, source, executable,
and header identities remain unchanged. The download wrapper verifies the
and exact Hyprland package version. Compiler, runtime, upstream source, executable,
and header identities remain unchanged; the separately recorded patch changes the build source. The download wrapper verifies the
original kit before deriving the updated profile, recipe, and provenance,
then verifies every derived member against its recorded digest.
The native qualification below was recorded with package release `2` and
Hyprland `-2`. This packaging update does not claim a new application or
Driver replay. The `-3` dependency must reach a destination channel before
Hyprland `-2`. The downstream keymap change needs its own application and Driver replay before promotion. The `-3` dependency must reach a destination channel before
this artifact can be installed there; publication still follows edge → RC → stable.
The generated `PKGBUILD` identifies the immutable kit download, outer checksum,
@@ -47,7 +46,19 @@ and production flags remain mandatory. Packaging runs all bundled CTests even
with `--nocheck` or `--repackage`; `--skipinteg` does not bypass recipe checks.
Production input is built in; experimental signed input and tracing are off.
## What is qualified
The pristine upstream archive, manifest, and verifier remain unchanged. `independent-keymaps.patch` is applied to a separate source tree, and `DOWNSTREAM-PROVENANCE.json` pins the patch and every resulting source file. Build, check, and package revalidate both trees, including when makepkg integrity checks are skipped. `BUILD-PROVENANCE.json` records the upstream base under `source`, the applied change under `downstream`, and the final module digest; the downstream manifest and patch are installed beside it. This preserves the existing compiler, headers, runtime, and consumer checks without representing the modified module as an unmodified upstream build.
## Keyboard behavior
Each background lane owns a canonical US keymap and independent modifier state. The physical keyboard keeps its layout, Compose key, and remaps. No installation or activation step edits `input:kb_*`. Existing Driver keycodes are interpreted by the agent keyboard, so this does not add Unicode, IME, or new Driver text routes.
Plain click, scroll, drag, and foreground activation do not require a canonical keyboard layout. Foreground keys still use the primary seat: the plugin checks the requested key and modifier sequence against its actual XKB map before activation or input. Unrelated remaps are accepted; a sequence whose symbols or modifier/lock transitions differ from the canonical meaning is refused with `unsupported_layout`. Arbitrary foreground layout translation remains outside protocol v3.
Foreground typing preserves Num Lock and admits a requested key sequence only when its symbols and shortcut semantics still match the canonical meaning. Num Lock does not block unaffected letters, top-row digits, Enter, or compatible shortcuts; a keypad sequence whose meaning changes is refused. Caps Lock, other unsupported lock states, held or latched modifiers, and nonzero layout groups remain guarded.
Both routes retain target/conflict checks and cancellation on desktop/keymap changes. `hyprctl -j cua:status` exposes `keyboard_layout_independent: true` and `foreground_numlock_compatible: true` for installers to distinguish this implementation from an older mapped module. The marker does not identify every future package revision; plugin updates still require a fresh desktop session.
## Historical upstream qualification
The initial app scope is native Wayland Inkscape `1.4.4-6` with the canonical
US keymap. Two lanes require independent Driver processes and distinct native
@@ -93,7 +104,7 @@ background refusal never authorizes a hidden foreground fallback or unlock.
Build the unsigned candidate in edge:
```sh
bin/repo build --package cua-hyprland-plugin --arch x86_64 --mirror edge
./bin/build --package cua-hyprland-plugin --arch x86_64 --mirror edge
```
In a fresh worker matching the reviewed profile:
@@ -131,7 +142,7 @@ kit-provenance digest:
```sh
python3 /usr/share/cua-hyprland-plugin/profile_verify.py \
--kit /usr/share/cua-hyprland-plugin \
--kit-sha256 1391084a903254fb16d251522dba41ae34a06cfa4b2f6b7ba91a3cceeb895921 \
--kit-sha256 089f447e11cacd8c2d3d6cd56528776417c51d353b9677c47c42bba1ef79c9f9 \
--consumer /usr/lib/cua/hyprland/cua-hyprland-plugin.so
```
@@ -146,98 +157,28 @@ hyprctl plugin load /usr/lib/cua/hyprland/cua-hyprland-plugin.so
hyprctl -j cua:status
```
Loading alone does not enable input. Before opting in, save open work and save
the exact current personal input configuration. The backup command refuses a
symlinked input file and refuses to replace an earlier backup:
Loading alone does not enable input. Use Omarchy's explicit Cua Input toggle when available; it verifies the installed profile and loaded capability and removes the legacy copied toggle's keyboard override. If it reports an older mapped plugin, disable Cua Input and log out and back in before enabling it again. Never hot-unload and reload the module.
```sh
test -f "$HOME/.config/hypr/input.lua" && \
test ! -L "$HOME/.config/hypr/input.lua" && \
test ! -e "$HOME/.config/hypr/input.lua.cua-before" && \
cp --archive -- "$HOME/.config/hypr/input.lua" \
"$HOME/.config/hypr/input.lua.cua-before"
```
If `input.lua` is a symlink, stop here: back up and later restore its resolved
target explicitly instead of using the commands below.
The background-input admission guard requires the exact XKB keymap
`rules=evdev`, `model=pc105`, `layout=us`, empty variant and options, and no
custom keymap file. Stock Omarchy 4.0.3 English (US) is not that literal
configuration: it leaves rules and model empty and sets
`compose:caps,shift:both_capslock_cancel`. Those options make Caps Lock the
Compose key and both Shift keys the Caps Lock/cancel chord. The required empty
options restore ordinary Caps Lock behavior and remove both stock shortcuts
while Cua input is enabled. Any other effective value is intentionally refused
as `unsupported_layout`; do not weaken or bypass that admission guard.
Append this override to `~/.config/hypr/input.lua` so it follows any existing
input settings. It both selects the exact admitted keymap and enables the
trusted local transport:
For manual activation, add only this plugin setting to a sourced Hyprland Lua configuration file, preserving all existing input settings:
```lua
hl.config({
input = {
kb_rules = "evdev",
kb_model = "pc105",
kb_layout = "us",
kb_variant = "",
kb_options = "",
kb_file = "",
},
plugin = { cua = { enabled = true } },
})
```
Reload, then read back every keymap value rather than relying on the source
file alone:
Then reload and inspect status:
```sh
hyprctl reload
for name in kb_rules kb_model kb_layout kb_variant kb_options kb_file; do
value=$(hyprctl -j getoption "input:$name" | jq -r '.str')
printf '%s=%s\n' "$name" "$value"
done
hyprctl -j cua:status
```
The keymap readback must be exactly:
```text
kb_rules=evdev
kb_model=pc105
kb_layout=us
kb_variant=
kb_options=
kb_file=
```
A runtime keyword or Lua evaluation without `hyprctl reload` does not reconcile
the input sockets. Continue only when status also reports input protocol v3,
input capability, socket paths, and the expected compositor identity. Do not
disable NumLock; that was required only by a strict qualification observer, not
by the demonstrated background-input contract.
Start Driver with `CUA_DRIVER_RS_ENABLE_WAYLAND=1`. For the activation check,
use background input in a new disposable Inkscape document to create a text
object containing `CUA activation check`. Save it under a new temporary
filename, then verify the text in both a fresh Driver snapshot and the reopened
saved SVG. Never test against an existing document, and do not automatically
replay an action with a partial or unknown outcome.
After the check, restore the exact saved configuration and reload it:
```sh
command mv --force -- "$HOME/.config/hypr/input.lua.cua-before" \
"$HOME/.config/hypr/input.lua"
hyprctl reload
hyprctl -j cua:status
```
Confirm that the prior keymap values are back and status reports input disabled.
Retained inert agent pointers can remain until the compositor exits; disabling
input does not unload the mapped module. If you intentionally keep activation,
retain the backup until you are ready to perform this exact restoration.
Continue only when status reports `keyboard_layout_independent: true`, `foreground_numlock_compatible: true`, input protocol v3, input capability, socket paths, and the expected compositor identity. Do not change `kb_layout`, `kb_options`, or NumLock for background input. If you previously followed the stock-US override instructions, remove only that Cua-specific override and reload to restore your underlying personal settings.
Start Driver with `CUA_DRIVER_RS_ENABLE_WAYLAND=1`. In a new disposable Inkscape document, test an admitted background key operation and pointer operation, then verify the result in both a fresh snapshot and a saved/reopened SVG. Driver text-route restrictions still apply. Never test against an existing document or automatically replay an action with a partial or unknown outcome.
To disable input, turn the Cua Input toggle off, or remove the manual `plugin.cua.enabled` setting and reload. Confirm that status reports input disabled. Retained inert agent pointers can remain until the compositor exits; disabling input does not unload the mapped module.
Before an incompatible desktop update, remove operator-added plugin activation
settings, save work, and exit the graphical session. From a text console, run
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Verify the Omarchy patch separately from the unchanged upstream source kit."""
import argparse
import hashlib
import importlib.util
import json
from pathlib import Path, PurePosixPath
import shutil
import subprocess
def require(condition, message):
if not condition:
raise ValueError(message)
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def inventory(root):
require(root.is_dir() and not root.is_symlink(), "source must be a real directory")
result = {}
for path in root.rglob("*"):
require(not path.is_symlink() and (path.is_dir() or path.is_file()), "nonregular source entry")
if path.is_file():
result[path.relative_to(root).as_posix()] = digest(path)
return result
def verify_inputs(pristine, patch, manifest):
require(manifest["schema"] == 1, "unsupported downstream schema")
require(patch.is_file() and not patch.is_symlink() and digest(patch) == manifest["patch_sha256"],
"downstream patch checksum mismatch")
require(digest(pristine / "SOURCE-PROVENANCE.json") == manifest["upstream_manifest_sha256"],
"downstream base manifest mismatch")
require(manifest["files"], "empty downstream inventory")
for name in manifest["files"]:
path = PurePosixPath(name)
require(name and not path.is_absolute() and path.as_posix() == name and
".." not in path.parts and "\\" not in name, "invalid downstream path")
def verify_tree(source, manifest):
require(inventory(source) == manifest["files"], "patched source inventory/checksum mismatch")
def prepare(pristine, source, patch, manifest):
require(not source.exists() and not source.is_symlink(), "patched source requires a fresh destination")
shutil.copytree(pristine, source)
subprocess.run(["patch", "--batch", "--fuzz=0", "-p1", "-i", str(patch.resolve())], cwd=source, check=True)
verify_tree(source, manifest)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("mode", choices=("prepare", "check", "build"))
parser.add_argument("--pristine", required=True, type=Path)
parser.add_argument("--source", required=True, type=Path)
parser.add_argument("--patch", required=True, type=Path)
parser.add_argument("--manifest", required=True, type=Path)
parser.add_argument("--kit", required=True, type=Path)
parser.add_argument("--kit-sha256", required=True)
parser.add_argument("--archive", required=True, type=Path)
parser.add_argument("--cxx", required=True, type=Path)
parser.add_argument("--build", type=Path)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
try:
# PKGBUILD authenticates the verifier and this helper before execution.
spec = importlib.util.spec_from_file_location("upstream_profile", args.kit / "profile_verify.py")
upstream = importlib.util.module_from_spec(spec)
spec.loader.exec_module(upstream)
profile, kit = upstream.verify_kit(args.kit, args.kit_sha256)
base = upstream.verify_archive(args.archive, profile)
require(upstream.verify_source(args.pristine, profile) == base, "upstream source identity mismatch")
manifest = upstream.read_json(args.manifest.read_bytes())
verify_inputs(args.pristine, args.patch, manifest)
if args.mode == "prepare":
prepare(args.pristine, args.source, args.patch, manifest)
else:
verify_tree(args.source, manifest)
if args.mode == "build":
require(args.build is not None and args.output is not None, "build evidence requires output")
native = upstream.verify_native(args.cxx, profile)
native["module_sha256"] = upstream.verify_build(args.build, args.source, args.cxx, profile)
native["module_runtime_sha256"] = profile["runtime"]["sha256"]
args.output.write_bytes(upstream.json_bytes(dict(native, source=base, profile=profile,
kit=kit, downstream=manifest)))
except (ValueError, KeyError, TypeError, OSError, subprocess.CalledProcessError) as error:
parser.exit(1, f"error: {error}\n")
if __name__ == "__main__":
main()
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Exercise downstream integrity with real patch application and tampering."""
import hashlib
from pathlib import Path
import tempfile
import unittest
import downstream
class DownstreamTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.pristine = self.root / "pristine"
self.pristine.mkdir()
(self.pristine / "SOURCE-PROVENANCE.json").write_text("upstream\n")
(self.pristine / "input.cpp").write_text("old\n")
self.patch = self.root / "change.patch"
self.patch.write_text("--- a/input.cpp\n+++ b/input.cpp\n@@ -1 +1 @@\n-old\n+new\n")
self.source = self.root / "patched"
self.manifest = {
"schema": 1,
"patch_sha256": downstream.digest(self.patch),
"upstream_manifest_sha256": downstream.digest(self.pristine / "SOURCE-PROVENANCE.json"),
"files": {"SOURCE-PROVENANCE.json": downstream.digest(self.pristine / "SOURCE-PROVENANCE.json"),
"input.cpp": hashlib.sha256(b"new\n").hexdigest()},
}
def test_applies_patch_without_changing_upstream(self):
downstream.verify_inputs(self.pristine, self.patch, self.manifest)
downstream.prepare(self.pristine, self.source, self.patch, self.manifest)
self.assertEqual((self.pristine / "input.cpp").read_text(), "old\n")
self.assertEqual((self.source / "input.cpp").read_text(), "new\n")
def test_changed_patch_refuses(self):
self.patch.write_text(self.patch.read_text().replace("+new", "+bad"))
with self.assertRaisesRegex(ValueError, "patch checksum"):
downstream.verify_inputs(self.pristine, self.patch, self.manifest)
def test_changed_base_manifest_refuses(self):
(self.pristine / "SOURCE-PROVENANCE.json").write_text("different\n")
with self.assertRaisesRegex(ValueError, "base manifest"):
downstream.verify_inputs(self.pristine, self.patch, self.manifest)
def test_tampered_missing_and_extra_files_refuse(self):
downstream.prepare(self.pristine, self.source, self.patch, self.manifest)
file = self.source / "input.cpp"
for content in ("tampered\n", None):
if content is None:
file.unlink()
else:
file.write_text(content)
with self.assertRaisesRegex(ValueError, "inventory/checksum"):
downstream.verify_tree(self.source, self.manifest)
file.write_text("new\n")
(self.source / "unexpected.cpp").write_text("extra\n")
with self.assertRaisesRegex(ValueError, "inventory/checksum"):
downstream.verify_tree(self.source, self.manifest)
def test_symlink_and_reused_destination_refuse(self):
downstream.prepare(self.pristine, self.source, self.patch, self.manifest)
with self.assertRaisesRegex(ValueError, "fresh destination"):
downstream.prepare(self.pristine, self.source, self.patch, self.manifest)
file = self.source / "input.cpp"
file.unlink()
file.symlink_to(self.pristine / "input.cpp")
with self.assertRaisesRegex(ValueError, "nonregular"):
downstream.verify_tree(self.source, self.manifest)
if __name__ == "__main__":
unittest.main()
File diff suppressed because it is too large. Load diff