// Compact explanation and control surface for persistent privacy indicators. import Quickshell import Quickshell.Hyprland import Quickshell.Wayland import QtQuick import qs.config import qs.services import qs.widgets PanelWindow { id: root visible: ShellState.activityOpen && PrivacyState.anyActive color: "transparent" anchors.top: true anchors.right: true margins.top: Theme.barHeight + Theme.barGap * 2 margins.right: Theme.barSideMargin exclusiveZone: 0 implicitWidth: 350 implicitHeight: surface.implicitHeight WlrLayershell.namespace: "qs-popover-activity" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.None readonly property var activities: { const result = []; if (PrivacyState.recordingActive) result.push({ kind: "recording", glyph: "\u{F044A}", label: "Screen recording", detail: "Panama ยท " + root.elapsed(), tone: "danger", stoppable: true }); if (PrivacyState.screenSharingActive) result.push({ kind: "screen", glyph: "\u{F0379}", label: "Screen sharing", detail: PrivacyState.screenSharingApp || "Managed by the application", tone: "warn", stoppable: false }); if (PrivacyState.cameraActive) result.push({ kind: "camera", glyph: "\u{F0100}", label: "Camera", detail: PrivacyState.cameraApp || "Managed by the application", tone: "warn", stoppable: false }); if (PrivacyState.microphoneActive) result.push({ kind: "microphone", glyph: "\u{F036C}", label: "Microphone", detail: PrivacyState.microphoneApp || "Managed by the application", tone: "warn", stoppable: false }); return result; } function elapsed(): string { const total = Capture.recordingSeconds; const seconds = String(total % 60).padStart(2, "0"); const minutes = Math.floor(total / 60) % 60; const hours = Math.floor(total / 3600); return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${seconds}` : `${minutes}:${seconds}`; } onVisibleChanged: { if (root.visible) openAnimation.restart(); } Connections { target: PrivacyState function onAnyActiveChanged(): void { if (!PrivacyState.anyActive && ShellState.activityOpen) ShellState.close(); } } HyprlandFocusGrab { windows: [root] active: root.visible onCleared: ShellState.close() } Rectangle { id: surface implicitHeight: content.implicitHeight + Theme.popoverPadding * 2 anchors.left: parent.left anchors.right: parent.right radius: Theme.popoverRadius color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha) border.width: 1 border.color: Theme.alpha(Theme.fg, 0.08) PrismEdge { anchors.top: parent.top anchors.topMargin: 1 anchors.left: parent.left anchors.right: parent.right inset: parent.radius } transform: Scale { id: openScale origin.x: surface.width origin.y: 0 xScale: 1 yScale: 1 } Column { id: content anchors.left: parent.left anchors.right: parent.right anchors.margins: Theme.popoverPadding spacing: 8 Text { width: parent.width text: "Privacy & activity" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge font.weight: Font.DemiBold } Text { width: parent.width text: "Active access stays visible until it ends." color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall } Rectangle { width: parent.width height: 1 color: Theme.alpha(Theme.fg, 0.08) } Repeater { model: root.activities Rectangle { id: activityRow required property var modelData width: content.width height: 50 radius: Theme.cardRadius border.width: 0 color: Theme.alpha(Theme.fg, 0.045) Text { id: activityIcon anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: activityRow.modelData.glyph color: activityRow.modelData.tone === "danger" ? Theme.danger : Theme.warn font.family: Theme.fontMono font.pixelSize: 17 } Column { anchors.left: activityIcon.right anchors.leftMargin: 11 anchors.right: stopButton.visible ? stopButton.left : parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter spacing: 2 Text { width: parent.width text: activityRow.modelData.label color: Theme.fg elide: Text.ElideRight font.family: Theme.fontFamily font.pixelSize: Theme.fontSize font.weight: Font.DemiBold } Text { width: parent.width text: activityRow.modelData.detail color: Theme.fgDim elide: Text.ElideRight font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall } } Rectangle { id: stopButton anchors.right: parent.right anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter visible: activityRow.modelData.stoppable width: 54 height: 28 radius: Theme.pillRadius border.width: 0 color: stopMouse.containsMouse ? Theme.alpha(Theme.danger, 0.26) : Theme.alpha(Theme.danger, 0.15) Text { anchors.centerIn: parent text: "Stop" color: Theme.danger font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.weight: Font.DemiBold } MouseArea { id: stopMouse anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: Capture.stopRecording() } } } } } } NumberAnimation { id: openAnimation target: openScale property: "yScale" from: 0.96 to: 1 duration: Theme.durNormal easing.type: Easing.OutCubic } }