Add airplane mode detection, improve NetworkService & add icons to notice toasts

This commit is contained in:
notiant
2025-10-31 15:54:50 +01:00
committed by GitHub
parent b7dc1aed84
commit 38a7c606c8
21 changed files with 197 additions and 47 deletions
+8
View File
@@ -1761,6 +1761,14 @@
"enabled": "Aktiviert",
"disabled": "Deaktiviert"
},
"airplane-mode": {
"title": "Flugmodus",
"enabled": "Aktiviert",
"disabled": "Deaktiviert"
},
"internet": {
"limited": "Verbunden ohne Internet"
},
"kofi": {
"opened": "Ko-fi-Seite in Ihrem Browser geöffnet"
},
+8
View File
@@ -1721,6 +1721,14 @@
"enabled": "Enabled",
"disabled": "Disabled"
},
"airplane-mode": {
"title": "Airplane mode",
"enabled": "Enabled",
"disabled": "Disabled"
},
"internet": {
"limited": "Connected without internet"
},
"kofi": {
"opened": "Ko-fi page opened in your browser"
},
+8
View File
@@ -1721,6 +1721,14 @@
"enabled": "Activado",
"disabled": "Desactivado"
},
"airplane-mode": {
"title": "Modo avión",
"enabled": "Activado",
"disabled": "Desactivado"
},
"internet": {
"limited": "Conectado sin Internet"
},
"kofi": {
"opened": "Página de Ko-fi abierta en tu navegador"
},
+8
View File
@@ -1721,6 +1721,14 @@
"enabled": "Activé",
"disabled": "Désactivé"
},
"airplane-mode": {
"title": "Mode avion",
"enabled": "Activé",
"disabled": "Désactivé"
},
"internet": {
"limited": "Connecté sans Internet"
},
"kofi": {
"opened": "Page Ko-fi ouverte dans votre navigateur"
},
+8
View File
@@ -1721,6 +1721,14 @@
"enabled": "Ativado",
"disabled": "Desativado"
},
"airplane-mode": {
"title": "Modo avião",
"enabled": "Ativado",
"disabled": "Desativado"
},
"internet": {
"limited": "Conectado sem Internet"
},
"kofi": {
"opened": "Página do Ko-fi aberta no seu navegador"
},
+8
View File
@@ -1721,6 +1721,14 @@
"enabled": "已启用",
"disabled": "已禁用"
},
"airplane-mode": {
"title": "飞行模式",
"enabled": "已启用",
"disabled": "已禁用"
},
"internet": {
"limited": "已连接但无互联网"
},
"kofi": {
"opened": "Ko-fi页面已在您的浏览器中打开"
},
+1 -1
View File
@@ -127,7 +127,7 @@ ColumnLayout {
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["xdg-open", "https://ko-fi.com/lysec"])
ToastService.showNotice(I18n.tr("settings.about.support"), I18n.tr("toast.kofi.opened"), 3000)
ToastService.showNotice(I18n.tr("settings.about.support"), I18n.tr("toast.kofi.opened"))
}
}
}
+2 -2
View File
@@ -111,7 +111,7 @@ ColumnLayout {
if (exitCode === 0) {
Settings.data.colorSchemes.useWallpaperColors = true
AppThemeService.generate()
ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.enabled"))
ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.enabled"), "settings-color-scheme")
} else {
ToastService.showWarning(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.not-installed"))
}
@@ -248,7 +248,7 @@ ColumnLayout {
matugenCheck.running = true
} else {
Settings.data.colorSchemes.useWallpaperColors = false
ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"))
ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"), "settings-color-scheme")
if (Settings.data.colorSchemes.predefinedScheme) {
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
+2 -2
View File
@@ -38,7 +38,7 @@ ColumnLayout {
if (exitCode === 0) {
Settings.data.nightLight.enabled = true
NightLightService.apply()
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.enabled"))
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.enabled"), "nightlight-on")
} else {
Settings.data.nightLight.enabled = false
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"))
@@ -194,7 +194,7 @@ ColumnLayout {
Settings.data.nightLight.enabled = false
Settings.data.nightLight.forced = false
NightLightService.apply()
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.disabled"))
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.disabled"), "nightlight-off")
}
}
}
+12 -11
View File
@@ -9,6 +9,7 @@ Rectangle {
property string message: ""
property string description: ""
property string icon: ""
property string type: "notice"
property int duration: 3000
readonly property real initialScale: 0.7
@@ -80,16 +81,15 @@ Rectangle {
// Icon
NIcon {
id: icon
icon: {
switch (type) {
case "warning":
return "toast-warning"
case "error":
return "toast-error"
default:
return "toast-notice"
}
}
icon: if (root.icon !== "") {
return root.icon
} else if (type === "warning") {
return "toast-warning"
} else if (type === "error") {
return "toast-error"
} else {
return "toast-notice"
}
color: {
switch (type) {
case "warning":
@@ -139,13 +139,14 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
}
function show(msg, desc, msgType, msgDuration) {
function show(msg, desc, msgIcon, msgType, msgDuration) {
// Stop all timers first
hideTimer.stop()
hideAnimation.stop()
message = msg
description = desc || ""
icon = msgIcon || ""
type = msgType || "notice"
duration = msgDuration || 3000
+5 -4
View File
@@ -22,10 +22,11 @@ Item {
Connections {
target: ToastService
function onNotify(message, description, type, duration) {
function onNotify(message, description, icon, type, duration) {
root.enqueueToast({
"message": message,
"description": description,
"icon": icon,
"type": type,
"duration": duration,
"timestamp": Date.now()
@@ -122,7 +123,7 @@ Item {
onStatusChanged: {
// When loader becomes ready, show the pending toast
if (status === Loader.Ready && pendingToast !== null) {
item.showToast(pendingToast.message, pendingToast.description, pendingToast.type, pendingToast.duration)
item.showToast(pendingToast.message, pendingToast.description, pendingToast.icon, pendingToast.type, pendingToast.duration)
pendingToast = null
}
}
@@ -201,8 +202,8 @@ Item {
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: PanelWindow.ExclusionMode.Ignore
function showToast(message, description, type, duration) {
toastItem.show(message, description, type, duration)
function showToast(message, description, icon, type, duration) {
toastItem.show(message, description, icon, type, duration)
}
function hideToast() {
+1 -1
View File
@@ -119,7 +119,7 @@ Singleton {
}
ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.set-success-desc", {
"percent": BatteryService.getThresholdValue(BatteryService.chargingMode)
}))
}), "battery")
Settings.data.battery.chargingMode = BatteryService.chargingMode
} else if (exitCode === 2) {
ToastService.showWarning(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.initial-setup"))
+48 -6
View File
@@ -3,14 +3,19 @@ pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Bluetooth
import Quickshell.Io
import qs.Commons
import qs.Services
Singleton {
id: root
property bool airplaneModeToggled: false
property bool lastBluetoothBlocked: false
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
readonly property bool available: (adapter !== null)
readonly property bool enabled: adapter?.enabled ?? false
readonly property bool blocked: (adapter?.state === BluetoothAdapterState.Blocked)
readonly property bool discovering: (adapter && adapter.discovering) ?? false
readonly property var devices: adapter ? adapter.devices : null
readonly property var pairedDevices: {
@@ -50,18 +55,25 @@ Singleton {
Connections {
target: adapter
function onEnabledChanged() {
function onStateChanged() {
if (!adapter) {
Logger.w("Bluetooth", "onEnabledChanged", "No adapter available")
Logger.w("Bluetooth", "onStateChanged", "No adapter available")
return
}
if (adapter.state === BluetoothAdapterState.Enabling || adapter.state === BluetoothAdapterState.Disabling) {
return
}
Logger.d("Bluetooth", "onEnableChanged", adapter.enabled)
if (adapter.enabled) {
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.enabled"))
Logger.d("Bluetooth", "onStateChanged", adapter.state)
const bluetoothBlockedToggled = (root.blocked !== lastBluetoothBlocked)
root.lastBluetoothBlocked = root.blocked
if (bluetoothBlockedToggled) {
checkWifiBlocked.running = true
} else if (adapter.enabled) {
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.enabled"), "bluetooth")
discoveryTimer.running = true
} else {
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.disabled"))
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.disabled"), "bluetooth-off")
}
}
}
@@ -238,4 +250,34 @@ Singleton {
Logger.i("Bluetooth", "SetBluetoothEnabled", state)
adapter.enabled = state
}
Process {
id: checkWifiBlocked
running: false
command: ["rfkill", "list", "wifi"]
stdout: StdioCollector {
onStreamFinished: {
const wifiBlocked = text && text.trim().includes("Soft blocked: yes")
Logger.d("Network", "Wi-Fi adapter was detected as blocked:", blocked)
// Check if airplane mode has been toggled
if (wifiBlocked && wifiBlocked === root.blocked) {
root.airplaneModeToggled = true
NetworkService.setWifiEnabled(false)
ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("toast.airplane-mode.enabled"), "plane")
} else if (!wifiBlocked && wifiBlocked === root.blocked) {
root.airplaneModeToggled = true
NetworkService.setWifiEnabled(true)
ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("toast.airplane-mode.disabled"), "plane")
} else if (adapter.enabled) {
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.enabled"), "bluetooth")
discoveryTimer.running = true
} else {
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.disabled"), "bluetooth-off")
}
root.airplaneModeToggled = false
}
}
}
}
+2 -2
View File
@@ -27,7 +27,7 @@ Singleton {
const enabled = !!Settings.data.colorSchemes.darkMode
const label = enabled ? "Dark mode" : "Light mode"
const description = enabled ? "Enabled" : "Enabled"
ToastService.showNotice(label, description)
ToastService.showNotice(label, description, "dark-mode")
}
}
@@ -109,7 +109,7 @@ Singleton {
if (schemeExists) {
Settings.data.colorSchemes.predefinedScheme = basename
applyScheme(schemeName)
ToastService.showNotice("Color Scheme", `Set to ${basename}`)
ToastService.showNotice("Color Scheme", `Set to ${basename}`, "settings-color-scheme")
} else {
Logger.e("ColorScheme", "Scheme not found:", schemeName)
ToastService.showError("Color Scheme", `Scheme '${basename}' not found!`)
+2 -2
View File
@@ -156,12 +156,12 @@ Singleton {
function manualToggle() {
if (activeInhibitors.includes("manual")) {
removeInhibitor("manual")
ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("toast.keep-awake.disabled"))
ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("toast.keep-awake.disabled"), "keep-awake-on")
Logger.i("IdleInhibitor", "Manual inhibition disabled")
return false
} else {
addInhibitor("manual", "Manually activated by user")
ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("toast.keep-awake.enabled"))
ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("toast.keep-awake.enabled"), "keep-awake-off")
Logger.i("IdleInhibitor", "Manual inhibition enabled (will reset on next session)")
return true
}
+62 -4
View File
@@ -4,6 +4,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services
Singleton {
id: root
@@ -17,6 +18,7 @@ Singleton {
property bool ethernetConnected: false
property string disconnectingFrom: ""
property string forgettingNetwork: ""
property string internetConnectivity: "unknown"
property bool ignoreScanResults: false
property bool scanPending: false
@@ -48,9 +50,18 @@ Singleton {
target: Settings.data.network
function onWifiEnabledChanged() {
if (Settings.data.network.wifiEnabled) {
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.enabled"))
if (!BluetoothService.airplaneModeToggled)
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.enabled"), "wifi")
// Perform a scan to update the UI
delayedScanTimer.interval = 3000
delayedScanTimer.restart()
} else {
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.disabled"))
if (!BluetoothService.airplaneModeToggled)
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.disabled"), "wifi-off")
// Clear networks so the widget icon changes
root.networks = ({})
}
}
}
@@ -89,6 +100,16 @@ Singleton {
onTriggered: ethernetStateProcess.running = true
}
// Internet connectivity check timer
// Always running every 10s
Timer {
id: connectivityCheckTimer
interval: 10000
running: true
repeat: true
onTriggered: connectivityCheckProcess.running = true
}
// Core functions
function syncWifiState() {
wifiStateProcess.running = true
@@ -202,6 +223,8 @@ Singleton {
// Helper functions
function signalIcon(signal) {
if (root.internetConnectivity === "limited" || root.internetConnectivity === "portal")
return "world-off"
if (signal >= 80)
return "wifi"
if (signal >= 50)
@@ -276,6 +299,38 @@ Singleton {
}
}
// Process to check the internet connectivity
Process {
id: connectivityCheckProcess
running: false
command: ["nmcli", "networking", "connectivity"]
stdout: StdioCollector {
onStreamFinished: {
const result = text.trim()
if (result && result !== root.internetConnectivity) {
root.internetConnectivity = result
Logger.i("Network", "Internet connectivity:", result)
if (result === "limited" || result === "portal") {
ToastService.showWarning(I18n.tr("wifi.panel.title"), "toast.internet.limited")
}
else {
scan()
}
}
}
}
stderr: StdioCollector {
onStreamFinished: {
if (text.trim()) {
Logger.w("Network", "Connectivity check error: " + text)
}
}
}
}
// Helper process to get existing profiles
Process {
id: profileCheckProcess
@@ -462,6 +517,9 @@ Singleton {
return cmd
}
}
environment: {
"LC_ALL": "C"
}
stdout: StdioCollector {
onStreamFinished: {
@@ -494,7 +552,7 @@ Singleton {
Logger.i("Network", `Connected to network: '${connectProcess.ssid}'`)
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.connected", {
"ssid": connectProcess.ssid
}))
}), "wifi")
// Still do a scan to get accurate signal and security info
delayedScanTimer.interval = 5000
@@ -537,7 +595,7 @@ Singleton {
Logger.i("Network", `Disconnected from network: '${disconnectProcess.ssid}'`)
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.disconnected", {
"ssid": disconnectProcess.ssid
}))
}), "wifi-off")
// Immediately update UI on successful disconnect
root.updateNetworkStatus(disconnectProcess.ssid, false)
+2 -2
View File
@@ -63,12 +63,12 @@ Singleton {
apply()
// Toast: night light toggled
const enabled = !!Settings.data.nightLight.enabled
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), enabled ? I18n.tr("toast.night-light.enabled") : I18n.tr("toast.night-light.disabled"))
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), enabled ? I18n.tr("toast.night-light.enabled") : I18n.tr("toast.night-light.disabled"), enabled ? "nightlight-on" : "nightlight-off")
}
function onForcedChanged() {
apply()
if (Settings.data.nightLight.enabled) {
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), Settings.data.nightLight.forced ? I18n.tr("toast.night-light.forced") : I18n.tr("toast.night-light.normal"))
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), Settings.data.nightLight.forced ? I18n.tr("toast.night-light.forced") : I18n.tr("toast.night-light.normal"), forced ? "nightlight-forced" : "nightlight-on")
}
}
function onNightTempChanged() {
+1 -1
View File
@@ -543,7 +543,7 @@ Singleton {
target: Settings.data.notifications
function onDoNotDisturbChanged() {
const enabled = Settings.data.notifications.doNotDisturb
ToastService.showNotice(enabled ? I18n.tr("toast.do-not-disturb.enabled") : I18n.tr("toast.do-not-disturb.disabled"), enabled ? I18n.tr("toast.do-not-disturb.enabled-desc") : I18n.tr("toast.do-not-disturb.disabled-desc"))
ToastService.showNotice(enabled ? I18n.tr("toast.do-not-disturb.enabled") : I18n.tr("toast.do-not-disturb.disabled"), enabled ? I18n.tr("toast.do-not-disturb.enabled-desc") : I18n.tr("toast.do-not-disturb.disabled-desc"), enabled ? "bell-off" : "bell")
}
}
}
+1 -1
View File
@@ -90,7 +90,7 @@ Singleton {
if (profileName !== "Unknown") {
ToastService.showNotice(I18n.tr("toast.power-profile.changed"), I18n.tr("toast.power-profile.profile-name", {
"profile": profileName
}))
}), profileName.toLowerCase().replace(" ", ""))
}
}
}
+2 -2
View File
@@ -85,7 +85,7 @@ Singleton {
return
}
ToastService.showNotice(I18n.tr("toast.recording.stopping"), outputPath, 2000)
ToastService.showNotice(I18n.tr("toast.recording.stopping"), outputPath, 2000, "settings-screen-recorder")
Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder' || pkill -SIGINT -f 'com.dec05eba.gpu_screen_recorder'"])
@@ -131,7 +131,7 @@ Singleton {
monitorTimer.running = false
// Consider successful save if exitCode == 0
if (exitCode === 0) {
ToastService.showNotice(I18n.tr("toast.recording.saved"), outputPath, 5000)
ToastService.showNotice(I18n.tr("toast.recording.saved"), outputPath, 5000, "settings-screen-recorder")
} else {
const err2 = String(recorderProcess.stderr.text || "").trim()
if (err2.length > 0)
+6 -6
View File
@@ -7,18 +7,18 @@ Singleton {
id: root
// Simple signal-based notification system
signal notify(string message, string description, string type, int duration)
signal notify(string message, string description, string icon, string type, int duration)
// Convenience methods
function showNotice(message, description = "", duration = 3000) {
notify(message, description, "notice", duration)
function showNotice(message, description = "", icon = "", duration = 3000) {
notify(message, description, icon, "notice", duration)
}
function showWarning(message, description = "", duration = 4000) {
notify(message, description, "warning", duration)
notify(message, description, "", "warning", duration)
}
function showError(message, description = "", duration = 5000) {
notify(message, description, "error", duration)
notify(message, description, "", "error", duration)
}
}
}