add squeekboard toggle button
This commit is contained in:
@@ -7,7 +7,7 @@ A bottom desktop-environment panel for wlroots-based compositors, built on
|
|||||||
| ------- | ----------------------------------------------------------------- |
|
| ------- | ----------------------------------------------------------------- |
|
||||||
| Left | Application menu built from `.desktop` entries, with search |
|
| Left | Application menu built from `.desktop` entries, with search |
|
||||||
| Middle | Open windows (task list), click to focus, middle-click to close |
|
| Middle | Open windows (task list), click to focus, middle-click to close |
|
||||||
| Right | Wifi, bluetooth, and a session menu: log out, suspend, restart, shut down |
|
| Right | On-screen keyboard, wifi, bluetooth, and a session menu: log out, suspend, restart, shut down |
|
||||||
|
|
||||||
No clock, no tray, no status readouts — it is designed to sit alongside a
|
No clock, no tray, no status readouts — it is designed to sit alongside a
|
||||||
separate status bar.
|
separate status bar.
|
||||||
@@ -25,6 +25,8 @@ separate status bar.
|
|||||||
- NetworkManager for the wifi section, likewise optional. A NetworkManager
|
- NetworkManager for the wifi section, likewise optional. A NetworkManager
|
||||||
configured with `wifi.backend=iwd` is fine — and is what this was built
|
configured with `wifi.backend=iwd` is fine — and is what this was built
|
||||||
against — since the connection still goes through iwd underneath
|
against — since the connection still goes through iwd underneath
|
||||||
|
- squeekboard, as a systemd user unit, for the keyboard button — also optional;
|
||||||
|
where the unit is not installed the button is absent
|
||||||
|
|
||||||
The window list uses the foreign-toplevel protocol rather than any single
|
The window list uses the foreign-toplevel protocol rather than any single
|
||||||
compositor's IPC, so it works the same everywhere — except under river 0.4,
|
compositor's IPC, so it works the same everywhere — except under river 0.4,
|
||||||
@@ -84,6 +86,13 @@ The `launcher`, `wifi`, `bluetooth` and `power` IPC targets each accept
|
|||||||
focused window. Only one menu is open at a time, so `close` closes whichever it
|
focused window. Only one menu is open at a time, so `close` closes whichever it
|
||||||
is.
|
is.
|
||||||
|
|
||||||
|
The `osk` target takes the same three and shows or hides the keyboard instead,
|
||||||
|
which is what to bind to a hardware key or a tablet-mode switch:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
att_menu ipc call osk toggle
|
||||||
|
```
|
||||||
|
|
||||||
## Behaviour
|
## Behaviour
|
||||||
|
|
||||||
**Placement.** The panel is a layer-shell surface anchored to the left, right and
|
**Placement.** The panel is a layer-shell surface anchored to the left, right and
|
||||||
@@ -113,6 +122,17 @@ The launcher is a full-screen overlay layer surface rather than an
|
|||||||
`xdg-popup`, which is what makes click-away dismissal and keyboard focus work on
|
`xdg-popup`, which is what makes click-away dismissal and keyboard focus work on
|
||||||
any wlroots compositor without compositor-specific focus grabs.
|
any wlroots compositor without compositor-specific focus grabs.
|
||||||
|
|
||||||
|
**On-screen keyboard.** One button shows squeekboard, another click hides it,
|
||||||
|
and the button is accented while the keyboard is up. squeekboard runs as a
|
||||||
|
systemd user unit (`oskUnit`, `squeekboard.service` by default): showing starts
|
||||||
|
it if it is not already running, hiding leaves it running, since stopping it
|
||||||
|
would only make the next keypress wait for a fresh start. Whatever manages the
|
||||||
|
unit — a tablet-mode hook, say — stays in charge of when it exists at all.
|
||||||
|
|
||||||
|
The keyboard also raises and lowers itself when a text field takes or loses
|
||||||
|
focus, so the button follows squeekboard's own `Visible` property rather than
|
||||||
|
remembering what it last asked for, and stays right either way.
|
||||||
|
|
||||||
**Wifi.** The button shows signal strength in the arcs and the SSID in use next
|
**Wifi.** The button shows signal strength in the arcs and the SSID in use next
|
||||||
to them, struck through when the radio is off. The menu lists the networks in
|
to them, struck through when the radio is off. The menu lists the networks in
|
||||||
range — connected first, then saved, then by strength — with the switch in the
|
range — connected first, then saved, then by strength — with the switch in the
|
||||||
@@ -176,6 +196,8 @@ running with `qs --path .`).
|
|||||||
small output still gets a usable menu.
|
small output still gets a usable menu.
|
||||||
- `terminal` — argv prefix for entries with `Terminal=true`. Honours
|
- `terminal` — argv prefix for entries with `Terminal=true`. Honours
|
||||||
`$TERMINAL`; otherwise change the `["foot"]` fallback to your terminal.
|
`$TERMINAL`; otherwise change the `["foot"]` fallback to your terminal.
|
||||||
|
- `showOsk` — set `false` to drop the on-screen keyboard button entirely.
|
||||||
|
- `oskUnit` — the systemd user unit squeekboard runs as.
|
||||||
- `showWifi` — set `false` to drop the wifi button entirely.
|
- `showWifi` — set `false` to drop the wifi button entirely.
|
||||||
- `wifiLabelMaxWidth` — cap on the SSID shown on the panel.
|
- `wifiLabelMaxWidth` — cap on the SSID shown on the panel.
|
||||||
- `wifiMenuWidth` / `wifiMenuHeight` — also clamped to the screen.
|
- `wifiMenuWidth` / `wifiMenuHeight` — also clamped to the screen.
|
||||||
@@ -196,12 +218,14 @@ config/Config.qml Behaviour, power actions, compositor detection
|
|||||||
services/Session.qml Which menu is open, and on which screen
|
services/Session.qml Which menu is open, and on which screen
|
||||||
services/AppLauncher.qml Desktop-entry launching
|
services/AppLauncher.qml Desktop-entry launching
|
||||||
services/Attwm.qml attwm's IPC socket, when there is one
|
services/Attwm.qml attwm's IPC socket, when there is one
|
||||||
|
services/Osk.qml squeekboard: its unit, and its DBus state
|
||||||
modules/Bar.qml The panel window and its three sections
|
modules/Bar.qml The panel window and its three sections
|
||||||
modules/AppMenu.qml Left: launcher button
|
modules/AppMenu.qml Left: launcher button
|
||||||
modules/Launcher.qml The application menu overlay
|
modules/Launcher.qml The application menu overlay
|
||||||
modules/AppEntry.qml One row in the menu
|
modules/AppEntry.qml One row in the menu
|
||||||
modules/WindowList.qml Middle: task list
|
modules/WindowList.qml Middle: task list
|
||||||
modules/WindowButton.qml One task button
|
modules/WindowButton.qml One task button
|
||||||
|
modules/OskMenu.qml Right: on-screen keyboard button
|
||||||
modules/WifiMenu.qml Right: wifi button
|
modules/WifiMenu.qml Right: wifi button
|
||||||
modules/WifiPanel.qml The network list overlay
|
modules/WifiPanel.qml The network list overlay
|
||||||
modules/WifiNetworkEntry.qml One network row
|
modules/WifiNetworkEntry.qml One network row
|
||||||
@@ -215,6 +239,7 @@ components/AppIcon.qml Icon resolution with fallbacks
|
|||||||
components/PanelButton.qml Shared hover/press surface
|
components/PanelButton.qml Shared hover/press surface
|
||||||
components/BluetoothGlyph.qml The bluetooth rune, drawn
|
components/BluetoothGlyph.qml The bluetooth rune, drawn
|
||||||
components/WifiGlyph.qml The wifi fan, drawn
|
components/WifiGlyph.qml The wifi fan, drawn
|
||||||
|
components/KeyboardGlyph.qml The keyboard, drawn
|
||||||
components/ToggleSwitch.qml On/off switch
|
components/ToggleSwitch.qml On/off switch
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -251,6 +276,21 @@ components/ToggleSwitch.qml On/off switch
|
|||||||
the compositor it shares the session's group, where wrapping buys nothing. A
|
the compositor it shares the session's group, where wrapping buys nothing. A
|
||||||
scope rather than a service, so the app inherits the environment it needs —
|
scope rather than a service, so the app inherits the environment it needs —
|
||||||
`WAYLAND_DISPLAY` above all.
|
`WAYLAND_DISPLAY` above all.
|
||||||
|
- **Why the keyboard button shells out.** Quickshell has no generic DBus
|
||||||
|
binding, and squeekboard is driven entirely over DBus, so `services/Osk.qml`
|
||||||
|
uses `busctl`: a call per request, and one long-lived `busctl --json=short
|
||||||
|
monitor` as the state feed, parsed a line at a time. A monitor connection
|
||||||
|
sees signals it is not the destination of, which is what lets the button
|
||||||
|
follow a keyboard raised by something else. The two match rules deliberately
|
||||||
|
do not name a `sender`: dbus-broker does not deliver the bus driver's own
|
||||||
|
signals to a monitor that asks for them that way, and `NameOwnerChanged` —
|
||||||
|
how the panel notices squeekboard going away, since it does not announce that
|
||||||
|
itself — would never arrive. Showing retries the call for a few seconds
|
||||||
|
because the unit goes active a moment before squeekboard claims its name.
|
||||||
|
- `qs ipc call` cannot reach a handler function named `show`: the name collides
|
||||||
|
with the `ipc show` subcommand and the call silently turns into a listing. The
|
||||||
|
`osk` target uses `open` and `close` instead, which matches the menu targets
|
||||||
|
anyway.
|
||||||
- Icons are resolved through the Qt icon theme, falling back to the last segment
|
- Icons are resolved through the Qt icon theme, falling back to the last segment
|
||||||
of a reverse-DNS app id (`org.qutebrowser.qutebrowser` → `qutebrowser`), then
|
of a reverse-DNS app id (`org.qutebrowser.qutebrowser` → `qutebrowser`), then
|
||||||
to a lettered tile. The packaged build adds Adwaita and hicolor to
|
to a lettered tile. The packaged build adds Adwaita and hicolor to
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.config
|
||||||
|
|
||||||
|
// The keyboard rune, drawn. Icon themes ship it symbolic only — black on
|
||||||
|
// transparent, which Qt renders as-is and would be invisible on the dark
|
||||||
|
// panel — and drawing it makes it recolourable, which is how the on/off state
|
||||||
|
// is shown.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property int size: Theme.iconSize
|
||||||
|
property color color: Theme.text
|
||||||
|
|
||||||
|
implicitWidth: root.size
|
||||||
|
implicitHeight: root.size
|
||||||
|
|
||||||
|
// One key, and the space between two of them. Five keys and four gaps are
|
||||||
|
// exactly the width inside the case.
|
||||||
|
readonly property real key: Math.max(1, Math.round(root.size * 0.09))
|
||||||
|
readonly property real gap: Math.max(1, Math.round(root.size * 0.07))
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: root.size
|
||||||
|
height: Math.round(root.size * 0.72)
|
||||||
|
radius: Math.max(2, Math.round(root.size * 0.12))
|
||||||
|
color: "transparent"
|
||||||
|
border.width: Math.max(1, Math.round(root.size / 16))
|
||||||
|
border.color: root.color
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: root.gap
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: root.gap
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 5
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: root.key
|
||||||
|
height: root.key
|
||||||
|
color: root.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The space bar, short of the full width so the two rows above
|
||||||
|
// read as keys rather than as a grid.
|
||||||
|
Rectangle {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
width: root.key * 3 + root.gap * 2
|
||||||
|
height: root.key
|
||||||
|
color: root.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,16 @@ Singleton {
|
|||||||
return needsExecFlag.includes(env.split("/").pop()) ? [env, "-e"] : [env];
|
return needsExecFlag.includes(env.split("/").pop()) ? [env, "-e"] : [env];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- On-screen keyboard ----------------------------------------------
|
||||||
|
|
||||||
|
// Set false to drop the keyboard button. It also hides itself where the
|
||||||
|
// unit below is not installed.
|
||||||
|
readonly property bool showOsk: true
|
||||||
|
|
||||||
|
// The user unit squeekboard runs as. Showing the keyboard starts it if it
|
||||||
|
// is not already up; hiding it leaves the unit running.
|
||||||
|
readonly property string oskUnit: "squeekboard.service"
|
||||||
|
|
||||||
// ---- Wifi ------------------------------------------------------------
|
// ---- Wifi ------------------------------------------------------------
|
||||||
|
|
||||||
// Set false to drop the wifi button. It also hides itself where there is no
|
// Set false to drop the wifi button. It also hides itself where there is no
|
||||||
|
|||||||
@@ -41,7 +41,8 @@
|
|||||||
--add-flags "--path $out/share/att_menu" \
|
--add-flags "--path $out/share/att_menu" \
|
||||||
--suffix PATH : ${
|
--suffix PATH : ${
|
||||||
pkgs.lib.makeBinPath [
|
pkgs.lib.makeBinPath [
|
||||||
# loginctl and systemctl back the default power actions. The
|
# loginctl and systemctl back the default power actions, and
|
||||||
|
# systemctl and busctl the on-screen keyboard button. The
|
||||||
# session's own PATH is searched first, so a compositor's
|
# session's own PATH is searched first, so a compositor's
|
||||||
# swaymsg/hyprctl/niri is still picked up for logging out.
|
# swaymsg/hyprctl/niri is still picked up for logging out.
|
||||||
pkgs.systemd
|
pkgs.systemd
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ PanelWindow {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OskMenu {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
WifiMenu {
|
WifiMenu {
|
||||||
bar: bar
|
bar: bar
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.config
|
||||||
|
import qs.components
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
// Right section: the on-screen keyboard button. No menu behind it — a click
|
||||||
|
// shows squeekboard, another hides it.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Machines with no squeekboard unit get no button at all.
|
||||||
|
visible: Osk.available
|
||||||
|
|
||||||
|
implicitWidth: button.implicitWidth
|
||||||
|
|
||||||
|
PanelButton {
|
||||||
|
id: button
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
height: root.height - Theme.spacing
|
||||||
|
minContentWidth: Theme.iconSize
|
||||||
|
active: Osk.shown
|
||||||
|
activeColor: Theme.accent
|
||||||
|
|
||||||
|
onClicked: Osk.toggle()
|
||||||
|
|
||||||
|
KeyboardGlyph {
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: button.active ? Theme.background : Theme.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import QtQuick
|
||||||
|
import qs.config
|
||||||
|
|
||||||
|
// The on-screen keyboard: squeekboard, started as a systemd user unit and
|
||||||
|
// shown or hidden over DBus (`sm.puri.OSK0`).
|
||||||
|
//
|
||||||
|
// Quickshell has no generic DBus binding, so busctl does the talking. One
|
||||||
|
// long-lived `busctl monitor` carries the state — squeekboard raises and
|
||||||
|
// lowers itself on text-input focus as well, so the panel button has to follow
|
||||||
|
// the keyboard rather than remember what it last asked for — and a short-lived
|
||||||
|
// `busctl call` carries each request the other way.
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// squeekboard's well-known name doubles as its interface name.
|
||||||
|
readonly property string busName: "sm.puri.OSK0"
|
||||||
|
readonly property string objectPath: "/sm/puri/OSK0"
|
||||||
|
|
||||||
|
// Whether the keyboard is on screen.
|
||||||
|
property bool shown: false
|
||||||
|
|
||||||
|
// Whether squeekboard is on the bus at all. Starting the unit is part of
|
||||||
|
// showing, so this is state to report, not a precondition to check.
|
||||||
|
property bool running: false
|
||||||
|
|
||||||
|
// Whether the unit exists on this machine, asked once at startup. Without
|
||||||
|
// it there is no button, the same way a machine with no bluetooth adapter
|
||||||
|
// gets no bluetooth button.
|
||||||
|
property bool unitPresent: false
|
||||||
|
|
||||||
|
readonly property bool available: Config.showOsk && root.unitPresent
|
||||||
|
|
||||||
|
// What was last asked for, and whether a click landed while the previous
|
||||||
|
// request was still in flight — a show can take a moment when the unit has
|
||||||
|
// to start, and the second click must not be dropped.
|
||||||
|
property bool desired: false
|
||||||
|
property bool queued: false
|
||||||
|
|
||||||
|
function show(): void {
|
||||||
|
root.request(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide(): void {
|
||||||
|
root.request(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(): void {
|
||||||
|
root.request(!root.shown);
|
||||||
|
}
|
||||||
|
|
||||||
|
function request(visible: bool): void {
|
||||||
|
root.desired = visible;
|
||||||
|
|
||||||
|
// Optimistic: squeekboard announces the change over the bus a moment
|
||||||
|
// later and the monitor below overwrites this either way, including
|
||||||
|
// when the request fails. It only keeps the button from sitting dead
|
||||||
|
// while the unit starts.
|
||||||
|
root.shown = visible;
|
||||||
|
|
||||||
|
if (call.running)
|
||||||
|
root.queued = true;
|
||||||
|
else
|
||||||
|
root.dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatch(): void {
|
||||||
|
call.command = root.desired ? root.showCommand : root.hideCommand;
|
||||||
|
call.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starting a unit that is already running is a no-op, so this one command
|
||||||
|
// covers both "start the keyboard" and "show the keyboard again". The
|
||||||
|
// retry is for the gap between the unit going active and squeekboard
|
||||||
|
// claiming its name: until it does, the call has nowhere to land.
|
||||||
|
//
|
||||||
|
// $1 is the unit, $2 the bus name — which is also the interface name — and
|
||||||
|
// $3 the object path.
|
||||||
|
readonly property var showCommand: ["sh", "-c", `
|
||||||
|
systemctl --user start "$1" || exit 1
|
||||||
|
|
||||||
|
n=0
|
||||||
|
while [ "$n" -lt 30 ]; do
|
||||||
|
busctl --user call "$2" "$3" "$2" SetVisible b true >/dev/null 2>&1 && exit 0
|
||||||
|
n=$((n + 1))
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
`, "att_menu", Config.oskUnit, root.busName, root.objectPath]
|
||||||
|
|
||||||
|
// Hiding leaves the unit running: squeekboard idles cheaply, and stopping
|
||||||
|
// it would only make the next keypress wait for a fresh start.
|
||||||
|
readonly property var hideCommand: ["busctl", "--user", "call", root.busName, root.objectPath, root.busName, "SetVisible", "b", "false"]
|
||||||
|
|
||||||
|
function handleMessage(line: string): void {
|
||||||
|
let message;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(line);
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = message.payload ? message.payload.data : null;
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (message.member === "NameOwnerChanged") {
|
||||||
|
// data is [name, oldOwner, newOwner].
|
||||||
|
root.running = data[2] !== "";
|
||||||
|
|
||||||
|
// Nothing owns the name, so nothing is on screen. squeekboard does
|
||||||
|
// not get to announce that on the way out.
|
||||||
|
if (!root.running)
|
||||||
|
root.shown = false;
|
||||||
|
} else if (message.member === "PropertiesChanged") {
|
||||||
|
// data is [interface, changed, invalidated].
|
||||||
|
const changed = data[1];
|
||||||
|
if (changed && changed.Visible !== undefined) {
|
||||||
|
root.running = true;
|
||||||
|
root.shown = changed.Visible.data === true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: call
|
||||||
|
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
if (root.queued) {
|
||||||
|
root.queued = false;
|
||||||
|
root.dispatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The unit would not start, or squeekboard is no longer there to
|
||||||
|
// answer. Either way nothing is on screen.
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
root.shown = false;
|
||||||
|
root.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: monitor
|
||||||
|
|
||||||
|
// A monitor connection sees the signals without being their
|
||||||
|
// destination, so this follows squeekboard however it was told to
|
||||||
|
// show — by this panel, or by a text field taking focus.
|
||||||
|
//
|
||||||
|
// Matching on `sender` is deliberately left out: dbus-broker does not
|
||||||
|
// deliver the bus driver's own signals to a monitor that asks for them
|
||||||
|
// by sender, so NameOwnerChanged would never arrive.
|
||||||
|
command: ["busctl", "--user", "--json=short", "monitor", "--match", `type='signal',path='${root.objectPath}',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'`, "--match", `type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='${root.busName}'`]
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: line => root.handleMessage(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: probe
|
||||||
|
|
||||||
|
command: ["busctl", "--user", "--json=short", "get-property", root.busName, root.objectPath, root.busName, "Visible"]
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
// A request made since the probe started is the newer truth.
|
||||||
|
if (call.running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
root.shown = JSON.parse(this.text).data === true;
|
||||||
|
root.running = true;
|
||||||
|
} catch (e) {
|
||||||
|
// No output to parse: squeekboard is not running, and the
|
||||||
|
// name is not activatable, so nothing started it either.
|
||||||
|
root.shown = false;
|
||||||
|
root.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
running: Config.showOsk
|
||||||
|
|
||||||
|
command: ["systemctl", "--user", "show", "-p", "LoadState", "--value", Config.oskUnit]
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: root.unitPresent = this.text.trim() === "loaded"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The monitor is the whole state feed, so it has to outlive anything that
|
||||||
|
// can kill it — a bus restart above all. Setting `running` on a process
|
||||||
|
// that is already running does nothing, so a tick normally costs a
|
||||||
|
// comparison.
|
||||||
|
Timer {
|
||||||
|
interval: 2000
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
running: root.available
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
if (monitor.running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
monitor.running = true;
|
||||||
|
|
||||||
|
// A monitor only ever reports changes, so ask separately for the
|
||||||
|
// state it started from.
|
||||||
|
probe.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,27 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not a menu: this one drives squeekboard, so it is worth binding to a
|
||||||
|
// hardware key or a tablet-mode switch as well as clicking.
|
||||||
|
IpcHandler {
|
||||||
|
target: "osk"
|
||||||
|
|
||||||
|
function toggle(): void {
|
||||||
|
Osk.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named to match the menu targets above: `qs ipc call` swallows a
|
||||||
|
// function called `show` — it collides with the `ipc show` subcommand
|
||||||
|
// — and a lone `hide` next to `open` would read oddly anyway.
|
||||||
|
function open(): void {
|
||||||
|
Osk.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
Osk.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "bluetooth"
|
target: "bluetooth"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user