diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index f4ab4cc9..b821323c 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -100,6 +100,11 @@ Singleton { property bool controlCenterShowNetworkIcon: true property bool controlCenterShowBluetoothIcon: true property bool controlCenterShowAudioIcon: true + property bool showPrivacyButton: true + property bool privacyShowMicIcon: false + property bool privacyShowCameraIcon: false + property bool privacyShowScreenShareIcon: false + property var controlCenterWidgets: [{ "id": "volumeSlider", "enabled": true, diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index ecbc2c06..a3913244 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -52,6 +52,12 @@ var SPEC = { controlCenterShowNetworkIcon: { def: true }, controlCenterShowBluetoothIcon: { def: true }, controlCenterShowAudioIcon: { def: true }, + + showPrivacyButton: { def: true }, + privacyShowMicIcon: { def: false }, + privacyShowCameraIcon: { def: false }, + privacyShowScreenShareIcon: { def: false }, + controlCenterWidgets: { def: [ { id: "volumeSlider", enabled: true, width: 50 }, { id: "brightnessSlider", enabled: true, width: 50 }, diff --git a/quickshell/Modules/DankBar/Widgets/PrivacyIndicator.qml b/quickshell/Modules/DankBar/Widgets/PrivacyIndicator.qml index e5b466a4..003d346f 100644 --- a/quickshell/Modules/DankBar/Widgets/PrivacyIndicator.qml +++ b/quickshell/Modules/DankBar/Widgets/PrivacyIndicator.qml @@ -14,9 +14,14 @@ Item { property var parentScreen: null property real widgetThickness: 30 property real barThickness: 48 + + property bool showMicIcon: SettingsData.privacyShowMicIcon + property bool showCameraIcon: SettingsData.privacyShowCameraIcon + property bool showScreenSharingIcon: SettingsData.privacyShowScreenShareIcon + readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS - readonly property bool hasActivePrivacy: PrivacyService.anyPrivacyActive - readonly property int activeCount: PrivacyService.microphoneActive + PrivacyService.cameraActive + PrivacyService.screensharingActive + readonly property bool hasActivePrivacy: showMicIcon || showCameraIcon || showScreenSharingIcon || PrivacyService.anyPrivacyActive + readonly property int activeCount: ( showMicIcon ? 1 : PrivacyService.microphoneActive) + (showCameraIcon ? 1 : PrivacyService.cameraActive) + (showScreenSharingIcon ? 1 : PrivacyService.screensharingActive) readonly property real contentWidth: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0 readonly property real contentHeight: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0 readonly property real visualWidth: isVertical ? widgetThickness : (hasActivePrivacy ? (contentWidth + horizontalPadding * 2) : 0) @@ -117,7 +122,7 @@ Item { Item { width: 18 height: 18 - visible: PrivacyService.microphoneActive + visible: showMicIcon || PrivacyService.microphoneActive anchors.verticalCenter: parent.verticalCenter DankIcon { @@ -128,7 +133,7 @@ Item { return "mic" } size: Theme.iconSizeSmall - color: Theme.error + color: PrivacyService.microphoneActive ? Theme.error : Theme.surfaceText filled: true anchors.centerIn: parent } @@ -137,13 +142,13 @@ Item { Item { width: 18 height: 18 - visible: PrivacyService.cameraActive + visible: showCameraIcon || PrivacyService.cameraActive anchors.verticalCenter: parent.verticalCenter DankIcon { name: "camera_video" size: Theme.iconSizeSmall - color: Theme.surfaceText + color: PrivacyService.cameraActive ? Theme.error : Theme.surfaceText filled: true anchors.centerIn: parent } @@ -157,19 +162,20 @@ Item { anchors.top: parent.top anchors.rightMargin: -2 anchors.topMargin: -1 + visible: PrivacyService.cameraActive } } Item { width: 18 height: 18 - visible: PrivacyService.screensharingActive + visible: showScreenSharingIcon || PrivacyService.screensharingActive anchors.verticalCenter: parent.verticalCenter DankIcon { name: "screen_share" size: Theme.iconSizeSmall - color: Theme.warning + color: PrivacyService.screensharingActive ? Theme.warning : Theme.surfaceText filled: true anchors.centerIn: parent } diff --git a/quickshell/Modules/Settings/DankBarTab.qml b/quickshell/Modules/Settings/DankBarTab.qml index 1fcbd188..8f8a1523 100644 --- a/quickshell/Modules/Settings/DankBarTab.qml +++ b/quickshell/Modules/Settings/DankBarTab.qml @@ -526,6 +526,16 @@ Item { } } + function handlePrivacySettingChanged(sectionId, widgetIndex, settingName, value) { + if (settingName === "showMicIcon") { + SettingsData.set("privacyShowMicIcon", value) + } else if (settingName === "showCameraIcon") { + SettingsData.set("privacyShowCameraIcon", value) + } else if (settingName === "showScreenSharingIcon") { + SettingsData.set("privacyShowScreenShareIcon", value) + } + } + function handleMinimumWidthChanged(sectionId, widgetIndex, enabled) { var widgets = [] if (sectionId === "left") @@ -2028,6 +2038,9 @@ Item { onControlCenterSettingChanged: (sectionId, widgetIndex, settingName, value) => { handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) } + onPrivacySettingChanged: (sectionId, widgetIndex, settingName, value) => { + handlePrivacySettingChanged(sectionId, widgetIndex, settingName, value) + } onGpuSelectionChanged: (sectionId, widgetIndex, selectedIndex) => { dankBarTab.handleGpuSelectionChanged( sectionId, widgetIndex, @@ -2111,6 +2124,9 @@ Item { onControlCenterSettingChanged: (sectionId, widgetIndex, settingName, value) => { handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) } + onPrivacySettingChanged: (sectionId, widgetIndex, settingName, value) => { + handlePrivacySettingChanged(sectionId, widgetIndex, settingName, value) + } onGpuSelectionChanged: (sectionId, widgetIndex, selectedIndex) => { dankBarTab.handleGpuSelectionChanged( sectionId, widgetIndex, @@ -2194,6 +2210,9 @@ Item { onControlCenterSettingChanged: (sectionId, widgetIndex, settingName, value) => { handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) } + onPrivacySettingChanged: (sectionId, widgetIndex, settingName, value) => { + handlePrivacySettingChanged(sectionId, widgetIndex, settingName, value) + } onGpuSelectionChanged: (sectionId, widgetIndex, selectedIndex) => { dankBarTab.handleGpuSelectionChanged( sectionId, widgetIndex, diff --git a/quickshell/Modules/Settings/WidgetsTabSection.qml b/quickshell/Modules/Settings/WidgetsTabSection.qml index fe153e0c..cf737509 100644 --- a/quickshell/Modules/Settings/WidgetsTabSection.qml +++ b/quickshell/Modules/Settings/WidgetsTabSection.qml @@ -22,6 +22,7 @@ Column { signal gpuSelectionChanged(string sectionId, int widgetIndex, int selectedIndex) signal diskMountSelectionChanged(string sectionId, int widgetIndex, string mountPath) signal controlCenterSettingChanged(string sectionId, int widgetIndex, string settingName, bool value) + signal privacySettingChanged(string sectionId, int widgetIndex, string settingName, bool value) signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled) signal showSwapChanged(string sectionId, int widgetIndex, bool enabled) @@ -580,6 +581,25 @@ Column { } } + DankActionButton { + visible: modelData.id === "privacyIndicator" + buttonSize: 32 + iconName: "more_vert" + iconSize: 18 + iconColor: Theme.outline + onClicked: { + console.log("Privacy three-dot button clicked for widget:", modelData.id) + privacyContextMenu.widgetData = modelData + privacyContextMenu.sectionId = root.sectionId + privacyContextMenu.widgetIndex = index + // Position relative to the action buttons row, not the specific button + var parentPos = parent.mapToItem(root, 0, 0) + privacyContextMenu.x = parentPos.x - 210 // Position to the left with margin + privacyContextMenu.y = parentPos.y - 10 // Slightly above + privacyContextMenu.open() + } + } + DankActionButton { id: visibilityButton visible: modelData.id !== "spacer" @@ -962,6 +982,227 @@ Column { } } + Popup { + id: privacyContextMenu + + property var widgetData: null + property string sectionId: "" + property int widgetIndex: -1 + + + width: 200 + height: 160 + padding: 0 + modal: true + focus: true + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + onOpened: { + console.log("Privacy context menu opened") + } + + onClosed: { + console.log("Privacy Center context menu closed") + } + + background: Rectangle { + color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) + radius: Theme.cornerRadius + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) + border.width: 0 + } + + contentItem: Item { + + Column { + id: menuPrivacyColumn + anchors.fill: parent + anchors.margins: Theme.spacingS + spacing: 2 + + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + StyledText { + text: I18n.tr("Always on icons") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Medium + anchors.verticalCenter: parent.verticalCenter + } + } + } + + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "mic" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Microphone") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Normal + anchors.verticalCenter: parent.verticalCenter + } + } + + DankToggle { + id: micToggle + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 20 + checked: SettingsData.privacyShowMicIcon + onToggled: toggled => { + root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showMicIcon", toggled) + } + } + + MouseArea { + id: micToggleArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: { + micToggle.checked = !micToggle.checked + root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showMicIcon", micToggle.checked) + } + } + } + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "camera_video" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Camera") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Normal + anchors.verticalCenter: parent.verticalCenter + } + } + + DankToggle { + id: cameraToggle + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 20 + checked: SettingsData.privacyShowCameraIcon + onToggled: toggled => { + root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showCameraIcon", toggled) + } + } + + MouseArea { + id: cameraToggleArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: { + cameraToggle.checked = !cameraToggle.checked + root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showCameraIcon", cameraToggle.checked) + } + } + } + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "screen_share" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Screen sharing") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Normal + anchors.verticalCenter: parent.verticalCenter + } + } + + DankToggle { + id: screenshareToggle + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 20 + checked: SettingsData.privacyShowScreenShareIcon + onToggled: toggled => { + root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showScreenSharingIcon", toggled) + } + } + + MouseArea { + id: screenshareToggleArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: { + screenshareToggle.checked = !screenshareToggle.checked + root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showScreenSharingIcon", screenshareToggle.checked) + } + } + } + } + + } + } + Loader { id: smallTooltipLoader active: false