feat(vdisplay): lifecycle state machine + display state/release API (Stage 1)
Stage 1 of design/display-management.md — the lifecycle core + the display
management surface:
- vdisplay/lifecycle.rs: pure per-slot state machine (Idle/Active{refs}/
Lingering{until}/Pinned) with acquire/release/expiry/force-release
transitions. No I/O, no OS types — the platform-neutral distillation of the
Windows manager's model. Unit + a 200k-iteration seeded property walk
(no leaks / double-frees / refcount underflow across arbitrary interleavings).
- vdisplay/registry.rs: neutral snapshot/release facade over the per-OS
lifecycle owners. Windows reads/controls the VirtualDisplayManager; Linux
keep-alive (a per-session pool) lands in a following increment (needs GPU-box
validation).
- windows/manager.rs: additive snapshot() + force_release() (no behavior change
to the on-glass-validated path).
- mgmt: GET /api/v1/display/state (live/kept displays) + POST /api/v1/display/release
(tear down lingering/pinned now; refuses active). OpenAPI regenerated.
- web console: Virtual displays card gains a live-display list (polled) with
per-row + release-all buttons and a linger countdown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -138,6 +138,48 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/display/release": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"display"
|
||||
],
|
||||
"summary": "Release kept virtual displays",
|
||||
"description": "Tear down lingering/pinned displays now — so a physical-screen user gets their screen back\nwithout waiting out the linger. `slot` releases one; omit it to release all kept displays.\nActive (streaming) displays are never torn down here (that is session control).",
|
||||
"operationId": "releaseDisplay",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ReleaseDisplayRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The number of kept displays released",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ReleaseDisplayResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or invalid bearer token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/display/settings": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -230,6 +272,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/display/state": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"display"
|
||||
],
|
||||
"summary": "Live virtual displays",
|
||||
"description": "The host's managed virtual displays right now — active (streaming), lingering (kept after\ndisconnect, counting down to teardown), or pinned (kept indefinitely). See\n`design/display-management.md`.",
|
||||
"operationId": "getDisplayState",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The live/kept virtual displays",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DisplayStateResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or invalid bearer token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/gpus": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -1693,6 +1767,59 @@
|
||||
"av1"
|
||||
]
|
||||
},
|
||||
"ApiDisplayInfo": {
|
||||
"type": "object",
|
||||
"description": "One live or kept virtual display.",
|
||||
"required": [
|
||||
"slot",
|
||||
"backend",
|
||||
"mode",
|
||||
"state",
|
||||
"sessions"
|
||||
],
|
||||
"properties": {
|
||||
"backend": {
|
||||
"type": "string",
|
||||
"description": "Backend name (`pf-vdisplay`, `kwin`, …)."
|
||||
},
|
||||
"client": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Short client label, when the owner tracks it."
|
||||
},
|
||||
"expires_in_ms": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int64",
|
||||
"description": "Milliseconds until a lingering display is torn down (absent when active/pinned).",
|
||||
"minimum": 0
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"description": "`WIDTHxHEIGHT@HZ`."
|
||||
},
|
||||
"sessions": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "Live sessions holding the display.",
|
||||
"minimum": 0
|
||||
},
|
||||
"slot": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Stable-enough id for the `/display/release` `slot` argument.",
|
||||
"minimum": 0
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "`active` | `lingering` | `pinned`."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiError": {
|
||||
"type": "object",
|
||||
"description": "Error envelope for every non-2xx response.",
|
||||
@@ -2076,6 +2203,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DisplayStateResponse": {
|
||||
"type": "object",
|
||||
"description": "The host's managed virtual displays right now.",
|
||||
"required": [
|
||||
"displays"
|
||||
],
|
||||
"properties": {
|
||||
"displays": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ApiDisplayInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"EffectivePolicy": {
|
||||
"type": "object",
|
||||
"description": "The six resolved fields after preset expansion — what the lifecycle/registry and the Stage-0 call\nsites read, and what the mgmt API echoes as the \"currently in force\" policy. Pure output of\n[`DisplayPolicy::effective`].",
|
||||
@@ -2795,6 +2937,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ReleaseDisplayRequest": {
|
||||
"type": "object",
|
||||
"description": "Request body for `releaseDisplay`.",
|
||||
"properties": {
|
||||
"slot": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int64",
|
||||
"description": "Slot to release (see `state`); omit to release **all** kept displays.",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ReleaseDisplayResult": {
|
||||
"type": "object",
|
||||
"description": "Result of a `/display/release`.",
|
||||
"required": [
|
||||
"released"
|
||||
],
|
||||
"properties": {
|
||||
"released": {
|
||||
"type": "integer",
|
||||
"description": "Number of kept displays torn down.",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"RuntimeStatus": {
|
||||
"type": "object",
|
||||
"description": "Live host status (changes as clients launch/end sessions).",
|
||||
|
||||
Reference in New Issue
Block a user