Merge pull request #346 from f-trycua/feat/cua-hyprland-plugin-package

Add optional ABI-pinned Cua Hyprland plugin package
This commit is contained in:
Spencer Bull authored and GitHub committed 2026-09-13 19:15:37 -05:00
commit 3f734e2f8c
3 files changed
+426

No files matched your search

@@ -0,0 +1,6 @@
{
"source": "local",
"release_ring": "fast",
"channels": ["stable"],
"skip_build": true
}
+146
View File
@@ -0,0 +1,146 @@
# Separately reviewed download wrapper; original kit and source identity are unchanged.
# 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=2
pkgdesc='Cua input candidate for reviewed profile omarchy-stable-20260910'
arch=('x86_64')
url='https://github.com/trycua/cua'
license=('MIT')
depends=('hyprland=0.56.2-2' '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')
options=('!strip' '!debug' '!lto')
_stem='cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7'
_archive_sha256='47bca9e018f32f4fcfe683f91c7475c60368f3b65d318cc35c1f2de88a4ee9ab'
_kit_sha256='7beb736adfd334eed52e84070177634269e3a09f8bb25971b38606933ff4c997'
_profile_sha256='5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a'
_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'
_download_sha256='a89bfa7f6490f598719dfabdd2a5badc137e27b0764fd25e6a05e1f969433520'
source=('https://github.com/trycua/cua/releases/download/cua-hyprland-kit-v1.1.0-omarchy-stable-20260910/cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7-profile-omarchy-stable-20260910-kit-1.1.0-5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a-67f89ceb47edd973aa748820eadd02ddbfb3d3e4.tar.gz')
noextract=("$_download_name")
sha256sums=('a89bfa7f6490f598719dfabdd2a5badc137e27b0764fd25e6a05e1f969433520')
_verify_download() {
python3 -I - "$SRCDEST/$_download_name" "$_download_sha256" "$srcdir" "$1" <<'CUA_DOWNLOAD_PY'
import hashlib
import io
from pathlib import Path, PurePosixPath
import sys
import tarfile
expected = {'KIT-PROVENANCE.json': '7beb736adfd334eed52e84070177634269e3a09f8bb25971b38606933ff4c997', 'PKGBUILD': 'b945a6a6eda13d0e382770edd5419485e3196041956663eb38f5183b05d30db3', 'PROFILE-PKGBUILD.in': 'c350d1b2375946cb0166893d67a1fc01344ee5e3215bbe801b4d54bb361d6bce', 'PROFILE-USAGE.md': 'c14d8e8103fccab59e558758f4249f86bce700a8723cd4ba1b97b1102e02bbd2', 'PROFILE.json': '5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a', 'SHA256SUMS': '34a2126bcfab983f171382aafac3ef218475b652f4583c58f4a04088044cb935', 'SOURCE-PROVENANCE.json': '54f514664c84e1358a435f29cd6befd5661b0b133d76997191c000b10f021a75', 'cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7.tar.gz': '47bca9e018f32f4fcfe683f91c7475c60368f3b65d318cc35c1f2de88a4ee9ab', 'lifecycle.py': 'b18dceb8b8e05b93586ddd3a2f1d90d70ae1c36088e54fc05c89e08585290990', 'profile_bundle.py': 'ac883883814787da477c037f017939ee92c9fb5f46f373af7de1331b24f1f6da', 'profile_verify.py': '480f649cbd35a0ddc7f49dc0a3a44785402c9e0ec9653b0f8ea4965d52d7f900'}
stem = 'cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7'
def require(condition, message):
if not condition:
raise SystemExit(message)
def digest(data):
return hashlib.sha256(data).hexdigest()
archive, checksum, srcdir, mode = sys.argv[1:]
archive, srcdir = Path(archive), Path(srcdir)
require(mode in {'check', 'extract'}, 'invalid kit verification mode')
require(archive.is_file() and not archive.is_symlink(), 'outer archive must be a regular file')
data = archive.read_bytes()
require(digest(data) == checksum, 'outer archive checksum mismatch')
payload = {}
with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as contents:
for member in contents:
require(member.isfile() and not member.issparse() and not member.pax_headers,
'nonregular outer kit member')
require(member.name in expected and member.name not in payload, 'outer kit inventory mismatch')
content = contents.extractfile(member).read()
require(digest(content) == expected[member.name], 'outer kit member checksum mismatch')
payload[member.name] = content
require(payload.keys() == expected.keys(), 'outer kit inventory mismatch')
require(srcdir.is_dir() and not srcdir.is_symlink(), 'srcdir must be a real directory')
kit, source = srcdir / 'cua-profile-kit', srcdir / stem
if mode == 'extract':
require(not kit.exists() and not kit.is_symlink() and not source.exists() and not source.is_symlink(),
'prepare requires fresh kit and source destinations; use a clean srcdir')
source_payload = {}
with tarfile.open(fileobj=io.BytesIO(payload[stem + '.tar.gz']), mode='r:gz') as contents:
for member in contents:
require(member.isfile() and not member.issparse() and not member.pax_headers and
member.name.startswith(stem + '/'), 'invalid source member')
name = member.name[len(stem) + 1:]
path = PurePosixPath(name)
require(name and path.as_posix() == name and not path.is_absolute() and
'..' not in path.parts and '\\' not in name and name not in source_payload,
'unsafe or duplicate source path')
source_payload[name] = contents.extractfile(member).read()
kit.mkdir()
source.mkdir()
for name, content in payload.items():
(kit / name).write_bytes(content)
for name, content in source_payload.items():
destination = source / name
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(content)
require(kit.is_dir() and not kit.is_symlink(), 'kit must be a real directory')
require({path.name for path in kit.iterdir()} == expected.keys(), 'extracted kit inventory mismatch')
for name, checksum in expected.items():
path = kit / name
require(path.is_file() and not path.is_symlink() and digest(path.read_bytes()) == checksum,
'extracted kit checksum mismatch: ' + name)
CUA_DOWNLOAD_PY
}
_verify() {
printf '%s %s\n' "$_download_sha256" "$SRCDEST/$_download_name" | sha256sum -c - || return 1
_verify_download check || return 1
# Explicit checks still apply to --skipinteg, --noextract and --repackage.
printf '%s %s\n' "$_archive_sha256" "$srcdir/cua-profile-kit/${_stem}.tar.gz" | sha256sum -c - || return 1
printf '%s %s\n' "$_kit_sha256" "$srcdir/cua-profile-kit/KIT-PROVENANCE.json" | sha256sum -c - || return 1
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" "$@"
}
prepare() {
_verify_download extract || return 1
_verify
}
build() {
_verify || return 1
cmake -S "$srcdir/$_stem" -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= \
-DBUILD_TESTING=ON -DCUA_HYPRLAND_BUILD_PLUGIN=ON \
-DCUA_HYPRLAND_EXPECTED_VERSION=0.56.2 \
-DCUA_HYPRLAND_INPUT=ON -DCUA_HYPRLAND_TEST_INPUT=OFF \
-DCUA_HYPRLAND_INPUT_TRACE=OFF -DCUA_HYPRLAND_TEST_OPERATOR_KEY= || return 1
cmake --build "$srcdir/build"
}
check() {
_verify || return 1
(
unset LD_PRELOAD FAKEROOTKEY FAKED_MODE
ctest --test-dir "$srcdir/build" --output-on-failure --no-tests=error
)
}
package() {
check || return 1
_verify --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" \
"$pkgdir/usr/share/licenses/$pkgname/LICENSE" || return 1
install -Dm644 "$srcdir/$_stem/SOURCE-PROVENANCE.json" \
"$pkgdir/usr/share/$pkgname/SOURCE-PROVENANCE.json" || return 1
local name
install -Dm644 "$srcdir/BUILD-PROVENANCE.json" "$pkgdir/usr/share/$pkgname/BUILD-PROVENANCE.json" || return 1
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
}
+274
View File
@@ -0,0 +1,274 @@
# 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 explicit merge, signing, and publication decisions remain required.
Keep `skip_build: true` until those gates pass.
The plugin is optional. Cua Driver works independently, and installation does
not load the plugin or enable input. Metadata restricts the initial destination
to `channels: ["stable"]`; `release_ring: fast` permits a native package build.
Edge, RC, and ARM publication are outside this initial scope.
## Source and build profile
The package uses the [Driver 0.26.1 plugin source](https://github.com/trycua/cua/releases/tag/cua-driver-rs-v0.26.1),
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 Driver client
intended for Omarchy publication 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. Omarchy must still build,
replay, sign, and publish that exact package pairing before rollout.
Profile `omarchy-stable-20260910`, kit `1.1.0`, and package release `2` pin:
- Hyprland `0.56.2-2`, headers `0.56.2`, and measured executable/header hashes.
- GCC `16.2.1 20260810`, including compiler bytes and emitted ELF identity.
- Shared runtime `libstdc++.so.6.0.36`, its bytes, and exact ABI package versions.
The generated `PKGBUILD` identifies the immutable kit download, outer checksum,
and member checksums. The kit records the full source and tooling revisions,
profile digest, and source archive/manifest digests. Do not infer compatibility
from a matching version label or substitute an unreviewed profile.
The download wrapper verifies its complete inventory before executing downloaded
tooling. It preserves the source archive and its historical embedded verifier,
but explicitly uses the new kit's `profile_verify.py`. Source integrity,
package-owned headers, pkg-config selection, compiler probes, runtime equality,
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 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
application clients, not merely two windows. This is concurrency inside one
desktop account, not multi-user or mutually untrusted-agent isolation.
Cua's retained evidence covers background application effects, two-lane overlap,
third-owner refusal, primary-input preservation, conflicts, stale targets and
geometry, cancellation, desktop faults, recovery, and cold package transitions.
Production-package app checks and independent primary observers are separate
from trace-enabled diagnostics. Cua's retained canonical run
`433ce968ee164d5e8e3226e800db93a6` recorded all 128 required cells: 87
deliveries and 41 expected refusals, with no failures or skips; its completion
report has SHA-256
`1eda4cc008ea6b27d96c21d58f8041834398a43409642376bafe84ad38f0e112`.
That historical run used source-built released Driver 0.24.0; earlier real-app
checks separately used the then-published Omarchy `cua-driver-bin 0.24.0-1`
executable. A separate later Omabot replay reported 124 passes and four
failures, plus seven incomplete native cases. Cua subsequently passed those
four cells with the repair candidate shipped in Driver 0.27.0 and passed all
seven lifecycle cases. A final exact-release Fleet replay then passed all four
cells with Driver and harness source `082de4344b731ae4738ddc6a6f13f21bb3c49a85`,
released Driver binary SHA-256
`bb1b65394e912246220f9f758c9efbbf6260cec16e3562e62a361fa95329377f`,
and evidence SHA-256
`b6c47278a3db398ecbb4d7aed2bce44a467e2af37e6d4ccfc236d1e07aa70af7`.
Omarchy replay of the intended 0.27.0 package pairing remains an explicit
merge gate. See the linked qualification record and PR description for exact
artifacts and observation limits.
Duplicate motion notifications are retained and counted. They are acceptable
only when pointer identity, coordinates, focus, held input, and foreground
interaction remain unchanged. Actual motion—including moving away and back—
fails isolation. After cancellation, an inert agent pointer may remain parked
if held input is released and authority is revoked.
Current LibreOffice Calc `26.8`, Chromium/Electron raw background input,
XWayland, Unicode/IME, non-US layouts, and modified pointer gestures are outside
this profile. The plugin does not widen Driver's application admission.
Foreground input, capture, and accessibility have separate contracts; a
background refusal never authorizes a hidden foreground fallback or unlock.
## Omabot replay before merge
Use the unsigned, explicitly scoped build command:
```sh
bin/repo build --package cua-hyprland-plugin --arch x86_64 --mirror stable
```
In a fresh worker matching the reviewed profile:
1. Verify the downloaded kit and source identities against the reviewed recipe.
Record the actual channel snapshot, Driver, compiler, compositor, runtime,
applications, keymap, and resulting package/module hashes.
2. Require all bundled tests and native compatibility checks. Do not weaken
exact dependencies or replace the compositor to make the build pass.
3. Install through pacman and activate in a fresh session. Replay the declared
app, two-lane, refusal, primary-input, cancellation, and fault/recovery checks
against the actual packaged Driver and module. A Cua Fleet result is not an
Omabot result; matching source alone does not certify different binaries.
4. Verify restart-based upgrade, rollback, removal, and reinstallation. Retain
evidence that binds each result to the package and mapped module bytes.
Portable tests, screenshots, health reports, and a successful build do not
replace native qualification. Recheck the published Driver package before
rollout and qualify any changed pairing explicitly.
## Activation, updates, and removal
The package installs the module at
`/usr/lib/cua/hyprland/cua-hyprland-plugin.so` and provenance plus the consumer
verifier under `/usr/share/cua-hyprland-plugin/`. There are no hooks, autoloading,
configuration edits, or hot replacement.
Save your work and exit Hyprland before installing, replacing, or removing the
package. Install the exact reviewed package from a text console, then start a
fresh session. Before loading, run the consumer check with the independently
reviewed kit-provenance digest from the qualification record:
```sh
python3 /usr/share/cua-hyprland-plugin/profile_verify.py \
--kit /usr/share/cua-hyprland-plugin \
--kit-sha256 7beb736adfd334eed52e84070177634269e3a09f8bb25971b38606933ff4c997 \
--consumer /usr/lib/cua/hyprland/cua-hyprland-plugin.so
```
This check requires Python 3.11+, binutils `readelf`, and system `ldd`/`pacman`,
not a compiler or headers. If it fails, leave the plugin unloaded. It verifies
installed compatibility, not runtime mapping or input effects.
After that check passes in the fresh session, load the module explicitly:
```sh
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:
```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:
```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:
```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.
Before an incompatible desktop update, remove operator-added plugin activation
settings, save work, and exit the graphical session. From a text console, run
`sudo pacman -R cua-hyprland-plugin`, then apply the normal desktop update and
verify a fresh session without the plugin. Declining removal preserves the
dependency refusal. Disabling input alone leaves exact dependencies installed;
do not force an upgrade past them.
Retain the previous package with its matching compositor, runtime, Driver, and
provenance as a rollback set. Restore a consistent set outside the graphical
session, then repeat the fresh-session consumer and app checks. Do not hot
unload/reload or replace a mapped module.
## Ownership and publication
The [agreed ownership split](https://github.com/omacom/omarchy-pkgs/pull/346#issuecomment-5612834061)
assigns profiles, build kits, plugin fixes, and native input evidence to Cua.
Francesco (@f-trycua) is the Cua contact through this PR. Omarchy owns package
integration, dependency-change detection, Omabot validation, and signing and
publication decisions. Spencer (@spencerbull) and Emir (@emirb) jointly own
that Omarchy package and release path. Maintenance is best effort, with no
turnaround commitment.
Edge detects upcoming incompatibilities; RC validates the intended stable
environment. Mirror/channel changes and changes to ABI dependencies, Driver,
or admitted apps request a new candidate and affected qualification. They do
not establish compatibility or authorize additional publication channels.
`skip_build` controls selection, not publication authority. This package has no
upstream polling, AUR synchronization, or automatic rebuild bump. After replay
and explicit merge approval, the named Omarchy owner must deliberately sign
and publish the validated bytes. `bin/repo release` rebuilds before publication;
`push` and `upload-prebuilt` also publish. None supplies native qualification.
Do not silently substitute newly rebuilt bytes during signing/publication.
Finally, install the signed published package on a fresh consumer, verify its
signature and package/module digests, and perform a short activation,
background-action, and cleanup smoke. Broader channels or unattended publishing
require an enforced artifact-to-evidence gate, including prebuilt uploads.