the avatar draws: serve the rig over loopback, not the opaque scheme
This commit is contained in:
parent
a5f0fadba8
commit
3d3eb2cc8e
4 changed files with 144 additions and 42 deletions
|
|
@ -45,27 +45,6 @@ Item {
|
|||
anchors.fill: parent
|
||||
hoverEnabled: !Config.options.bar.tooltips.clickToShow
|
||||
|
||||
// Double tap summons her face, and dismisses it. TASK-59 Q0 asked how
|
||||
// she is reached; Casey, 2026-08-05: *"I double tap on the clock widget
|
||||
// and I get the avatar."* Deliberately not always-on — the webview is
|
||||
// 283 MB measured, which on a 3.5 GB daily driver is a design argument
|
||||
// and not only a taste one.
|
||||
//
|
||||
// A toggle rather than a summon, because the same gesture has to be the
|
||||
// way out: a presence you cannot dismiss is the user's column being
|
||||
// ignored (doctrine §13, and TASK-59 Q4).
|
||||
property real lastTapAt: -1
|
||||
readonly property int doubleTapInterval: 350
|
||||
onClicked: {
|
||||
const now = Date.now();
|
||||
if (lastTapAt > 0 && now - lastTapAt <= doubleTapInterval) {
|
||||
lastTapAt = -1;
|
||||
Face.toggle();
|
||||
} else {
|
||||
lastTapAt = now;
|
||||
}
|
||||
}
|
||||
|
||||
ClockWidgetPopup {
|
||||
hoverTarget: mouseArea
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,4 +219,42 @@ Item {
|
|||
style: Config.options.background.widgets.clock.cookie.dateStyle
|
||||
}
|
||||
}
|
||||
// Double tap summons Annie. Casey, 2026-08-05: *"I double tap on the clock
|
||||
// widget and I get the avatar."* The desktop clock, not the bar's — this is
|
||||
// the one on the wallpaper, on home, where she has room to stand.
|
||||
//
|
||||
// A toggle, because the same gesture has to be the way out: a presence you
|
||||
// cannot dismiss is the user's column being ignored (doctrine §13). The
|
||||
// flip/fade/puff the clock should do on the way is deliberately not here
|
||||
// yet — the trigger first, the theatre after.
|
||||
// Touching the singleton here is what constructs it. Quickshell builds
|
||||
// singletons lazily, so `Face`'s IpcHandler did not register until
|
||||
// something reached for it — `qs ipc call face status` answered "Target not
|
||||
// found" while the double tap below worked fine, because the tap was the
|
||||
// first reference. The dial and the agent both need it addressable before
|
||||
// anyone has tapped anything.
|
||||
readonly property bool faceReady: Face.rigPresent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
// Above the clock's children, not below. `z: -1` was the first guess
|
||||
// and it is why the first version did nothing: the clock's own visual
|
||||
// items sit at the default z, so the handler was underneath every one
|
||||
// of them. Nothing here competes for the tap — the clock has no other
|
||||
// input at all, and never has.
|
||||
z: 1
|
||||
property real lastTapAt: -1
|
||||
onClicked: {
|
||||
const now = Date.now();
|
||||
console.log("[face] clock tap at " + now
|
||||
+ " (delta " + (lastTapAt > 0 ? now - lastTapAt : -1) + ")");
|
||||
if (lastTapAt > 0 && now - lastTapAt <= 350) {
|
||||
lastTapAt = -1;
|
||||
Face.toggle();
|
||||
} else {
|
||||
lastTapAt = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,25 +50,18 @@ Singleton {
|
|||
+ "These are the postures the presence system already uses. "
|
||||
+ "Use them sparingly, where the shift is real."
|
||||
|
||||
// The rig is not in the repo and never will be — it is licensed assets on
|
||||
// Casey's own device (TASK-59). So its absence is the ordinary state of any
|
||||
// machine that has not staged it, and it must read as "not set up here"
|
||||
// rather than as a broken face.
|
||||
property bool rigPresent: rigProbe.exists
|
||||
FileView {
|
||||
id: rigProbe
|
||||
path: root.rigDir + "/index.html"
|
||||
preload: true
|
||||
}
|
||||
|
||||
// No pre-flight check on the rig.
|
||||
//
|
||||
// There was one, reading `FileView.exists`, and it reported false for a
|
||||
// directory that was plainly there — so the guard meant to explain a
|
||||
// missing rig became the thing preventing a present one from loading. The
|
||||
// host already fails loudly and specifically when the directory is wrong,
|
||||
// and `onExited` puts `joined` back, so the honest answer is to let it try
|
||||
// and report what actually happened. A guard that can be wrong about the
|
||||
// world is worse than no guard.
|
||||
function join() {
|
||||
if (root.joined)
|
||||
return;
|
||||
if (!root.rigPresent) {
|
||||
console.log("[face] no rig at " + root.rigDir
|
||||
+ " — stage it with tools/stage-face.sh; not joining");
|
||||
return;
|
||||
}
|
||||
host.running = true;
|
||||
root.joined = true;
|
||||
}
|
||||
|
|
@ -114,6 +107,33 @@ Singleton {
|
|||
|
||||
signal tapped(string area)
|
||||
|
||||
// Reachable by name, so the dial, a launcher and the agent all summon her
|
||||
// the same way rather than each growing a copy (TASK-30/31). `status`
|
||||
// answers rather than assumes — an agent that cannot ask whether she is up
|
||||
// has to guess, and guessing is what a verb table exists to stop.
|
||||
IpcHandler {
|
||||
target: "face"
|
||||
|
||||
function toggle(): void {
|
||||
root.toggle();
|
||||
}
|
||||
|
||||
function join(): void {
|
||||
root.join();
|
||||
}
|
||||
|
||||
function leave(): void {
|
||||
root.leave();
|
||||
}
|
||||
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
joined: root.joined,
|
||||
rig: root.rigDir
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Socket {
|
||||
id: sock
|
||||
path: root.socketPath
|
||||
|
|
|
|||
Loading…
Reference in a new issue