Files
DankMaterialShell/quickshell/Services/PrivacyService.qml
T
Zer0TraceXandclaude[bot] 87a4da9e13 feat(freebsd): Add initial native FreeBSD support (#3559)
* feat(freebsd): add comprehensive native FreeBSD support

* Update BatteryService.qml

* Add battery probe completion and fallback updates

* Add isVideoSource function and update PwObjectTracker filter

* Add isPipewireVideoSource function to AudioService

Added a function to check if a Pipewire node is a video source.

* Refactor video source check to use AudioService

* Remove unnecessary call in BatteryService.qml

* Add call to updateFreeBsdBatteryFallback on completion

* Simplify AudioService by eliminating redundant bindings

Removed unnecessary bindings in the AudioService loader to streamline the loading process.

* Improve error handling in CheckUpdates function

Handle specific exit error for package upgrade check.

* Enhance FreeBSD network detection logic

* Implement FreeBSD Wi-Fi hardware discovery in Initialize

Added FreeBSD Wi-Fi hardware discovery and updated initialization logic to handle cases where no wpa_supplicant control socket is found.

* Implement Ethernet state monitoring in StartMonitoring

Added monitoring for Ethernet state updates in StartMonitoring function.

* Add Ethernet management functions to backend

* Remove unimplemented Ethernet control methods

Removed unimplemented Ethernet control methods from WpaSupplicantBackend.

* Implement SetWiFiEnabled method for WiFi control

Add error handling and state management for WiFi control

* Update core/internal/server/network/backend_wpa_ethernet.go

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* Implement Ethernet monitoring in WpaSupplicantBackend

* Refactor startEthernetMonitor and add debounce logic

Refactor the startEthernetMonitor function to improve readability and maintainability. Introduce a debounce mechanism for processing relevant FreeBSD route events.

* Reorder import statements in backend_pkg.go

* Remove unnecessary blank line in backend_wpa_ethernet.go

* Implement FreeBSD audio polling based on UI visibility

Introduce a polling mechanism for FreeBSD audio device updates based on visibility of UI elements.

* Add readonly freeBsdPollingRequested property and optimize polling

Introduce a readonly property to determine if FreeBSD polling is requested based on various UI conditions. Update command execution to prioritize polling efficiency.

* Simplify freeBsdPollingRequested logic

Removed unnecessary conditions from freeBsdPollingRequested property.

* Implement reference counting for battery consumers

Added reference counting to manage battery information consumers and updated related functions for better battery state handling.

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-10-05 22:03:32 -04:00

183 lines
5.6 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
Singleton {
id: root
readonly property bool microphoneActive: {
if (!AudioService.pipewireReady) {
return false
}
for (let i = 0; i < AudioService.pipewireNodes.length; i++) {
const node = AudioService.pipewireNodes[i]
if (!node) {
continue
}
if (node.properties?.["media.class"] === "Stream/Input/Audio") {
if (!looksLikeSystemVirtualMic(node)) {
if (node.audio && node.audio.muted) {
return false
}
return true
}
}
}
return false
}
readonly property bool cameraActive: {
if (!AudioService.pipewireReady) {
return false
}
for (let i = 0; i < AudioService.pipewireNodes.length; i++) {
const node = AudioService.pipewireNodes[i]
if (!node || !node.ready || node.properties?.["media.role"] === "Screen") {
continue
}
if (node.properties && node.properties["media.class"] === "Stream/Input/Video") {
if (node.properties["stream.is-live"] === "true") {
return true
}
}
}
return false
}
readonly property bool screensharingActive: {
if (CompositorService.isNiri && NiriService.hasActiveCast) {
return true
}
if (!AudioService.pipewireReady) {
return false
}
for (let i = 0; i < AudioService.pipewireNodes.length; i++) {
const node = AudioService.pipewireNodes[i]
if (!node || !node.ready) {
continue
}
if (AudioService.isPipewireVideoSource(node)) {
if (looksLikeScreencast(node)) {
return true
}
}
if (node.properties && node.properties["media.class"] === "Stream/Output/Video") {
if (looksLikeScreencast(node)) {
return true
}
}
if (node.properties && node.properties["media.class"] === "Stream/Input/Audio") {
const mediaName = (node.properties["media.name"] || "").toLowerCase()
const appName = (node.properties["application.name"] || "").toLowerCase()
if (mediaName.includes("desktop") || appName.includes("screen") || appName === "obs") {
if (node.properties["stream.is-live"] === "true") {
if (node.audio && node.audio.muted) {
return false
}
return true
}
}
}
}
return false
}
readonly property bool anyPrivacyActive: microphoneActive || cameraActive || screensharingActive
function looksLikeSystemVirtualMic(node) {
if (!node) {
return false
}
const name = (node.name || "").toLowerCase()
const mediaName = (node.properties && node.properties["media.name"] || "").toLowerCase()
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase()
const combined = name + " " + mediaName + " " + appName
return /cava|monitor|system/.test(combined)
}
function looksLikeScreencast(node) {
if (!node) {
return false
}
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase()
const nodeName = (node.name || "").toLowerCase()
const mediaName = (node.properties && node.properties["media.name"] || "").toLowerCase()
const combined = appName + " " + nodeName + " " + mediaName
return /xdg-desktop-portal|xdpw|screencast|screen-cast|screen|gnome shell|kwin|obs|niri/.test(combined)
}
function screencastSourceIds() {
const ids = []
if (CompositorService.isNiri) {
for (const cast of NiriService.casts) {
if (cast && cast.is_active) {
ids.push("niri:" + cast.stream_id)
}
}
}
if (!AudioService.pipewireReady) {
return ids
}
for (let i = 0; i < AudioService.pipewireNodes.length; i++) {
const node = AudioService.pipewireNodes[i]
if (!node || !node.ready) {
continue
}
const isVideoSource = node.properties?.["media.class"] === "Video/Source"
const isVideoStream = node.properties && node.properties["media.class"] === "Stream/Output/Video"
if ((isVideoSource || isVideoStream) && looksLikeScreencast(node)) {
ids.push("pw:" + node.id)
}
}
return ids.sort()
}
function getMicrophoneStatus() {
return microphoneActive ? "active" : "inactive"
}
function getCameraStatus() {
return cameraActive ? "active" : "inactive"
}
function getScreensharingStatus() {
return screensharingActive ? "active" : "inactive"
}
function getPrivacySummary() {
const active = []
if (microphoneActive) {
active.push("microphone")
}
if (cameraActive) {
active.push("camera")
}
if (screensharingActive) {
active.push("screensharing")
}
return active.length > 0 ? `Privacy active: ${active.join(", ")}` : "No privacy concerns detected"
}
}