219 lines
7.6 KiB
QML
219 lines
7.6 KiB
QML
// 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
|
|
}
|
|
}
|