main
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f95853dc25 |
Add GPL-3.0 licensing and prepare for a public repository
metadata.json claimed MIT while no LICENSE file existed, so the repository had no effective licence at all. It is now GPL-3.0-or-later: the full FSF text in LICENSE, the identifier in metadata.json, and REUSE-style SPDX-FileCopyrightText and SPDX-License-Identifier headers on all 33 source files, which is the convention KDE itself uses. Worth recording that GPL-3.0 is a preference here rather than an obligation. Nothing in the dependency graph forces it: linkctl is MIT and is invoked as a subprocess rather than linked, and the KDE QML modules the applet imports are LGPL. Removed two things that should not ship in a public repository. tests's QML linter had an absolute sandbox path, /tmp/pyside/PySide6/qmllint, baked into its search list; it now looks for Qt's qmllint and PySide6's pyside6-qmllint on PATH and honours a QMLLINT override, and says how to install one when it finds nothing. The systemd unit carried a Documentation= URL pointing at a host that does not exist, and its ExecStart silently assumed a particular checkout directory; the URL is gone and the path is now an obvious PATH/TO/CHECKOUT placeholder next to the comment telling you to edit it. Added a Credits section naming the work this is built on, since the project is mostly glue: linkctl does the camera control, schlarpc's firmware teardown documents the extension-unit protocol, and vrwallace's controller established that the Link 2 needs both pan and tilt axes in a single VIDIOC_S_EXT_CTRLS call, which is the detail the whole thing depends on. Also made the two Python tests executable, matching the other scripts. Audited for operator-specific detail: no credentials, tokens, private hostnames, IP addresses or absolute home paths in any tracked file or anywhere in the history's diffs. Three merge commits do carry Reviewed-on and Reviewed-by trailers naming a private forge, added by the forge at merge time; those are commit metadata rather than file content and are handled separately. Tested: 169 checks across seven suites plus the QML linter, all passing after the header insertion, which confirms it did not disturb the order-sensitive `.pragma library` line in logic.js or the qmldir module definitions. shellcheck clean. |
||
|
|
1f0d5ec0f4 |
Fix the QML scope error that disabled all camera movement
The joystick, the keyboard and every movement command did nothing. The cause
was in the journal all along:
main.qml: ReferenceError: stick is not defined
`stick` is the id of the Joystick inside the `fullRepresentation` Component.
Ids declared inside a Component are not visible from the enclosing scope, so
the inNx, inNy and inEngaged properties declared on the root object could not
resolve it. Those bindings threw, inEngaged stayed false, the tick timer never
ran, and no input of any kind produced a command. Everything else kept
working, which is why status, zoom and binary resolution all looked healthy in
the log while dragging did nothing.
The three properties now live inside the fullRepresentation, where both
`stick` and the root object are in scope.
Also fixed from the same log: the configuration page logged "does not have a
property called cfg_<name>Default" for all eight keys plus `title`, because
Plasma assigns those when it instantiates a config page and they were never
declared. They are declared now, so the Defaults button has something to
compare against.
Startup probed for the binary twice, because Component.onCompleted and
onConfiguredBinChanged both fired while the component was still being set up,
duplicating every follow-up query. The second path is now guarded.
The diagnostics readout is one label built by one guarded function instead of
a Repeater over an array of expressions. In that earlier shape a single
throwing sub-expression discards the whole model and the panel renders empty,
which is precisely the failure mode it is supposed to diagnose, and is what it
did.
The real repair is to the verification, in tests/lint_qml.py. qmllint had
flagged `stick`, and a filter discarded it. Without the KDE QML modules
present qmllint cannot resolve PlasmoidItem, so nothing inside it has a known
scope and all 87 identifiers in a clean file are reported as unqualified
access. Filtering that category wholesale was the only way to get a readable
result, and it threw away the one finding that mattered.
tests/qmlstubs now holds minimal stand-ins for org.kde.plasma.plasmoid,
org.kde.plasma.components, org.kde.plasma.plasma5support and
org.kde.kirigami, so the types resolve and the scope analysis is meaningful.
With the stubs in place the only remaining unqualified accesses are `root`,
`sh` and `modelData`, each allowlisted with a reason, so anything else is a
genuine finding. Confirmed by reintroducing the bug: the linter fails and
names the exact lines and identifier.
Tested: 169 checks across seven suites plus the new linter, all passing.
shellcheck clean.
|
||
|
|
82974f242d |
Never reuse a command string for Plasma's executable engine
Plasma's executable data engine keys its command containers by the command string. DataEngine::connectSource calls requestSource, which returns a pre-existing container when one matches, and then delivers that container's cached data; the process itself only starts when the container is first constructed. Re-issuing a byte-identical command therefore reports success, with a plausible exit code and stdout, while running nothing at all. That is precisely what the joystick produces. A stick held at full deflection emits the same command every tick, so the camera would take one step and then stop, with the widget showing no error because it received a successful result. The same applies to a key held down, and to repeated presses of Center or Home. Every command now carries a trailing shell comment holding a counter, so the engine never sees a string it has already cached. `#` starts a comment in sh, and the commands already contain quotes so they always go through sh -c, meaning the suffix changes the engine's key without changing what runs. Resolution state moved from comparing the probe's command string to a plain boolean, since the string is no longer stable across calls. Tested: 65 node checks, including that successive sequence numbers differ and that the suffix lands after a chained diagonal command. tests/test_resolver.sh additionally runs the generated command through a real shell both with and without the suffix and requires identical output, so the uniqueness trick cannot quietly alter behaviour. Full suite of 172 checks across seven suites passes, shellcheck clean, QML free of syntax errors. This is a defect established by reading Plasma's source, not a confirmed diagnosis of the reported fault. The two earlier theories for that fault, PATH resolution and a stale pinned device node, were both ruled out by the reporter: linkctl is installed from a pacman package into /usr/bin, and the widget's device field was never set. |
||
|
|
fa2d4e89d8 |
Resolve linkctl explicitly in the widget, and surface failures
The widget could not run linkctl even though the binary worked fine from a terminal and through linkctl-macro. Plasma executes applet commands with KProcess::setShellCommand, which is `/bin/sh -c` inheriting plasmashell's own environment. That environment never sources a login profile, so any install that puts linkctl on PATH from a shell rc file is invisible to the widget: mise and asdf shims, and ~/.local/bin on some distributions. bin/linkctl-macro has carried a resolver for exactly this reason since it was written, and the widget should have had one too rather than trusting a bare name. It now searches the same locations at startup, caches the absolute path, and holds commands back until it has one. Three further changes, all about not failing silently. The status line is now always visible. It previously appeared only while the pan and tilt readout was unknown, so any failure after the first successful poll was hidden behind a stale position. errorMessage explains the exit codes that are easy to misread. 127 is the shell failing to run the binary rather than anything about the camera, and 3 most often means a pinned device node has moved, which happens because the Link's /dev/videoN number is not stable across power cycles or replugs. A watchdog clears the busy flag after four seconds. Plasma's executable engine silently drops a request while an identical command is still running, so a lost callback could leave busy set and freeze every subsequent step with nothing to indicate why. Also corrected the focus timing for the keyboard support: Component.onCompleted can run before the popup is on screen, where the focus grab does not stick, so the panel claims focus whenever the popup opens and clears held keys when it closes. Tested: 164 checks across seven suites, all passing. tests/test_resolver.sh is new and runs the generated resolver in a real shell against real directories, confirming it finds a binary on PATH, finds mise shims, ~/.local/bin and ~/.cargo/bin when they are not on PATH, prefers PATH when both exist, ignores a non-executable file, and prints nothing when the binary is genuinely absent. 11 new node checks cover the command's shape and confirm a hostile configured name stays quoted. Not verified against a live Plasma session. The PATH mechanism is established from KProcess's documented behaviour and the resolver from a real shell, but whether this was the actual cause on the reporter's machine is unconfirmed. |
||
|
|
eb303db316 |
Add keyboard, gamepad and directional-shortcut control
Three input methods for the gimbal, all sharing the widget's velocity model: hold to move, release to stop. Keyboard in the applet. WASD and the arrow keys feed the same tick timer the pointer does, through a new vectorFromKeys helper. Opposing keys cancel and a diagonal is normalised to length 1, so two keys do not move faster than one. Auto-repeat is discarded, because it arrives as a press/release cycle and would make the motion stutter. The held keys are cleared when the popup loses focus: otherwise the matching release never arrives and the camera keeps panning on its own. The buttons and zoom slider are set to Qt.NoFocus so the arrow keys still reach the pad after one of them is clicked. Gamepad support, as a separate process. Qt 6 removed QtGamepad and ships no replacement, so the applet cannot read a controller itself. bin/linkctl-gamepad reads /dev/input/js* directly using only the standard library: the event struct is eight bytes and needs no third-party package. Defaults target a DualSense on hid_playstation, but axis and button numbering comes from the driver rather than the controller and varies between kernels and connection modes, so --monitor prints live events for mapping and every number is overridable. Opening a joystick replays the current state of every button with JS_EVENT_INIT set; those are filtered, or a held button would fire its mapped action at startup. --wait reconnects a wireless pad that sleeps. Directional global shortcuts. Meta+Alt plus the arrow keys nudge the gimbal. These are discrete steps by necessity: kglobalaccel fires once per keypress and exposes no press-and-hold, so this cannot be smooth motion and the README says so and points at the widget or a gamepad instead. NUDGE sets the degrees. Tested: 145 checks across five suites, all passing, shellcheck clean, QML free of syntax errors. 13 new node checks cover the key vector, including the cancellation and diagonal-normalisation cases. 36 new python checks cover event decoding, the INIT flag, deadzone rescaling and command construction. A new FIFO-driven suite stands in for /dev/input/js* and plays scripted events through the real select/read/decode/tick path, confirming diagonals emit both axes, a centred stick goes quiet, a deadzone deflection moves nothing, a real button press fires, the initial-state burst does not, and disconnect is handled. Not verified: no physical controller, camera or Plasma session was available, so the synthetic events cannot confirm a real DualSense reports the assumed axis and button numbers. |
||
|
|
bf6595614a |
Fix KDE shortcuts never binding, and add a joystick widget
The shortcuts wrote the Plasma 5 value format and so never bound anything, which is what produced "No default shortcuts" in System Settings. kglobalacceld's KServiceActionComponent::loadSettings reads the services _launch key as a bare key sequence and hands it straight to QKeySequence::fromString. Plasma 5 stored a "keys,default,name" triplet there and Plasma 6 does not, so "Meta+Alt+T,none,Insta360: Toggle AI tracking" parsed to an empty sequence and bound no key. The value is now just the key sequence; alternates would be tab-separated and "none" means unbound. Two supporting changes. The desktop entries now carry X-KDE-Shortcuts, which is where kglobalacceld reads an entry's default shortcut from, so the binding shows up in System Settings as a default instead of as nothing. And they are no longer NoDisplay by default: KApplicationTrader skips NoDisplay services, so hiding them from the launcher also hid them from the Shortcuts page, which is the opposite of helpful. HIDE_FROM_MENU=1 restores the old behaviour. --list is now a diagnostic rather than a bare dump: it reports the registered key, the default from the desktop entry, whether the entry file exists, and flags any comma-bearing value as the unparseable Plasma 5 form. It would have identified this fault directly. The new plasmoid is a Plasma 6 applet: a velocity-style virtual joystick plus zoom slider and Home/Center/Tracking buttons. It drives the gimbal with relative moves, not absolute ones, because linkctl status reports no pan or tilt while the camera is idle, leaving no trustworthy position to aim from; relative steps work in either state and cannot drift, and linkctl clamps them to the device range. The pure logic lives in a .js library so it is testable outside Plasma. Tested: 36 new node checks over shell quoting, tick command construction, inversion, the per-axis threshold and the multi-object JSON merge, each confirmed meaningful by mutation. Reproduced the broken shortcut state in a throwaway HOME and verified --list flags it and reinstalling repairs it. All QML passes qmllint with no syntax errors (KDE modules are unresolvable offline). shellcheck clean. The applet has not been loaded in a real Plasma session. |
||
|
|
cca3c1f2b4 |
Test the OBS script against a mock OBS API
The OBS script was the largest piece with no coverage, and its failure mode inside OBS is silence rather than an error, so wiring mistakes would have surfaced only as "the hotkey does nothing". Mocks obslua and drives the script through the sequence OBS uses: defaults, update, load, hotkey callbacks, frontend events, properties, save, unload. Asserts the behaviour that is easy to get wrong: key-down acts while key-up does not, the preset hotkey reads the preset name when pressed rather than when registered, an unmapped scene does nothing, repeated studio-mode events collapse, and the scene-prefill button preserves actions already assigned. Confirmed the suite has teeth by mutation: shrinking the debounce window, dropping --force, and capturing the preset name at registration time are each caught. |
||
|
|
f7e42f7132 |
Add OBS and KDE hotkey integration for linkctl
Wraps linkctl so the Insta360 Link 2's AI tracking and gimbal presets are reachable from a keypress and from OBS, instead of a terminal. bin/linkctl-macro resolves the linkctl binary itself, because a desktop shortcut is launched with a far sparser PATH than an interactive shell and would otherwise just fail silently. It defaults to --force on the grounds that a deliberate keypress means "act now" even when nothing holds the camera open, which is precisely the state the Link parks itself in. Results go to a desktop notification, since tracking state and gimbal position are invisible otherwise, and linkctl's documented exit codes are mapped to actionable text. kde/install-shortcuts.sh registers Plasma global shortcuts via .desktop entries plus kglobalshortcutsrc. These work under Wayland, where OBS's own hotkeys only fire while OBS has focus. It writes with the shortcut daemon stopped, because the daemon rewrites that file on exit and would discard the entries. Supports --dry-run, --list and --uninstall. obs/obs-linkctl.lua adds OBS hotkeys, test buttons, and scene-to-action mapping, so switching scenes can set the camera state unattended. Its startup action defaults to loading the home preset, which undoes the gimbal's idle parking when OBS launches. Commands are backgrounded so the OBS UI thread never waits on the camera, and identical commands within 250ms are collapsed to absorb the repeated events studio mode emits. Tested: 22 offline Lua checks over shell quoting, action tokenizing, scene map parsing and command construction; the wrapper exercised against a stub linkctl for every documented exit code; the KDE installer dry-run, install, list and uninstall paths run against stubbed Plasma tools in a throwaway HOME. shellcheck clean. Not yet run against a live camera or a real Plasma session. |