Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
db2677a
chore(quest): claim quest/m1/hotkeys.md
kixelated Nov 6, 2026
8766c6f
feat(studio): system-wide hotkeys, arrow nudge, aspect lock, and kept…
kixelated Nov 6, 2026
4927056
fix(studio): never hold the hotkey map while the platform registers
kixelated Nov 6, 2026
0d6c3c6
fix(studio): hotkeys follow every scene rename and never fire while a…
kixelated Nov 6, 2026
2175742
fix(studio): a held focused hotkey fires once, and a renamed scene ke…
kixelated Nov 6, 2026
0d34486
fix(studio): keys bind as each platform hears them, the portal keeps …
kixelated Nov 6, 2026
51868fb
style: cargo fmt
kixelated Nov 6, 2026
c5664cc
Merge remote-tracking branch 'origin/main' into fix/windows-layout-ho…
kixelated Nov 6, 2026
5e2c99a
fix(studio): hotkeys register the key pressed on every layout
kixelated Nov 6, 2026
6330bd9
fix(studio): refuse keypad keys, which egui reports as their twins
kixelated Nov 6, 2026
02e0ecc
Merge remote-tracking branch 'origin/main' into quest/m1/hotkeys
kixelated Nov 6, 2026
650b9c7
Merge remote-tracking branch 'origin/main' into HEAD
kixelated Nov 6, 2026
f3ac74c
Merge remote-tracking branch 'origin/main' into HEAD
kixelated Nov 6, 2026
2fc7be1
Merge origin/main into quest/m1/hotkeys
kixelated Nov 6, 2026
833ee5f
fix(studio): settle the online review of the hotkeys PR
kixelated Nov 6, 2026
4a24431
fix(studio): end abandoned key captures and report hotkey clip saves
kixelated Nov 6, 2026
74215c4
fix(studio): end a key capture the operator left while the window is …
kixelated Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Every panel is a tab you can drag, split, or stack.
| Output | Start and stop recording, pick the mix it records, set the canvas size and frame rate, add, resize, and remove further canvases, and see the broadcast path, whether the program is encoding, the encoder sessions open, and each canvas's renditions' encoder work. |
| Health | See how the stream reaches viewers, relay by relay and track by track, and the cause of each problem, and which recordings a crash left unfinished were repaired at launch. |
| Replay | Replay the last moments of the program or a source in a replay source, slowed down if you like, or save them as a clip. |
| Hotkeys | Bind keys to cut, transition, preview a scene, replay, save a clip, and start or stop recording, and clear them (see [Hotkeys](#hotkeys)). |

A new source, from the Sources or Devices panel, is placed over whatever the
Items panel shows. The **top layer** is drawn over every scene, in preview and
Expand Down Expand Up @@ -215,7 +216,12 @@ edges and centers to the canvas's edges and center, to the other items shown,
and to the grid while **Grid** is on, drawing a guide through what it lines up
with. A moved item also snaps to the spacing of its neighbors, marking the
equal spaces. Hold Ctrl (Cmd on macOS) to drag without snapping, or turn
**Snap** off. The snap and grid settings last until the studio quits.
**Snap** off. Resizing a camera, display, window, image, media file, broadcast,
or replay keeps its shape, and resizing anything else does not; hold Shift to
flip that. Click the preview, then the arrow keys nudge the selected item a
pixel, or ten with Shift held, and a held key undoes as one step. The snap and
grid settings are kept per user, with the [hotkeys](#hotkeys), not in the scene
file.

Items showing the same source are **linked**: one capture, with the source's
filters, wherever it is shown. A **clone** is a new source showing another's
Expand Down Expand Up @@ -868,6 +874,49 @@ CPU, which the compositor uploads. A browser source takes that path with no
display at all too, and there renders its page in software, WebGL included, so
a WebGL page runs slower.

## Hotkeys

The Hotkeys panel binds a key to **Cut**, **Transition**, **Replay**, **Save
clip**, starting or stopping a recording, and putting each scene in preview.
Press **Bind**, then the key with any modifiers; Escape, or leaving the window
or the panel, cancels. Every action starts unbound, so no key a game uses is
taken unasked, and a key bound to one action is refused for another until it is
cleared there. Replay and Save clip use the Replay panel's picks, and a
recording the Output panel's.

The key pressed is the key that fires, on any keyboard layout: the panel names
it as the layout prints it, and the system hears the key where it sits, through
the layout in use when the studio starts or a key is bound. On X11, a key that
types only a letter outside A to Z, such as ö on a German layout, is refused.
Keypad keys are refused everywhere, since the window hears keypad 1 as the 1
above the letters.

Bound keys fire even while another app, such as a game in full screen, has
focus, and the app does not see them, so bind keys you do not type or play
with, such as a function key or one with modifiers:

- **macOS** and **Windows** register them with the system, which needs no
permission. A key another app already registered is refused, saying so. On
macOS 15 and later, the system refuses a key whose only modifiers are Option,
or Option and Shift.
- **Linux on X11** grabs them from the X server. Once a layout change moves
a key the studio holds, its keys stay as they are until it restarts, and the
panel says so.
- **Linux on Wayland**, where no app hears keys meant for another, asks the
desktop through its GlobalShortcuts portal (KDE Plasma, and GNOME 48 and
later). The desktop shows its own dialog, may bind other keys than those
asked, and lists them in its shortcut settings; the panel shows what it
bound. Without the portal, or if the dialog is declined, bound keys work only
while the studio has focus, and the panel says so.

The bindings, the snap and grid settings, and the output **Listen** plays on
are kept per user in the app's settings, `~/Library/Application
Support/moq-studio` on macOS, `$XDG_DATA_HOME/moq-studio` (or
`~/.local/share/moq-studio`) on Linux, and `%APPDATA%\moq-studio\data` on
Windows, apart from the scene file, so a scene file moves between machines
without one operator's keys. A key bound to a scene follows it when renamed in
the studio.

## Replay

Add a `replay` source and place it like any other, such as full screen in a
Expand Down
3 changes: 2 additions & 1 deletion quest/m0/destinations.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ A creator adds any number of RTMP, RTMPS, and WHIP destinations alongside
MoQ, each with its own codec and bitrate, and goes live to all of them at
once. Destinations that take the same picture scene and audio scene with identical
encode settings share one encoder. Each destination's status and errors show
in the UI.
in the UI. Going live and stopping are actions the Hotkeys panel can bind,
as recording is.

The fan-out lands here as a UI-free egress library, with the studio as its
first consumer. The library subscribes once to a MoQ broadcast and remuxes it
Expand Down
3 changes: 1 addition & 2 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The work held until m0 ships. A studio broadcast can be composed by its
viewers and by a server from the sources' own tracks and a layout track,
matching the program the studio composites, so a viewer can toggle and
prioritize elements. The studio gains features m0 left out: overlay assets
and restarts, global hotkeys, and isolated guest recordings.
and restarts, and isolated guest recordings.

## Plan

Expand All @@ -24,6 +24,5 @@ than opening an m2 for them.
## Required

- [Viewer-side composition](/quest/m1/compose/README.md) - sources as their own tracks plus a layout track, composed by viewers and by a server
- [Hotkeys and editor preferences](/quest/m1/hotkeys.md) - system-wide hotkeys, arrow nudge, aspect lock, and persisted editor settings
- [Keypad keys bind as hotkeys](/quest/m1/hotkeys-keypad.md) - scene and action keys on the keypad, apart from the main row
- [Scene graph view](/quest/m1/scene-graph.md) - a node view of picture and audio scenes, their routing, volume, pan, and waveforms
4 changes: 0 additions & 4 deletions quest/m1/hotkeys-keypad.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,3 @@ refuses keypad keys rather than binding them; picked 2026-11-06 (maintainer,
- Register global-hotkey's `Numpad*` codes on macOS, Windows, and X11, and
ask the GlobalShortcuts portal for `KP_*` keysyms on Wayland.
- `README.md` drops the note that keypad keys are refused.

## Required

- [Hotkeys and editor preferences](/quest/m1/hotkeys.md) - the bindings and settings store this extends
42 changes: 0 additions & 42 deletions quest/m1/hotkeys.md

This file was deleted.

22 changes: 22 additions & 0 deletions rs/moq-studio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ eframe = { version = "0.36", default-features = false, features = ["default_font
egui = "0.36"
# Docked, rearrangeable panels. 0.21 is the newest release and pins egui 0.36.
egui_dock = "0.21"
# System-wide hotkeys on macOS, Windows, and X11; Wayland goes through the
# GlobalShortcuts portal instead.
global-hotkey = "0.9"
hang = { workspace = true }
# Stamps a recording's file name with when it started. Already in the graph
# through moq-scene's humantime-serde.
Expand Down Expand Up @@ -80,6 +83,25 @@ toml = { workspace = true }
tracing = { workspace = true }
url = { workspace = true }
usage-rs = { workspace = true, features = ["completions", "config"] }
# The window's event loop, run here so keypad keys are noted before egui
# folds them into their main keyboard twins. eframe's version and features.
winit = { version = "0.30", default-features = false }

[target.'cfg(target_os = "linux")'.dependencies]
# The GlobalShortcuts portal for hotkeys on Wayland. 0.13, as moq-video's
# PipeWire capture uses, so the two share one D-Bus stack.
ashpd = { version = "0.13", default-features = false, features = ["global_shortcuts", "tokio"] }
futures-util = "0.3"
# Whether the window is on Wayland, where only the portal hears keys globally.
raw-window-handle = "0.6"
# Which keysyms the X11 layout puts on each key, so a hotkey grabs the key
# pressed. 0.13, as global-hotkey uses, so default builds compile one.
x11rb = "0.13"

[target.'cfg(windows)'.dependencies]
# Which virtual key the Windows layout puts on each key, so a hotkey
# registers the key pressed.
windows-sys = { version = "0.61", features = ["Win32_UI_Input_KeyboardAndMouse"] }

[dev-dependencies]
# A clock that moves wall time too, which renewed tokens' expiries read.
Expand Down
Loading