- a probe or raster-stall failure record now also downgrades sessions
that are already running: main_set_local_option broadcasts the
fallback for failed-* health writes, which also closes the hole where
the watchdog's idempotence guard no-oped after the probe had already
written the record
- probe success requires a frame timing newer than the first push:
'consumed' advances inside the plugin callback before the GL/Metal
upload, so a raster thread hanging in the driver no longer counts as
a pass (and cannot clear a previous failure)
- watchdog failures are tagged with the failing backend
(failed-watchdog-rgba/gpu); the rgba-only probe clears only the rgba
class, so a working pixel-buffer path can no longer re-enable a
broken D3D shared-handle path every launch
- the watchdog pauses while the session's window is hidden (new
session_set_render_visible FFI wired to the window minimize/restore
events): a display registered in a minimized window no longer records
a false global failure; observation now counts pushes within the
window rather than since registration
- probe failure verdicts additionally require a resumed lifecycle,
matching the raster-stall monitor
- linux plugin: deferred-unref grace lengthened to 10s (no raster-side
completion barrier exists; documented as heuristic), ref bumped
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- watchdog: compare the plugin's cumulative consumed counter against a
snapshot taken when arming (re-arm was a no-op before), and judge on
cumulative pushes + elapsed time so sparse damage-driven streams are
still detected
- failure handling is idempotent (one record per breakage) and updates
every render session like main_set_local_option does, not only the
failing one; the fallback toast is not claimed for multi-display
windows the soft path cannot rescue
- probe: skip when the consumed API is missing (old plugin) or a
raster-stall is recorded (compositing could hang the main window);
a fail verdict requires fresh frame timings and a non-minimized
window; a pass never clears failed-raster-stall; toast only when
texture rendering was effectively on; probe pixel is transparent
- raster-stall monitor: judge on frame-timing staleness (a mid-episode
hang was undetectable before), gate on lifecycle/minimized/idle with
a moving quiet anchor, threshold 30s, shared with the camera page;
clear stateGlobal minimized flag on plain window restore
- adopt mismatched frame sizes only in multi-ui-session mode (legacy
mode pairs frames loosely and could ping-pong between displays)
- drop the now-dead closeSession parameter from texture destroy();
trim comments to repo style
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: Add relative mouse mode
- Add "Relative Mouse Mode" toggle in desktop toolbar and bind to InputModel
- Implement relative mouse movement path: Flutter pointer deltas -> `type: move_relative` -> new `MOUSE_TYPE_MOVE_RELATIVE` in Rust
- In server input service, simulate relative movement via Enigo and keep latest cursor position in sync
- Track pointer-lock center in Flutter (local widget + screen coordinates) and re-center OS cursor after each relative move
- Update pointer-lock center on window move/resize/restore/maximize and when remote display geometry changes
- Hide local cursor when relative mouse mode is active (both Flutter cursor and OS cursor), restore on leave/disable
- On Windows, clip OS cursor to the window rect while in relative mode and release clip when leaving/turning off
- Implement platform helpers: `get_cursor_pos`, `set_cursor_pos`, `show_cursor`, `clip_cursor` (no-op clip/hide on Linux for now)
- Add keyboard shortcut Ctrl+Alt+Shift+M to toggle relative mode (enabled by default, works on all platforms)
- Remove `enable-relative-mouse-shortcut` config option - shortcut is now always available when keyboard permission is granted
- Handle window blur/focus/minimize events to properly release/restore cursor constraints
- Add MOUSE_TYPE_MASK constant and unit tests for mouse event constants
Note: Relative mouse mode state is NOT persisted to config (session-only).
Note: On Linux, show_cursor and clip_cursor are no-ops; cursor hiding is handled by Flutter side.
Signed-off-by: fufesou <linlong1266@gmail.com>
* feat(mouse): relative mouse mode, exit hint
Signed-off-by: fufesou <linlong1266@gmail.com>
* refact(relative mouse): shortcut
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* Added method equals to class LastWindowPosition to compare the contents of instances.
Added storage to common.dart for remembering what window position data has previously been written.
Factored the actual save code from saveWindowPosition to _saveWindowPositionActual and updated saveWindowPosition to call it through a debouncer, and only if the window position data has actually changed since the last call in the same instance.
Added named parameter 'flush' to saveWindowPosition in common.dart, and to _saveFrame in tabbar_widget.dart, and updated the onWindowClosed handler in tabbar_widget.dart to call _saveFrame with flush: true, forcing an immediate save on close.
Removed the _saveFrame debouncer from tabbar_widget.dart.
* saveWindowPosition: don't reschedule debounce if it's already in flight
* Reworked the logic in saveWindowPosition to collapse a rapid series of updates into one save at the end.
1. Always block desktop settings page if video connection exists, both mouse event and key event are blocked..
2. Server control page always block key event.
Signed-off-by: 21pages <sunboeasy@gmail.com>
* This happens when after changing DesktopTab to StatefulWidget, 1.2.7
and 1.3.0 have this problem.
* When `addConnection` in server_model.dart is called, the old closed
client is removed, the client parameter of buildConnectionCard is new,
but client id inside Consumer is old.
* The only state in cm page is timer, its value is kept in test.
* There may be a better way to solve the ui update.
Signed-off-by: 21pages <sunboeasy@gmail.com>
* block window body only so user can still click minisize button.
* cm doesn't show mask
* Remove focusable Offstage in tabbar_widget.dart
Signed-off-by: 21pages <sunboeasy@gmail.com>