# Insta360 Link 2 integration for OBS and KDE Hotkey and OBS integration on top of [`linkctl`](https://github.com/illegalstudio/linkctl), so AI tracking and gimbal presets are a keypress rather than a terminal command. Several pieces, usable independently: | Path | What it gives you | |---|---| | `bin/linkctl-macro` | A wrapper that makes `linkctl` safe to bind to a hotkey: finds the binary in a desktop session's sparse `PATH`, passes `--force`, and reports the result as a desktop notification. | | `kde/install-shortcuts.sh` | Registers KDE Plasma global shortcuts, so the keys work from anywhere including under Wayland. | | `obs/obs-linkctl.lua` | OBS hotkeys, buttons, and scene automation: a scene switch can set tracking or load a preset on its own. | | `plasmoid/` | A Plasma 6 widget with a virtual joystick, zoom slider, and Home/Center/Tracking buttons. Pointer or WASD/arrow keys. | | `bin/linkctl-gamepad` | Drives the gimbal from a game controller (DualSense and anything else the kernel exposes as a joystick). | ## Prerequisites `linkctl` installed and working (`linkctl status` reports your camera), and a saved home preset: ```sh linkctl preset save home # point the camera where you want "home" first ``` ## Install ```sh ./kde/install-shortcuts.sh --dry-run # review, changes nothing ./kde/install-shortcuts.sh ``` Default bindings, editable in the `BINDINGS` table at the top of that script: | Key | Action | |---|---| | `Meta+Alt+T` | toggle AI tracking | | `Meta+Alt+1` | tracking on | | `Meta+Alt+0` | tracking off | | `Meta+Alt+H` | `preset load home` | | `Meta+Alt+C` | recenter gimbal | | `Meta+Alt+←/→` | pan 5 degrees | | `Meta+Alt+↑/↓` | tilt 5 degrees | The directional ones are discrete nudges: `kglobalaccel` fires once per keypress and offers no press-and-hold, so holding the key will not pan smoothly. Use the widget or a gamepad for continuous motion. `NUDGE=10 ./kde/install-shortcuts.sh` changes the step. Check them afterwards with `./kde/install-shortcuts.sh --list`, which shows the registered key, the default from the desktop entry, and flags any value Plasma cannot parse. Or look in System Settings under Keyboard, Shortcuts, searching for "Insta360". Reverse the whole thing with `--uninstall`. The entries are visible in the launcher by default, because Plasma skips `NoDisplay` services when building the Shortcuts list and they would otherwise not appear there at all. `HIDE_FROM_MENU=1 ./kde/install-shortcuts.sh` hides them at that cost. ## Joystick widget ```sh ./plasmoid/install.sh ``` Then right-click a panel or the desktop, Add Widgets, and search for "Insta360". Pass `--restart` when reinstalling over a running copy, since Plasma otherwise keeps the old one loaded. The joystick is a velocity control: how far you push sets how fast the camera moves, and it stops when you let go. Degrees per step, step interval, deadzone and axis inversion are all in the widget's settings, along with the `linkctl` path and an optional device. **WASD and the arrow keys** work whenever the popup has focus, with the same velocity behaviour: hold to move, release to stop. Opposing keys cancel, and a diagonal moves at the same speed as a straight push rather than faster. The buttons and zoom slider are set to take no focus, so the arrow keys keep reaching the pad after you click one. If focus leaves the popup mid-press the held keys are cleared, so the camera cannot be left panning on its own. It drives the gimbal with *relative* moves rather than absolute ones, because `linkctl status` reports no pan or tilt at all while the camera is idle, so there is no trustworthy position to aim from. Relative steps work either way and cannot drift out of sync, and linkctl clamps each one to the camera's own range, so holding the stick against a limit is harmless. ## Game controller ```sh bin/linkctl-gamepad --list # which joystick devices exist bin/linkctl-gamepad --monitor # print events as you move sticks and press buttons bin/linkctl-gamepad # run it ``` Same velocity behaviour as the widget: push the left stick to move, release to stop. Defaults are a DualSense on the in-tree `hid_playstation` driver, left stick on axes 0 and 1, with buttons 0, 1 and 2 bound to recenter, home preset and tracking toggle. **Confirm your mapping with `--monitor` before trusting the defaults.** Axis and button numbering comes from the driver, not the controller, so it varies between kernels and connection modes (a DualSense often enumerates differently over Bluetooth than over USB). `--monitor` prints the number for whatever you touch, and everything is overridable: ```sh bin/linkctl-gamepad --pan-axis 0 --tilt-axis 1 --step 8 --deadzone 0.2 --button 3="tracking toggle" --button 9="preset load home" ``` It reads `/dev/input/js*` directly, using only the Python standard library. Qt 6 removed QtGamepad with no replacement, so the widget cannot do this itself and this runs as a separate process. You need to be in the `input` group to open the device. `--wait` makes it tolerate the controller being absent and reconnect when it returns, which is what you want for a wireless pad that sleeps. There is a user service in `contrib/linkctl-gamepad.service` for running it that way; edit the `ExecStart` path before installing it. ## OBS Tools, Scripts, `+`, then pick `obs/obs-linkctl.lua`. Set the `linkctl` binary path if it is not on OBS's `PATH`, then bind the hotkeys under Settings, Hotkeys (search "Insta360"). **Scene automation.** Press *Insert current scene names* to prefill the mapping box, then give the scenes you care about an action: ``` Talking Head = tracking on Overhead = preset load overhead Slides = tracking off ``` The left side is the scene name, the right side is any `linkctl` arguments. Blank actions and `#` comments are ignored. **Startup action** runs when OBS finishes loading, and defaults to `preset load home`. This is the setting worth keeping: the Link parks its gimbal facing down when idle, so launching OBS puts it back on its own. Under Wayland, OBS's own hotkeys only fire while OBS has focus. That is an OBS limitation, and the reason the KDE shortcuts exist; install both and use the KDE ones for anything you need mid-stream. ## Configuration Optional, at `~/.config/linkctl-macro.conf` (see `linkctl-macro.conf.example`): ```sh LINKCTL=/usr/local/bin/linkctl # if not on PATH LINKCTL_DEVICE=/dev/video0 # pin the camera if you have more than one LINKCTL_FORCE=1 # 0 respects linkctl's idle-camera guard LINKCTL_NOTIFY=always # always | error | never ``` `--force` is on by default because a deliberate keypress means "do it now", including when nothing currently holds the camera open. That is exactly the case where the gimbal is parked facing down. ## Testing without a camera ```sh lua tests/test_obs_linkctl.lua # pure helpers: quoting, parsing, commands lua tests/test_obs_lifecycle.lua # the OBS script against a mock OBS API node tests/test_joystick_logic.js # widget command building, parsing, key vectors python3 tests/test_gamepad.py # gamepad event decoding and deadzone maths tests/test_gamepad_loop.sh # the gamepad read loop, fed through a FIFO bin/linkctl-macro --print tracking on # show the command without running it bin/linkctl-gamepad --dry-run # print moves instead of sending them ./kde/install-shortcuts.sh --dry-run # show the KDE changes, apply nothing ./kde/install-shortcuts.sh --list # diagnose what is actually registered shellcheck bin/linkctl-macro kde/install-shortcuts.sh plasmoid/install.sh ``` The lifecycle suite drives the script the way OBS does, so it catches broken wiring (hotkeys not firing, scene actions not matching, the preset name being captured too early) without needing OBS or a camera. `test_gamepad_loop.sh` substitutes a FIFO for `/dev/input/js*` and plays scripted events through it, so the select/read/decode/tick path is covered without a controller attached. It is also the test that pins down the initial-state burst: opening a joystick replays every button as currently pressed, and treating that as real input would fire mapped actions at startup. ## Troubleshooting **Shortcut does nothing, and System Settings says "No default shortcuts".** Run `./kde/install-shortcuts.sh --list`. A `_launch` value containing commas is the Plasma 5 format: Plasma 6 passes that value straight to `QKeySequence::fromString`, so `Meta+Alt+T,none,Some Name` parses to nothing and binds no key. Re-running the installer rewrites it as a bare key sequence. Versions of this repo before that fix wrote the Plasma 5 form. **Shortcut still does nothing.** Run the action directly: `bin/linkctl-macro tracking on`. A failure notification names the cause. If that works but the key does not, check for a conflict in System Settings, and confirm `--list` shows the entry as `present`. **Gamepad does nothing.** Check `bin/linkctl-gamepad --list` sees it. If the device is listed but nothing moves, run `--monitor` and push the stick: no events means a permissions or driver problem, events with unexpected axis numbers mean you need `--pan-axis`/`--tilt-axis`. "permission denied" means adding yourself to the `input` group and logging back in. **Arrow keys change zoom instead of moving the camera.** That would mean the slider has keyboard focus. The controls are set to `Qt.NoFocus` to prevent it; if you see it, say so, because it means the focus policy is not taking effect on your Plasma build. **`tracking` fails with exit 12.** AI tracking is experimental in `linkctl` and goes through the camera's vendor extension unit. Pan, tilt, zoom and presets use standard UVC and are unaffected. **Permission denied (exit 6).** Add yourself to the `video` group, or install the udev rule from `linkctl`'s `contrib/99-insta360-link.rules`. **Nothing moves at all.** The Link 2 rejects single-axis pan/tilt writes and needs both axes set in one `VIDIOC_S_EXT_CTRLS` call. `linkctl` does this correctly; raw `v4l2-ctl` needs `--set-ctrl pan_absolute=0,tilt_absolute=0` as one argument. ## Status The helpers, the shell scripts and the gamepad read loop are tested offline, and the KDE installer is dry-runnable and reversible. Use `--dry-run` first. Not verified against real hardware at the time of writing: no live camera, no running Plasma session, and no physical game controller. The gamepad bridge was tested by feeding synthetic kernel events through a FIFO, which exercises every line of the loop but cannot confirm that a DualSense reports the axis and button numbers assumed by the defaults. Treat `--monitor` as a required first step rather than a debugging aid.