nosignal-shell/modules/nsbar/NsOverlay.qml
28allday e42538896f fix(nosignal): hardware-test round 4 — Wi-Fi join, multi-monitor popouts, sudo focus
From the 2026-06-20 Acer muxless-Optimus hardware test:

- F-wifi (NsNetwork.qml): the Wi-Fi panel was display-only — no onClicked,
  no password field — so a secured network could not be joined from the GUI.
  Wire the existing Network service: click to connect; open/saved networks
  connect directly; secured-without-profile expands an inline password field
  (StyledTextField, echoMode Password). Also strip a stray NUL byte that the
  on-box edit left at the `?? " "` sentinel. HW-verified on the test laptop.
- F-multimon (NsShell.qml + NsBar.qml + NsOverlay.qml): bar popouts mirrored
  on every monitor. Add a `screen` identity to NsShell; pills pass their
  output name; each NsOverlay only renders when it owns the open panel ("" =
  unscoped, for the global power modal). HW-verified dual-monitor.
- F-sudo (SudoToggleRow.qml): the passwordless-sudo prompt used the shared
  TUI.float class with no focus rule, so keystrokes missed it and three blank
  tries tripped pam_faillock with no feedback. Use a dedicated `nosignal-sudo`
  class (builder ships float/center/pin/stayfocused rules) and call the new
  `nosignal-sudo-toggle enable-tui` wrapper, which keeps the terminal open on
  failure so the reason is visible.

Add .gitattributes (*.qml/*.conf/*.json text) for clean diffs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:49:18 +01:00

166 lines
4.4 KiB
QML

pragma ComponentBehavior: Bound
// NoSignal popup overlay: one full-screen layer per screen that hosts the active
// floating panel (driven by NsShell.open). A transparent click-catcher behind the
// panel closes on outside-click; Escape also closes. Panels are positioned below
// the bar, anchored center / right / under-trigger per their `anchorMode`.
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.services
import "panels" as Panels
Variants {
model: Screens.screens
PanelWindow {
id: ov
required property ShellScreen modelData
// This overlay owns the panel only when NsShell.screen names this output
// (or is "" = unscoped, e.g. the global power modal → show on all screens).
// Without this, every screen's overlay rendered the panel → mirrored popups.
readonly property bool onThisScreen: NsShell.screen === "" || NsShell.screen === modelData.name
screen: modelData
visible: NsShell.open !== "" && ov.onThisScreen
color: "transparent"
WlrLayershell.namespace: "nspanels"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
anchors.top: true
anchors.bottom: true
anchors.left: true
anchors.right: true
exclusiveZone: 0
// outside-click closes
MouseArea {
anchors.fill: parent
onClicked: NsShell.close()
}
// Escape closes
Item {
anchors.fill: parent
focus: ov.visible
Keys.onEscapePressed: NsShell.close()
}
// full-screen modal: Power Menu
Loader {
anchors.fill: parent
active: NsShell.open === "power" && ov.onThisScreen
sourceComponent: powerC
Component {
id: powerC
Panels.NsPowerMenu {}
}
}
Loader {
id: loader
active: NsShell.open !== "" && NsShell.open !== "power" && ov.onThisScreen
y: Theme.size.barHeight + 6
x: {
if (!item)
return 0;
const w = item.implicitWidth;
const mode = item.anchorMode ?? "right";
if (mode === "center")
return Math.round((ov.width - w) / 2);
if (mode === "trigger")
return Math.max(8, Math.min(ov.width - w - 8, NsShell.anchorX - w / 2));
return ov.width - w - 12; // right
}
// small enter slide
opacity: active ? 1 : 0
transform: Translate {
y: loader.active ? 0 : -5
}
Behavior on opacity {
NumberAnimation {
duration: 120
}
}
sourceComponent: {
switch (NsShell.open) {
case "calendar":
return calendarC;
case "quicksettings":
return quickSettingsC;
case "network":
return networkC;
case "bluetooth":
return bluetoothC;
case "notifications":
return notificationsC;
case "audio":
return audioC;
case "tray":
return trayC;
case "overview":
return overviewC;
default:
return null;
}
}
Component {
id: calendarC
Panels.NsCalendar {}
}
Component {
id: quickSettingsC
Panels.NsQuickSettings {}
}
Component {
id: networkC
Panels.NsNetwork {}
}
Component {
id: bluetoothC
Panels.NsBluetooth {}
}
Component {
id: notificationsC
Panels.NsNotifications {}
}
Component {
id: audioC
Panels.NsAudio {}
}
Component {
id: trayC
Panels.NsSystemTray {}
}
Component {
id: overviewC
Panels.NsOverview {}
}
}
}
}