split chooser, home reaches the compositor, idle-inhibit comment was backwards
This commit is contained in:
parent
f7eba9f658
commit
b0688bc30b
4 changed files with 130 additions and 12 deletions
|
|
@ -82,8 +82,18 @@ pub fn run(sink: Arc<dyn Fn(IdleEvent) + Send + Sync>) -> Result<()> {
|
|||
);
|
||||
};
|
||||
|
||||
// `get_idle_notification` (not the input-ignoring variant): we want the
|
||||
// idle timer to be reset by real input only.
|
||||
// `get_idle_notification`, which **respects idle inhibitors** — not
|
||||
// `get_input_idle_notification`, which ignores them and reports pure input
|
||||
// silence. An earlier comment here had this backwards.
|
||||
//
|
||||
// The difference is video. Input alone cannot see a film playing: nobody
|
||||
// touches the glass for two hours and the machine would blank on a screen
|
||||
// someone is watching. A player takes a `zwp_idle_inhibitor_v1`, viewtop
|
||||
// reports it through `set_is_inhibited`, and this notification simply never
|
||||
// idles while it is held. So the answer to "does playback keep the phone
|
||||
// awake" is yes, through the client's inhibitor rather than through faked
|
||||
// input — which also means a client that lies is visible as an inhibitor
|
||||
// rather than as phantom activity.
|
||||
let _notification = notifier.get_idle_notification(
|
||||
QUIET_PERIOD.as_millis() as u32,
|
||||
&seat,
|
||||
|
|
|
|||
|
|
@ -230,8 +230,14 @@ PanelWindow {
|
|||
GlobalStates.overviewOpen = false
|
||||
GlobalStates.dockRevealed = false
|
||||
GlobalStates.oskOpen = false
|
||||
Quickshell.execDetached(["hyprctl", "dispatch",
|
||||
"hl.dsp.focus({ workspace = 1 })"])
|
||||
// The compositor's own verb, not `hyprctl dispatch` — that binary
|
||||
// is not running under viewtop, so this call went nowhere and Home
|
||||
// silently stopped existing. Square one has to be reachable: it is
|
||||
// the state you get back to when everything else is confusing, and
|
||||
// a phone whose Home does nothing is one wrong gesture from being
|
||||
// stuck. `workspace` is served on the control socket and takes the
|
||||
// zone to go to.
|
||||
ViewtopControl.zone(Config.options.navigation?.homeZone ?? 1)
|
||||
}
|
||||
|
||||
// Commit an upward gesture. `stage` is the stage the drag settled on
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ Scope {
|
|||
// a blank sheet nobody can explain.
|
||||
property int target: 0
|
||||
property bool sheetOpen: false
|
||||
// Split has pinned a window and is waiting for the other half to be filled.
|
||||
property bool choosing: false
|
||||
|
||||
// How long a hold on Close means kill. Longer than a tap could ever be,
|
||||
// short enough that a stuck app does not feel like a negotiation.
|
||||
|
|
@ -64,6 +66,7 @@ Scope {
|
|||
|
||||
function dismiss() {
|
||||
scope.sheetOpen = false;
|
||||
scope.choosing = false;
|
||||
scope.target = 0;
|
||||
}
|
||||
|
||||
|
|
@ -98,6 +101,12 @@ Scope {
|
|||
// The window is gone or moved; the sheet has nothing left to be
|
||||
// about. Kept open for `pose`-style verbs would mean a sheet
|
||||
// pointing at a window that is no longer where it was.
|
||||
//
|
||||
// Except mid-Split: that `place` is the *first half* of a flow, and
|
||||
// dismissing on it would close the sheet before the chooser could
|
||||
// ask which window fills the other half.
|
||||
if (scope.choosing)
|
||||
return;
|
||||
if (intent === "close" || intent === "kill" || intent === "place")
|
||||
scope.dismiss();
|
||||
}
|
||||
|
|
@ -137,8 +146,68 @@ Scope {
|
|||
Behavior on opacity { NumberAnimation { duration: 120 } }
|
||||
}
|
||||
|
||||
// The chooser for Split's other half. Sits in the bottom half —
|
||||
// where the window it is choosing for will land — so the gesture
|
||||
// reads as "put something here" rather than as a menu that happens
|
||||
// to be on screen.
|
||||
ColumnLayout {
|
||||
visible: scope.choosing
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: parent.height * 0.58
|
||||
spacing: 14
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: ViewtopControl.windows.length > 1
|
||||
? "fill the other half"
|
||||
: "nothing else is open"
|
||||
color: "#e6ffffff"
|
||||
font.pixelSize: Appearance.font.pixelSize.normal
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ViewtopControl.windows.filter(w => w.id !== scope.target)
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: 220
|
||||
implicitHeight: 56
|
||||
radius: 14
|
||||
color: pick.pressed
|
||||
? Appearance.colors.colLayer2
|
||||
: Appearance.colors.colLayer1
|
||||
border.width: 1
|
||||
border.color: Appearance.colors.colLayer1Active
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
// The compositor answers with ids, not titles —
|
||||
// `workspaces` carries {id, workspace}. A title
|
||||
// needs foreign-toplevel, which is a separate
|
||||
// lookup; showing the id is honest until then.
|
||||
text: "window " + parent.modelData.id
|
||||
color: Appearance.colors.colOnLayer1
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pick
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
ViewtopControl.place(parent.modelData.id, 0,
|
||||
win.height / 2, win.width, win.height / 2);
|
||||
scope.choosing = false;
|
||||
scope.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The row. Above centre so a thumb does not cover the window.
|
||||
RowLayout {
|
||||
visible: !scope.choosing
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: parent.height * 0.38
|
||||
spacing: 28
|
||||
|
|
@ -159,16 +228,18 @@ Scope {
|
|||
SheetButton {
|
||||
icon: "splitscreen"
|
||||
label: "Split"
|
||||
// Split pins this window to one half and the other half
|
||||
// wants a chooser — open apps from the overview, or the
|
||||
// app list. That chooser is the missing half and it has no
|
||||
// verb yet, so this places the window and says so rather
|
||||
// than pretending the flow is finished.
|
||||
// Pin this window to the top half, then offer the other
|
||||
// half to something already open. Casey's shape: "the
|
||||
// second waiting one possibly having a button to add an
|
||||
// already opened one from the multitasking section".
|
||||
//
|
||||
// The list is queried, not assumed — a chooser offering a
|
||||
// window that has since closed would `place` a dead id and
|
||||
// refuse, which reads as the button being broken.
|
||||
onTapped: {
|
||||
ViewtopControl.place(scope.target, 0, 0, win.width, win.height / 2);
|
||||
refusal.text = "pinned to the top half — chooser for the other half is not built yet";
|
||||
refusal.opacity = 1;
|
||||
refusalFade.restart();
|
||||
ViewtopControl.refreshWindows();
|
||||
scope.choosing = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,17 @@ Singleton {
|
|||
signal refused(string intent, string reason)
|
||||
signal succeeded(string intent)
|
||||
|
||||
// What the compositor last said is on the canvas: `[{id, workspace}, …]`.
|
||||
// Queried, never cached across opens — a chooser showing a window that
|
||||
// closed a minute ago is worse than one that takes a moment to fill.
|
||||
property var windows: []
|
||||
signal windowsChanged_()
|
||||
|
||||
// Ask what is open. Answers into `windows`.
|
||||
function refreshWindows() {
|
||||
root._send({ op: "workspaces" });
|
||||
}
|
||||
|
||||
// Requests waiting on a connection. A queue rather than SessiondPolicy's
|
||||
// single slot: the sheet can fire two verbs in a row (kill after a close
|
||||
// that was refused), and dropping the second would be silent.
|
||||
|
|
@ -114,6 +125,19 @@ Singleton {
|
|||
root.scene("raise", { id: id });
|
||||
}
|
||||
|
||||
// Go to a zone. Not a `scene` intent — zones are the canvas, not a surface
|
||||
// on it, so the compositor serves this as its own op.
|
||||
//
|
||||
// "Zone", not "workspace", in everything we name: viewtop's canvas is a
|
||||
// large scalable space of states rather than Hyprland's numbered desks, and
|
||||
// the vocabulary is being moved off Hyprland's deliberately. The wire op is
|
||||
// still spelled `workspace` — renaming that is a separate sweep, and doing
|
||||
// it halfway would leave the shell calling an op the compositor does not
|
||||
// serve.
|
||||
function zone(to) {
|
||||
root._send({ op: "workspace", to: to });
|
||||
}
|
||||
|
||||
function focus(id) {
|
||||
root.scene("focus", { id: id });
|
||||
}
|
||||
|
|
@ -165,6 +189,13 @@ Singleton {
|
|||
root.refused(intent, why);
|
||||
} else {
|
||||
root.lastError = "";
|
||||
// A `workspaces` reply carries the canvas rather than a
|
||||
// verb's outcome. Captured here so a chooser has something
|
||||
// real to list instead of a guess at what is open.
|
||||
if (reply.windows !== undefined) {
|
||||
root.windows = reply.windows;
|
||||
root.windowsChanged_();
|
||||
}
|
||||
root.succeeded(intent);
|
||||
}
|
||||
root._drain();
|
||||
|
|
|
|||
Loading…
Reference in a new issue