diff --git a/.github/workflows/poeditor-export.yml b/.github/workflows/poeditor-export.yml index 12314652..20f06295 100644 --- a/.github/workflows/poeditor-export.yml +++ b/.github/workflows/poeditor-export.yml @@ -17,91 +17,9 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - name: Install jq run: sudo apt-get update && sudo apt-get install -y jq - - name: Extract source strings from codebase - env: - API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }} - PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }} - run: | - set -euo pipefail - - echo "::group::Extracting strings from QML files" - python3 translations/extract_translations.py - echo "::endgroup::" - - echo "::group::Checking for changes in en.json" - if [[ -f "translations/en.json" ]]; then - jq -S . "translations/en.json" > /tmp/en_new.json - if [[ -f "translations/en.json.orig" ]]; then - jq -S . "translations/en.json.orig" > /tmp/en_old.json - else - git show HEAD:translations/en.json > /tmp/en_old.json 2>/dev/null || echo "[]" > /tmp/en_old.json - jq -S . /tmp/en_old.json > /tmp/en_old.json.tmp && mv /tmp/en_old.json.tmp /tmp/en_old.json - fi - - if diff -q /tmp/en_new.json /tmp/en_old.json >/dev/null 2>&1; then - echo "No changes in source strings" - echo "source_changed=false" >> "$GITHUB_OUTPUT" - else - echo "Detected changes in source strings" - echo "source_changed=true" >> "$GITHUB_OUTPUT" - - echo "::group::Uploading source strings to POEditor" - RESP=$(curl -sS -X POST https://api.poeditor.com/v2/projects/upload \ - -F api_token="$API_TOKEN" \ - -F id="$PROJECT_ID" \ - -F updating="terms" \ - -F file=@"translations/en.json") - - STATUS=$(echo "$RESP" | jq -r '.response.status') - if [[ "$STATUS" != "success" ]]; then - echo "::warning::POEditor upload failed: $RESP" - else - TERMS_ADDED=$(echo "$RESP" | jq -r '.result.terms.added // 0') - TERMS_UPDATED=$(echo "$RESP" | jq -r '.result.terms.updated // 0') - TERMS_DELETED=$(echo "$RESP" | jq -r '.result.terms.deleted // 0') - echo "Terms added: $TERMS_ADDED, updated: $TERMS_UPDATED, deleted: $TERMS_DELETED" - fi - echo "::endgroup::" - fi - else - echo "::warning::translations/en.json not found" - echo "source_changed=false" >> "$GITHUB_OUTPUT" - fi - echo "::endgroup::" - id: extract - - - name: Commit and push source strings - if: steps.extract.outputs.source_changed == 'true' - run: | - set -euo pipefail - - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - git add translations/en.json translations/template.json - git commit -m "i18n: update source strings from codebase" - - for attempt in 1 2 3; do - if git push; then - echo "Successfully pushed source string updates" - exit 0 - fi - echo "Push attempt $attempt failed, pulling and retrying..." - git pull --rebase - sleep $((attempt*2)) - done - - echo "Failed to push after retries" >&2 - exit 1 - - name: Export and update translations from POEditor env: API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }} diff --git a/Common/SettingsData.qml b/Common/SettingsData.qml index 0b89a6a4..c2adee98 100644 --- a/Common/SettingsData.qml +++ b/Common/SettingsData.qml @@ -207,9 +207,6 @@ Singleton { property bool dankBarAutoHide: false property bool dankBarOpenOnOverview: false property bool dankBarVisible: true - property int overviewRows: 2 - property int overviewColumns: 5 - property real overviewScale: 0.16 property real dankBarSpacing: 4 property real dankBarBottomGap: 0 property real dankBarInnerPadding: 4 diff --git a/Common/Theme.qml b/Common/Theme.qml index 2ef74347..69cfa163 100644 --- a/Common/Theme.qml +++ b/Common/Theme.qml @@ -441,10 +441,7 @@ Singleton { if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) SessionData.setLightMode(light) if (!isGreeterMode) { - // Skip with matugen becuase, our script runner will do it. - if (!matugenAvailable) { - PortalService.setLightMode(light) - } + PortalService.setLightMode(light) generateSystemThemesFromCurrentTheme() } } @@ -722,16 +719,15 @@ Singleton { Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`]) workerRunning = true - const syncModeWithPortal = (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) ? "true" : "false" if (rawWallpaperPath.startsWith("we:")) { console.log("Theme: Starting matugen worker (WE wallpaper)") systemThemeGenerator.command = [ "sh", "-c", - `sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' '${syncModeWithPortal}' --run` + `sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' --run` ] } else { console.log("Theme: Starting matugen worker") - systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, syncModeWithPortal, "--run"] + systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, "--run"] } systemThemeGenerator.running = true } diff --git a/DMSGreeter.qml b/DMSGreeter.qml index 0a1fc86c..0f833539 100644 --- a/DMSGreeter.qml +++ b/DMSGreeter.qml @@ -1,11 +1,29 @@ import QtQuick import Quickshell +import Quickshell.Wayland import Quickshell.Services.Greetd import qs.Common import qs.Modules.Greetd -Scope { +Item { id: root - GreeterSurface {} + WlSessionLock { + id: sessionLock + locked: false + + Component.onCompleted: { + Qt.callLater(() => { locked = true }) + } + + onLockedChanged: { + if (!locked) { + console.log("Greetd session unlocked, exiting") + } + } + + GreeterSurface { + lock: sessionLock + } + } } diff --git a/DMSShell.qml b/DMSShell.qml index 7f9a56b3..12dbb982 100644 --- a/DMSShell.qml +++ b/DMSShell.qml @@ -22,7 +22,6 @@ import qs.Modules.ProcessList import qs.Modules.Settings import qs.Modules.DankBar import qs.Modules.DankBar.Popouts -import qs.Modules.HyprWorkspaces import qs.Modules.Plugins import qs.Services @@ -64,11 +63,8 @@ Item { property var currentPosition: SettingsData.dankBarPosition property bool initialized: false - property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader sourceComponent: DankBar { - hyprlandOverviewLoader: dankBarLoader.hyprlandOverviewLoaderRef - onColorPickerRequested: { if (colorPickerModal.shouldBeVisible) { colorPickerModal.close() @@ -197,19 +193,17 @@ Item { } } - WifiPasswordModal { - id: wifiPasswordModal + LazyLoader { + id: wifiPasswordModalLoader - Component.onCompleted: { - PopoutService.wifiPasswordModal = wifiPasswordModal - } - } + active: false - Connections { - target: NetworkService + WifiPasswordModal { + id: wifiPasswordModal - function onCredentialsNeeded(token, ssid, setting, fields, hints, reason) { - wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason) + Component.onCompleted: { + PopoutService.wifiPasswordModal = wifiPasswordModal + } } } @@ -502,7 +496,6 @@ Item { notepadSlideoutVariants: notepadSlideoutVariants hyprKeybindsModalLoader: hyprKeybindsModalLoader dankBarLoader: dankBarLoader - hyprlandOverviewLoader: hyprlandOverviewLoader } Variants { @@ -545,12 +538,4 @@ Item { modelData: item } } - - LazyLoader { - id: hyprlandOverviewLoader - active: CompositorService.isHyprland - component: HyprlandOverview { - id: hyprlandOverview - } - } } diff --git a/DMSShellIPC.qml b/DMSShellIPC.qml index abc84747..16cff3de 100644 --- a/DMSShellIPC.qml +++ b/DMSShellIPC.qml @@ -15,7 +15,6 @@ Item { required property var notepadSlideoutVariants required property var hyprKeybindsModalLoader required property var dankBarLoader - required property var hyprlandOverviewLoader IpcHandler { function open() { @@ -348,30 +347,6 @@ Item { return "HYPR_KEYBINDS_TOGGLE_FAILED" } - function toggleOverview(): string { - if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) { - return "HYPR_NOT_AVAILABLE" - } - root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen - return root.hyprlandOverviewLoader.item.overviewOpen ? "OVERVIEW_OPEN_SUCCESS" : "OVERVIEW_CLOSE_SUCCESS" - } - - function closeOverview(): string { - if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) { - return "HYPR_NOT_AVAILABLE" - } - root.hyprlandOverviewLoader.item.overviewOpen = false - return "OVERVIEW_CLOSE_SUCCESS" - } - - function openOverview(): string { - if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) { - return "HYPR_NOT_AVAILABLE" - } - root.hyprlandOverviewLoader.item.overviewOpen = true - return "OVERVIEW_OPEN_SUCCESS" - } - target: "hypr" } } diff --git a/Modals/Spotlight/SpotlightResults.qml b/Modals/Spotlight/SpotlightResults.qml index a0404e86..449e2731 100644 --- a/Modals/Spotlight/SpotlightResults.qml +++ b/Modals/Spotlight/SpotlightResults.qml @@ -7,10 +7,6 @@ import qs.Widgets Rectangle { id: resultsContainer - // DEVELOPER NOTE: This component renders the Spotlight launcher (accessed via Mod+Space). - // Changes to launcher behavior, especially item rendering, filtering, or model structure, - // likely require corresponding updates in Modules/AppDrawer/AppLauncher.qml and vice versa. - property var appLauncher: null property var contextMenu: null @@ -94,32 +90,19 @@ Rectangle { width: resultsList.iconSize height: resultsList.iconSize anchors.verticalCenter: parent.verticalCenter - visible: model.icon !== undefined && model.icon !== "" - - property string iconValue: model.icon || "" - property bool isMaterial: iconValue.indexOf("material:") === 0 - property string materialName: isMaterial ? iconValue.substring(9) : "" - - DankIcon { - anchors.centerIn: parent - name: parent.materialName - size: resultsList.iconSize - color: Theme.surfaceText - visible: parent.isMaterial - } IconImage { id: listIconImg anchors.fill: parent - source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) + source: Quickshell.iconPath(model.icon, true) asynchronous: true - visible: !parent.isMaterial && status === Image.Ready + visible: status === Image.Ready } Rectangle { anchors.fill: parent - visible: !parent.isMaterial && !listIconImg.visible + visible: !listIconImg.visible color: Theme.surfaceLight radius: Theme.cornerRadius border.width: 1 @@ -137,7 +120,7 @@ Rectangle { Column { anchors.verticalCenter: parent.verticalCenter - width: (model.icon !== undefined && model.icon !== "") ? (parent.width - resultsList.iconSize - Theme.spacingL) : parent.width + width: parent.width - resultsList.iconSize - Theme.spacingL spacing: Theme.spacingXS StyledText { @@ -272,33 +255,20 @@ Rectangle { width: iconSize height: iconSize anchors.horizontalCenter: parent.horizontalCenter - visible: model.icon !== undefined && model.icon !== "" - - property string iconValue: model.icon || "" - property bool isMaterial: iconValue.indexOf("material:") === 0 - property string materialName: isMaterial ? iconValue.substring(9) : "" - - DankIcon { - anchors.centerIn: parent - name: parent.materialName - size: parent.iconSize - color: Theme.surfaceText - visible: parent.isMaterial - } IconImage { id: gridIconImg anchors.fill: parent - source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) + source: Quickshell.iconPath(model.icon, true) smooth: true asynchronous: true - visible: !parent.isMaterial && status === Image.Ready + visible: status === Image.Ready } Rectangle { anchors.fill: parent - visible: !parent.isMaterial && !gridIconImg.visible + visible: !gridIconImg.visible color: Theme.surfaceLight radius: Theme.cornerRadius border.width: 1 diff --git a/Modals/WifiPasswordModal.qml b/Modals/WifiPasswordModal.qml index dcadbea1..4516fe3e 100644 --- a/Modals/WifiPasswordModal.qml +++ b/Modals/WifiPasswordModal.qml @@ -15,23 +15,12 @@ DankModal { property string wifiAnonymousIdentityInput: "" property string wifiDomainInput: "" - property bool isPromptMode: false - property string promptToken: "" - property string promptReason: "" - property var promptFields: [] - property string promptSetting: "" - function show(ssid) { wifiPasswordSSID = ssid wifiPasswordInput = "" wifiUsernameInput = "" wifiAnonymousIdentityInput = "" wifiDomainInput = "" - isPromptMode = false - promptToken = "" - promptReason = "" - promptFields = [] - promptSetting = "" const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid) requiresEnterprise = network?.enterprise || false @@ -48,41 +37,6 @@ DankModal { }) } - function showFromPrompt(token, ssid, setting, fields, hints, reason) { - wifiPasswordSSID = ssid - isPromptMode = true - promptToken = token - promptReason = reason - promptFields = fields || [] - promptSetting = setting || "802-11-wireless-security" - - requiresEnterprise = setting === "802-1x" - - if (reason === "wrong-password") { - wifiPasswordInput = "" - wifiUsernameInput = "" - } else { - wifiPasswordInput = "" - wifiUsernameInput = "" - wifiAnonymousIdentityInput = "" - wifiDomainInput = "" - } - - open() - Qt.callLater(() => { - if (contentLoader.item) { - if (reason === "wrong-password" && contentLoader.item.passwordInput) { - contentLoader.item.passwordInput.text = "" - contentLoader.item.passwordInput.forceActiveFocus() - } else if (requiresEnterprise && contentLoader.item.usernameInput) { - contentLoader.item.usernameInput.forceActiveFocus() - } else if (contentLoader.item.passwordInput) { - contentLoader.item.passwordInput.forceActiveFocus() - } - } - }) - } - shouldBeVisible: false width: 420 height: requiresEnterprise ? 430 : 230 @@ -106,9 +60,6 @@ DankModal { }) } onBackgroundClicked: () => { - if (isPromptMode) { - NetworkService.cancelCredentials(promptToken) - } close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -139,9 +90,6 @@ DankModal { anchors.fill: parent focus: true Keys.onEscapePressed: event => { - if (isPromptMode) { - NetworkService.cancelCredentials(promptToken) - } close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -169,28 +117,12 @@ DankModal { font.weight: Font.Medium } - Column { + StyledText { + text: requiresEnterprise ? I18n.tr("Enter credentials for ") + wifiPasswordSSID : I18n.tr("Enter password for ") + wifiPasswordSSID + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceTextMedium width: parent.width - spacing: Theme.spacingXS - - StyledText { - text: { - const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for ") - return prefix + wifiPasswordSSID - } - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceTextMedium - width: parent.width - elide: Text.ElideRight - } - - StyledText { - visible: isPromptMode && promptReason === "wrong-password" - text: I18n.tr("Incorrect password") - font.pixelSize: Theme.fontSizeSmall - color: Theme.error - width: parent.width - } + elide: Text.ElideRight } } @@ -199,9 +131,6 @@ DankModal { iconSize: Theme.iconSize - 4 iconColor: Theme.surfaceText onClicked: () => { - if (isPromptMode) { - NetworkService.cancelCredentials(promptToken) - } close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -279,26 +208,14 @@ DankModal { wifiPasswordInput = text } onAccepted: () => { - if (isPromptMode) { - const secrets = {} - if (promptSetting === "802-11-wireless-security") { - secrets["psk"] = passwordInput.text - } else if (promptSetting === "802-1x") { - if (usernameInput.text) secrets["identity"] = usernameInput.text - if (passwordInput.text) secrets["password"] = passwordInput.text - if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput - } - NetworkService.submitCredentials(promptToken, secrets, true) - } else { - const username = requiresEnterprise ? usernameInput.text : "" - NetworkService.connectToWifi( - wifiPasswordSSID, - passwordInput.text, - username, - wifiAnonymousIdentityInput, - wifiDomainInput - ) - } + const username = requiresEnterprise ? usernameInput.text : "" + NetworkService.connectToWifi( + wifiPasswordSSID, + passwordInput.text, + username, + wifiAnonymousIdentityInput, + wifiDomainInput + ) close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -478,9 +395,6 @@ DankModal { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: () => { - if (isPromptMode) { - NetworkService.cancelCredentials(promptToken) - } close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -516,26 +430,14 @@ DankModal { cursorShape: Qt.PointingHandCursor enabled: parent.enabled onClicked: () => { - if (isPromptMode) { - const secrets = {} - if (promptSetting === "802-11-wireless-security") { - secrets["psk"] = passwordInput.text - } else if (promptSetting === "802-1x") { - if (usernameInput.text) secrets["identity"] = usernameInput.text - if (passwordInput.text) secrets["password"] = passwordInput.text - if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput - } - NetworkService.submitCredentials(promptToken, secrets, true) - } else { - const username = requiresEnterprise ? usernameInput.text : "" - NetworkService.connectToWifi( - wifiPasswordSSID, - passwordInput.text, - username, - wifiAnonymousIdentityInput, - wifiDomainInput - ) - } + const username = requiresEnterprise ? usernameInput.text : "" + NetworkService.connectToWifi( + wifiPasswordSSID, + passwordInput.text, + username, + wifiAnonymousIdentityInput, + wifiDomainInput + ) close() wifiPasswordInput = "" wifiUsernameInput = "" diff --git a/Modules/AppDrawer/AppDrawerPopout.qml b/Modules/AppDrawer/AppDrawerPopout.qml index a1d36b96..ecbf3266 100644 --- a/Modules/AppDrawer/AppDrawerPopout.qml +++ b/Modules/AppDrawer/AppDrawerPopout.qml @@ -404,29 +404,16 @@ DankPopout { width: appList.iconSize height: appList.iconSize anchors.verticalCenter: parent.verticalCenter - visible: model.icon !== undefined && model.icon !== "" - - property string iconValue: model.icon || "" - property bool isMaterial: iconValue.indexOf("material:") === 0 - property string materialName: isMaterial ? iconValue.substring(9) : "" - - DankIcon { - anchors.centerIn: parent - name: parent.materialName - size: appList.iconSize - Theme.spacingM - color: Theme.surfaceText - visible: parent.isMaterial - } IconImage { id: listIconImg anchors.fill: parent anchors.margins: Theme.spacingXS - source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) + source: Quickshell.iconPath(model.icon, true) smooth: true asynchronous: true - visible: !parent.isMaterial && status === Image.Ready + visible: status === Image.Ready } Rectangle { @@ -434,7 +421,7 @@ DankPopout { anchors.leftMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS anchors.bottomMargin: Theme.spacingM - visible: !parent.isMaterial && listIconImg.status !== Image.Ready + visible: !listIconImg.visible color: Theme.surfaceLight radius: Theme.cornerRadius border.width: 0 @@ -448,12 +435,11 @@ DankPopout { font.weight: Font.Bold } } - } Column { anchors.verticalCenter: parent.verticalCenter - width: (model.icon !== undefined && model.icon !== "") ? (parent.width - appList.iconSize - Theme.spacingL) : parent.width + width: parent.width - appList.iconSize - Theme.spacingL spacing: Theme.spacingXS StyledText { @@ -527,7 +513,6 @@ DankPopout { property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns property int baseCellHeight: baseCellWidth + 20 property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns - property int remainingSpace: width - (actualColumns * cellWidth) signal keyboardNavigationReset @@ -593,19 +578,6 @@ DankPopout { width: iconSize height: iconSize anchors.horizontalCenter: parent.horizontalCenter - visible: model.icon !== undefined && model.icon !== "" - - property string iconValue: model.icon || "" - property bool isMaterial: iconValue.indexOf("material:") === 0 - property string materialName: isMaterial ? iconValue.substring(9) : "" - - DankIcon { - anchors.centerIn: parent - name: parent.materialName - size: parent.iconSize - Theme.spacingL - color: Theme.surfaceText - visible: parent.isMaterial - } IconImage { id: gridIconImg @@ -614,10 +586,10 @@ DankPopout { anchors.leftMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS anchors.bottomMargin: Theme.spacingS - source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) + source: Quickshell.iconPath(model.icon, true) smooth: true asynchronous: true - visible: !parent.isMaterial && status === Image.Ready + visible: status === Image.Ready } Rectangle { @@ -625,7 +597,7 @@ DankPopout { anchors.leftMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS anchors.bottomMargin: Theme.spacingS - visible: !parent.isMaterial && gridIconImg.status !== Image.Ready + visible: !gridIconImg.visible color: Theme.surfaceLight radius: Theme.cornerRadius border.width: 0 diff --git a/Modules/AppDrawer/AppLauncher.qml b/Modules/AppDrawer/AppLauncher.qml index 0cd13b55..b5c5f0a2 100644 --- a/Modules/AppDrawer/AppLauncher.qml +++ b/Modules/AppDrawer/AppLauncher.qml @@ -8,10 +8,6 @@ import qs.Widgets Item { id: root - // DEVELOPER NOTE: This component manages the AppDrawer launcher (accessed via DankBar icon). - // Changes to launcher behavior, especially item rendering, filtering, or model structure, - // likely require corresponding updates in Modals/Spotlight/SpotlightResults.qml and vice versa. - property string searchQuery: "" property string selectedCategory: I18n.tr("All") property string viewMode: "list" // "list" or "grid" @@ -167,7 +163,7 @@ Item { filteredModel.append({ "name": app.name || "", "exec": app.execString || app.exec || app.action || "", - "icon": app.icon !== undefined ? app.icon : (isPluginItem ? "" : "application-x-executable"), + "icon": app.icon || "application-x-executable", "comment": app.comment || "", "categories": app.categories || [], "isPlugin": isPluginItem, diff --git a/Modules/ControlCenter/Details/NetworkDetail.qml b/Modules/ControlCenter/Details/NetworkDetail.qml index 43a1d752..3e2188b7 100644 --- a/Modules/ControlCenter/Details/NetworkDetail.qml +++ b/Modules/ControlCenter/Details/NetworkDetail.qml @@ -509,11 +509,7 @@ Rectangle { onClicked: function(event) { if (modelData.ssid !== NetworkService.currentWifiSSID) { if (modelData.secured && !modelData.saved) { - if (DMSService.apiVersion >= 7) { - NetworkService.connectToWifi(modelData.ssid) - } else if (PopoutService.wifiPasswordModal) { - PopoutService.wifiPasswordModal.show(modelData.ssid) - } + wifiPasswordModal.show(modelData.ssid) } else { NetworkService.connectToWifi(modelData.ssid) } @@ -567,11 +563,7 @@ Rectangle { NetworkService.disconnectWifi() } else { if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) { - if (DMSService.apiVersion >= 7) { - NetworkService.connectToWifi(networkContextMenu.currentSSID) - } else if (PopoutService.wifiPasswordModal) { - PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID) - } + wifiPasswordModal.show(networkContextMenu.currentSSID) } else { NetworkService.connectToWifi(networkContextMenu.currentSSID) } @@ -626,6 +618,10 @@ Rectangle { } } + WifiPasswordModal { + id: wifiPasswordModal + } + NetworkInfoModal { id: networkInfoModal } diff --git a/Modules/DankBar/DankBar.qml b/Modules/DankBar/DankBar.qml index b41b91e5..4cfd0982 100644 --- a/Modules/DankBar/DankBar.qml +++ b/Modules/DankBar/DankBar.qml @@ -23,7 +23,6 @@ Item { signal colorPickerRequested property alias barVariants: barVariants - property var hyprlandOverviewLoader: null function triggerControlCenterOnFocusedScreen() { let focusedScreenName = "" @@ -49,6 +48,30 @@ Item { return false } + function triggerWallpaperBrowserOnFocusedScreen() { + let focusedScreenName = "" + if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) { + focusedScreenName = Hyprland.focusedWorkspace.monitor.name + } else if (CompositorService.isNiri && NiriService.currentOutput) { + focusedScreenName = NiriService.currentOutput + } + + if (!focusedScreenName && barVariants.instances.length > 0) { + const firstBar = barVariants.instances[0] + firstBar.triggerWallpaperBrowser() + return true + } + + for (var i = 0; i < barVariants.instances.length; i++) { + const barInstance = barVariants.instances[i] + if (barInstance.modelData && barInstance.modelData.name === focusedScreenName) { + barInstance.triggerWallpaperBrowser() + return true + } + } + return false + } + Variants { id: barVariants model: SettingsData.getFilteredScreens("dankBar") @@ -57,6 +80,7 @@ Item { id: barWindow property var controlCenterButtonRef: null + property var clockButtonRef: null function triggerControlCenter() { controlCenterLoader.active = true @@ -79,6 +103,27 @@ Item { } } + function triggerWallpaperBrowser() { + dankDashPopoutLoader.active = true + if (!dankDashPopoutLoader.item) { + return + } + + if (clockButtonRef && dankDashPopoutLoader.item.setTriggerPosition) { + const globalPos = clockButtonRef.mapToGlobal(0, 0) + const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, clockButtonRef.width) + const section = clockButtonRef.section || "center" + dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen) + } else { + dankDashPopoutLoader.item.triggerScreen = barWindow.screen + } + + if (!dankDashPopoutLoader.item.dashVisible) { + dankDashPopoutLoader.item.currentTabIndex = 2 + } + dankDashPopoutLoader.item.dashVisible = !dankDashPopoutLoader.item.dashVisible + } + readonly property var dBarLayer: { switch (Quickshell.env("DMS_DANKBAR_LAYER")) { case "bottom": @@ -773,7 +818,6 @@ Item { section: topBarContent.getWidgetSection(parent) popupTarget: appDrawerLoader.item parentScreen: barWindow.screen - hyprlandOverviewLoader: root.hyprlandOverviewLoader onClicked: { appDrawerLoader.active = true appDrawerLoader.item?.toggle() @@ -787,7 +831,6 @@ Item { WorkspaceSwitcher { screenName: barWindow.screenName widgetHeight: barWindow.widgetThickness - hyprlandOverviewLoader: root.hyprlandOverviewLoader } } @@ -825,6 +868,17 @@ Item { return dankDashPopoutLoader.item } parentScreen: barWindow.screen + + Component.onCompleted: { + barWindow.clockButtonRef = this + } + + Component.onDestruction: { + if (barWindow.clockButtonRef === this) { + barWindow.clockButtonRef = null + } + } + onClockClicked: { dankDashPopoutLoader.active = true if (dankDashPopoutLoader.item) { @@ -874,7 +928,7 @@ Item { dankDashPopoutLoader.active = true if (dankDashPopoutLoader.item) { dankDashPopoutLoader.item.dashVisible = !dankDashPopoutLoader.item.dashVisible - dankDashPopoutLoader.item.currentTabIndex = 2 + dankDashPopoutLoader.item.currentTabIndex = 3 } } } @@ -1208,4 +1262,15 @@ Item { } } } + + IpcHandler { + target: "dankdash" + + function wallpaper(): string { + if (root.triggerWallpaperBrowserOnFocusedScreen()) { + return "SUCCESS: Toggled wallpaper browser" + } + return "ERROR: Failed to toggle wallpaper browser" + } + } } diff --git a/Modules/DankBar/Widgets/FocusedApp.qml b/Modules/DankBar/Widgets/FocusedApp.qml index 327c8339..8765f487 100644 --- a/Modules/DankBar/Widgets/FocusedApp.qml +++ b/Modules/DankBar/Widgets/FocusedApp.qml @@ -74,20 +74,14 @@ Rectangle { return false } - try { - if (!Hyprland.toplevels) return false - const hyprlandToplevels = Array.from(Hyprland.toplevels.values) - const activeHyprToplevel = hyprlandToplevels.find(t => t?.wayland === activeWindow) + const hyprlandToplevels = Array.from(Hyprland.toplevels.values) + const activeHyprToplevel = hyprlandToplevels.find(t => t.wayland === activeWindow) - if (!activeHyprToplevel || !activeHyprToplevel.workspace) { - return false - } - - return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id - } catch (e) { - console.error("FocusedApp: hasWindowsOnCurrentWorkspace error:", e) + if (!activeHyprToplevel || !activeHyprToplevel.workspace) { return false } + + return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id } return activeWindow && activeWindow.title diff --git a/Modules/DankBar/Widgets/LauncherButton.qml b/Modules/DankBar/Widgets/LauncherButton.qml index 685c6d43..f5e05269 100644 --- a/Modules/DankBar/Widgets/LauncherButton.qml +++ b/Modules/DankBar/Widgets/LauncherButton.qml @@ -17,7 +17,6 @@ Item { property var parentScreen: null property real widgetThickness: 30 property real barThickness: 48 - property var hyprlandOverviewLoader: null readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30)) signal clicked() @@ -36,8 +35,6 @@ Item { if (mouse.button === Qt.RightButton) { if (CompositorService.isNiri) { NiriService.toggleOverview() - } else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) { - root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen } return } diff --git a/Modules/DankBar/Widgets/RunningApps.qml b/Modules/DankBar/Widgets/RunningApps.qml index dfe0413f..a3426b74 100644 --- a/Modules/DankBar/Widgets/RunningApps.qml +++ b/Modules/DankBar/Widgets/RunningApps.qml @@ -30,28 +30,22 @@ Rectangle { if (!SettingsData.runningAppsGroupByApp) { return []; } - try { - const appGroups = new Map(); - sortedToplevels.forEach((toplevel, index) => { - if (!toplevel) return; - const appId = toplevel?.appId || "unknown"; - if (!appGroups.has(appId)) { - appGroups.set(appId, { - appId: appId, - windows: [] - }); - } - appGroups.get(appId).windows.push({ - toplevel: toplevel, - windowId: index, - windowTitle: toplevel?.title || "(Unnamed)" + const appGroups = new Map(); + sortedToplevels.forEach((toplevel, index) => { + const appId = toplevel.appId || "unknown"; + if (!appGroups.has(appId)) { + appGroups.set(appId, { + appId: appId, + windows: [] }); + } + appGroups.get(appId).windows.push({ + toplevel: toplevel, + windowId: index, + windowTitle: toplevel.title || "(Unnamed)" }); - return Array.from(appGroups.values()); - } catch (e) { - console.error("RunningApps: groupedWindows error:", e); - return []; - } + }); + return Array.from(appGroups.values()); } readonly property int windowCount: SettingsData.runningAppsGroupByApp ? groupedWindows.length : sortedToplevels.length readonly property int calculatedSize: { diff --git a/Modules/DankBar/Widgets/WorkspaceSwitcher.qml b/Modules/DankBar/Widgets/WorkspaceSwitcher.qml index f1432a17..5afc27c3 100644 --- a/Modules/DankBar/Widgets/WorkspaceSwitcher.qml +++ b/Modules/DankBar/Widgets/WorkspaceSwitcher.qml @@ -15,7 +15,6 @@ Rectangle { property string screenName: "" property real widgetHeight: 30 property real barThickness: 48 - property var hyprlandOverviewLoader: null readonly property var sortedToplevels: { return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, parentScreen?.name); } @@ -245,17 +244,11 @@ Rectangle { MouseArea { anchors.fill: parent - acceptedButtons: Qt.RightButton + acceptedButtons: Qt.NoButton property real scrollAccumulator: 0 property real touchpadThreshold: 500 - onClicked: mouse => { - if (mouse.button === Qt.RightButton && CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) { - root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen - } - } - onWheel: wheel => { const deltaY = wheel.angleDelta.y const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0 diff --git a/Modules/DankDash/DankDashPopout.qml b/Modules/DankDash/DankDashPopout.qml index f65f2c9d..5fbcf378 100644 --- a/Modules/DankDash/DankDashPopout.qml +++ b/Modules/DankDash/DankDashPopout.qml @@ -16,6 +16,8 @@ DankPopout { property var triggerScreen: null property int currentTabIndex: 0 + keyboardFocusMode: WlrKeyboardFocus.Exclusive + function setTriggerPosition(x, y, width, section, screen) { triggerSection = section triggerScreen = screen @@ -43,15 +45,49 @@ DankPopout { shouldBeVisible: dashVisible visible: shouldBeVisible + property bool __focusArmed: false + property bool __contentReady: false + + function __tryFocusOnce() { + if (!__focusArmed) return + const win = root.window + if (!win || !win.visible) return + if (!contentLoader.item) return + + if (win.requestActivate) win.requestActivate() + contentLoader.item.forceActiveFocus(Qt.TabFocusReason) + + if (contentLoader.item.activeFocus) + __focusArmed = false + } onDashVisibleChanged: { if (dashVisible) { + __focusArmed = true + __contentReady = !!contentLoader.item open() + __tryFocusOnce() } else { + __focusArmed = false + __contentReady = false close() } } + Connections { + target: contentLoader + function onLoaded() { + __contentReady = true + if (__focusArmed) __tryFocusOnce() + } + } + + Connections { + target: root.window ? root.window : null + enabled: !!root.window + function onVisibleChanged() { if (__focusArmed) __tryFocusOnce() } + } + onBackgroundClicked: { dashVisible = false } @@ -67,18 +103,12 @@ DankPopout { Component.onCompleted: { if (root.shouldBeVisible) { - forceActiveFocus() - } - } - - Keys.onPressed: function(event) { - if (event.key === Qt.Key_Escape) { - root.dashVisible = false - event.accepted = true + mainContainer.forceActiveFocus() } } Connections { + target: root function onShouldBeVisibleChanged() { if (root.shouldBeVisible) { Qt.callLater(function() { @@ -86,7 +116,52 @@ DankPopout { }) } } - target: root + } + + Keys.onPressed: function(event) { + if (event.key === Qt.Key_Escape) { + root.dashVisible = false + event.accepted = true + return + } + + if (event.key === Qt.Key_Tab && !(event.modifiers & Qt.ShiftModifier)) { + let nextIndex = root.currentTabIndex + 1 + while (nextIndex < tabBar.model.length && tabBar.model[nextIndex] && tabBar.model[nextIndex].isAction) { + nextIndex++ + } + if (nextIndex >= tabBar.model.length) { + nextIndex = 0 + } + root.currentTabIndex = nextIndex + event.accepted = true + return + } + + if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) { + let prevIndex = root.currentTabIndex - 1 + while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) { + prevIndex-- + } + if (prevIndex < 0) { + prevIndex = tabBar.model.length - 1 + while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) { + prevIndex-- + } + } + if (prevIndex >= 0) { + root.currentTabIndex = prevIndex + } + event.accepted = true + return + } + + if (root.currentTabIndex === 2 && wallpaperTab.handleKeyEvent) { + if (wallpaperTab.handleKeyEvent(event)) { + event.accepted = true + return + } + } } Rectangle { @@ -128,11 +203,23 @@ DankPopout { currentIndex: root.currentTabIndex spacing: Theme.spacingS equalWidthTabs: true + enableArrowNavigation: false + focus: false + activeFocusOnTab: false + nextFocusTarget: { + const item = pages.currentItem + if (!item) + return null + if (item.focusTarget) + return item.focusTarget + return item + } model: { let tabs = [ { icon: "dashboard", text: I18n.tr("Overview") }, - { icon: "music_note", text: I18n.tr("Media") } + { icon: "music_note", text: I18n.tr("Media") }, + { icon: "wallpaper", text: I18n.tr("Wallpapers") } ] if (SettingsData.weatherEnabled) { @@ -148,7 +235,7 @@ DankPopout { } onActionTriggered: function(index) { - let settingsIndex = SettingsData.weatherEnabled ? 3 : 2 + let settingsIndex = SettingsData.weatherEnabled ? 4 : 3 if (index === settingsIndex) { dashVisible = false settingsModal.show() @@ -168,7 +255,8 @@ DankPopout { implicitHeight: { if (currentIndex === 0) return overviewTab.implicitHeight if (currentIndex === 1) return mediaTab.implicitHeight - if (SettingsData.weatherEnabled && currentIndex === 2) return weatherTab.implicitHeight + if (currentIndex === 2) return wallpaperTab.implicitHeight + if (SettingsData.weatherEnabled && currentIndex === 3) return weatherTab.implicitHeight return overviewTab.implicitHeight } currentIndex: root.currentTabIndex @@ -178,8 +266,8 @@ DankPopout { onSwitchToWeatherTab: { if (SettingsData.weatherEnabled) { - tabBar.currentIndex = 2 - tabBar.tabClicked(2) + tabBar.currentIndex = 3 + tabBar.tabClicked(3) } } @@ -193,9 +281,16 @@ DankPopout { id: mediaTab } + WallpaperTab { + id: wallpaperTab + active: root.currentTabIndex === 2 + tabBarItem: tabBar + keyForwardTarget: mainContainer + } + WeatherTab { id: weatherTab - visible: SettingsData.weatherEnabled && root.currentTabIndex === 2 + visible: SettingsData.weatherEnabled && root.currentTabIndex === 3 } } } diff --git a/Modules/DankDash/WallpaperTab.qml b/Modules/DankDash/WallpaperTab.qml new file mode 100644 index 00000000..b650808c --- /dev/null +++ b/Modules/DankDash/WallpaperTab.qml @@ -0,0 +1,523 @@ +import Qt.labs.folderlistmodel +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Common +import qs.Modals.FileBrowser +import qs.Services +import qs.Widgets + +Item { + id: root + + implicitWidth: 700 + implicitHeight: 410 + + property var wallpaperList: [] + property string wallpaperDir: "" + property int currentPage: 0 + property int itemsPerPage: 16 + property int totalPages: Math.max(1, Math.ceil(wallpaperList.length / itemsPerPage)) + property bool active: false + property Item focusTarget: wallpaperGrid + property Item tabBarItem: null + property int gridIndex: 0 + property Item keyForwardTarget: null + property int lastPage: 0 + property bool enableAnimation: false + + signal requestTabChange(int newIndex) + + onCurrentPageChanged: { + if (currentPage !== lastPage) { + enableAnimation = false + lastPage = currentPage + } + } + + onVisibleChanged: { + if (visible && active) { + setInitialSelection() + } + } + + Component.onCompleted: { + loadWallpapers() + if (visible && active) { + setInitialSelection() + } + } + + onActiveChanged: { + if (active && visible) { + setInitialSelection() + } + } + + function handleKeyEvent(event) { + const columns = 4 + const rows = 4 + const currentRow = Math.floor(gridIndex / columns) + const currentCol = gridIndex % columns + const visibleCount = wallpaperGrid.model.length + + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + if (gridIndex >= 0) { + const item = wallpaperGrid.currentItem + if (item && item.wallpaperPath) { + SessionData.setWallpaper(item.wallpaperPath) + } + } + return true + } + + if (event.key === Qt.Key_Right) { + if (gridIndex + 1 < visibleCount) { + // Move right within current page + gridIndex++ + } else if (gridIndex === visibleCount - 1 && currentPage < totalPages - 1) { + // At last item in page, go to next page + gridIndex = 0 + currentPage++ + } + return true + } + + if (event.key === Qt.Key_Left) { + if (gridIndex > 0) { + // Move left within current page + gridIndex-- + } else if (gridIndex === 0 && currentPage > 0) { + // At first item in page, go to previous page (last item) + currentPage-- + gridIndex = Math.min(itemsPerPage - 1, wallpaperList.length - currentPage * itemsPerPage - 1) + } + return true + } + + if (event.key === Qt.Key_Down) { + if (gridIndex + columns < visibleCount) { + // Move down within current page + gridIndex += columns + } else if (gridIndex >= visibleCount - columns && currentPage < totalPages - 1) { + // In last row, go to next page + gridIndex = currentCol + currentPage++ + } + return true + } + + if (event.key === Qt.Key_Up) { + if (gridIndex >= columns) { + // Move up within current page + gridIndex -= columns + } else if (gridIndex < columns && currentPage > 0) { + // In first row, go to previous page (last row) + currentPage-- + const prevPageCount = Math.min(itemsPerPage, wallpaperList.length - currentPage * itemsPerPage) + const prevPageRows = Math.ceil(prevPageCount / columns) + gridIndex = (prevPageRows - 1) * columns + currentCol + gridIndex = Math.min(gridIndex, prevPageCount - 1) + } + return true + } + + if (event.key === Qt.Key_PageUp && currentPage > 0) { + gridIndex = 0 + currentPage-- + return true + } + + if (event.key === Qt.Key_PageDown && currentPage < totalPages - 1) { + gridIndex = 0 + currentPage++ + return true + } + + if (event.key === Qt.Key_Home && event.modifiers & Qt.ControlModifier) { + gridIndex = 0 + currentPage = 0 + return true + } + + if (event.key === Qt.Key_End && event.modifiers & Qt.ControlModifier) { + gridIndex = 0 + currentPage = totalPages - 1 + return true + } + + return false + } + + function setInitialSelection() { + if (!SessionData.wallpaperPath) { + gridIndex = 0 + return + } + + const startIndex = currentPage * itemsPerPage + const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length) + const pageWallpapers = wallpaperList.slice(startIndex, endIndex) + + for (let i = 0; i < pageWallpapers.length; i++) { + if (pageWallpapers[i] === SessionData.wallpaperPath) { + gridIndex = i + return + } + } + gridIndex = 0 + } + + onWallpaperListChanged: { + if (visible && active) { + setInitialSelection() + } + } + + function loadWallpapers() { + const currentWallpaper = SessionData.wallpaperPath + + // Try current wallpaper path / fallback to wallpaperLastPath + if (!currentWallpaper || currentWallpaper.startsWith("#") || currentWallpaper.startsWith("we:")) { + if (CacheData.wallpaperLastPath && CacheData.wallpaperLastPath !== "") { + wallpaperDir = CacheData.wallpaperLastPath + } else { + wallpaperDir = "" + wallpaperList = [] + } + return + } + + wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/')) + } + + function updateWallpaperList() { + if (!wallpaperFolderModel || wallpaperFolderModel.count === 0) { + wallpaperList = [] + currentPage = 0 + gridIndex = 0 + return + } + + // Build list from FolderListModel + const files = [] + for (let i = 0; i < wallpaperFolderModel.count; i++) { + const filePath = wallpaperFolderModel.get(i, "filePath") + if (filePath) { + // Remove file:// prefix if present + const cleanPath = filePath.toString().replace(/^file:\/\//, '') + files.push(cleanPath) + } + } + + wallpaperList = files + + const currentPath = SessionData.wallpaperPath + const selectedIndex = currentPath ? wallpaperList.indexOf(currentPath) : -1 + + if (selectedIndex >= 0) { + currentPage = Math.floor(selectedIndex / itemsPerPage) + gridIndex = selectedIndex % itemsPerPage + } else { + const maxPage = Math.max(0, Math.ceil(files.length / itemsPerPage) - 1) + currentPage = Math.min(Math.max(0, currentPage), maxPage) + gridIndex = 0 + } + } + + Connections { + target: SessionData + function onWallpaperPathChanged() { + loadWallpapers() + } + } + + FolderListModel { + id: wallpaperFolderModel + + showDirsFirst: false + showDotAndDotDot: false + showHidden: false + nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] + showFiles: true + showDirs: false + sortField: FolderListModel.Name + folder: wallpaperDir ? "file://" + wallpaperDir : "" + + onStatusChanged: { + if (status === FolderListModel.Ready) { + updateWallpaperList() + } + } + + onCountChanged: { + if (status === FolderListModel.Ready) { + updateWallpaperList() + } + } + } + + Loader { + id: wallpaperBrowserLoader + active: false + asynchronous: true + + sourceComponent: FileBrowserModal { + Component.onCompleted: { + open() + } + browserTitle: "Select Wallpaper Directory" + browserIcon: "folder_open" + browserType: "wallpaper" + showHiddenFiles: false + fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] + allowStacking: true + + onFileSelected: (path) => { + // Set the selected wallpaper + const cleanPath = path.replace(/^file:\/\//, '') + SessionData.setWallpaper(cleanPath) + + // Extract directory from the selected file and load all wallpapers + const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/')) + if (dirPath) { + wallpaperDir = dirPath + CacheData.wallpaperLastPath = dirPath + CacheData.saveCache() + } + close() + } + + onDialogClosed: { + Qt.callLater(() => wallpaperBrowserLoader.active = false) + } + } + } + + Column { + anchors.fill: parent + spacing: 0 + + Item { + width: parent.width + height: parent.height - 50 + + GridView { + id: wallpaperGrid + anchors.centerIn: parent + width: parent.width - Theme.spacingS + height: parent.height - Theme.spacingS + cellWidth: width / 4 + cellHeight: height / 4 + clip: true + enabled: root.active + interactive: root.active + boundsBehavior: Flickable.StopAtBounds + keyNavigationEnabled: false + activeFocusOnTab: false + highlightFollowsCurrentItem: true + highlightMoveDuration: enableAnimation ? Theme.shortDuration : 0 + focus: false + + highlight: Item { + z: 1000 + Rectangle { + anchors.fill: parent + anchors.margins: Theme.spacingXS + color: "transparent" + border.width: 3 + border.color: Theme.primary + radius: Theme.cornerRadius + } + } + + model: { + const startIndex = currentPage * itemsPerPage + const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length) + return wallpaperList.slice(startIndex, endIndex) + } + + onModelChanged: { + const clampedIndex = model.length > 0 ? Math.min(Math.max(0, gridIndex), model.length - 1) : 0 + if (gridIndex !== clampedIndex) { + gridIndex = clampedIndex + } + } + + onCountChanged: { + if (count > 0) { + const clampedIndex = Math.min(gridIndex, count - 1) + currentIndex = clampedIndex + positionViewAtIndex(clampedIndex, GridView.Contain) + } + enableAnimation = true + } + + Connections { + target: root + function onGridIndexChanged() { + if (enableAnimation && wallpaperGrid.count > 0) { + wallpaperGrid.currentIndex = gridIndex + } + } + } + + delegate: Item { + width: wallpaperGrid.cellWidth + height: wallpaperGrid.cellHeight + + property string wallpaperPath: modelData || "" + property bool isSelected: SessionData.wallpaperPath === modelData + + Rectangle { + id: wallpaperCard + anchors.fill: parent + anchors.margins: Theme.spacingXS + color: Theme.surfaceContainerHighest + radius: Theme.cornerRadius + clip: true + + Rectangle { + anchors.fill: parent + color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent" + radius: parent.radius + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + } + + Image { + id: thumbnailImage + anchors.fill: parent + source: modelData ? `file://${modelData}` : "" + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + smooth: true + + layer.enabled: true + layer.effect: MultiEffect { + maskEnabled: true + maskThresholdMin: 0.5 + maskSpreadAtMin: 1.0 + maskSource: ShaderEffectSource { + sourceItem: Rectangle { + width: thumbnailImage.width + height: thumbnailImage.height + radius: Theme.cornerRadius + } + } + } + } + + BusyIndicator { + anchors.centerIn: parent + running: thumbnailImage.status === Image.Loading + visible: running + } + + StateLayer { + anchors.fill: parent + cornerRadius: parent.radius + stateColor: Theme.primary + } + + MouseArea { + id: wallpaperMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + gridIndex = index + if (modelData) { + SessionData.setWallpaper(modelData) + } + // Don't steal focus - let mainContainer keep it for keyboard nav + } + } + } + } + } + + StyledText { + anchors.centerIn: parent + visible: wallpaperList.length === 0 + text: "No wallpapers found\n\nClick the folder icon below to browse" + font.pixelSize: 14 + color: Theme.outline + horizontalAlignment: Text.AlignHCenter + } + } + + Row { + width: parent.width + height: 50 + spacing: Theme.spacingS + + Item { + width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2 + height: parent.height + } + + Row { + id: controlsRow + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankActionButton { + anchors.verticalCenter: parent.verticalCenter + iconName: "skip_previous" + iconSize: 20 + buttonSize: 32 + enabled: currentPage > 0 + opacity: enabled ? 1.0 : 0.3 + onClicked: { + if (currentPage > 0) { + currentPage-- + } + } + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: wallpaperList.length > 0 ? `${wallpaperList.length} wallpapers • ${currentPage + 1} / ${totalPages}` : "No wallpapers" + font.pixelSize: 14 + color: Theme.surfaceText + opacity: 0.7 + } + + DankActionButton { + anchors.verticalCenter: parent.verticalCenter + iconName: "skip_next" + iconSize: 20 + buttonSize: 32 + enabled: currentPage < totalPages - 1 + opacity: enabled ? 1.0 : 0.3 + onClicked: { + if (currentPage < totalPages - 1) { + currentPage++ + } + } + } + } + + DankActionButton { + id: browseButton + anchors.verticalCenter: parent.verticalCenter + iconName: "folder_open" + iconSize: 20 + buttonSize: 32 + opacity: 0.7 + onClicked: wallpaperBrowserLoader.active = true + } + } + } +} \ No newline at end of file diff --git a/Modules/Greetd/GreeterContent.qml b/Modules/Greetd/GreeterContent.qml index 7c2e7209..0ace65fd 100644 --- a/Modules/Greetd/GreeterContent.qml +++ b/Modules/Greetd/GreeterContent.qml @@ -7,6 +7,7 @@ import Quickshell import Quickshell.Io import Quickshell.Services.Greetd import Quickshell.Services.Pam +import Quickshell.Services.Mpris import qs.Common import qs.Services import qs.Widgets @@ -15,6 +16,8 @@ import qs.Modules.Lock Item { id: root + required property var sessionLock + readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS") property string screenName: "" property string randomFact: "" @@ -114,6 +117,21 @@ Item { onTriggered: updateHyprlandLayout() } + // ! This was for development and testing, just leaving so people can see how I did it. + // Timer { + // id: autoUnlockTimer + // interval: 10000 + // running: true + // onTriggered: { + // root.sessionLock.locked = false + // GreeterState.unlocking = true + // const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex] + // if (sessionCmd) { + // GreetdMemory.setLastSessionId(sessionCmd.split(" ")[0]) + // Greetd.launch(sessionCmd.split(" "), [], true) + // } + // } + // } Connections { target: GreetdMemory @@ -655,11 +673,180 @@ Item { height: 24 color: Qt.rgba(255, 255, 255, 0.2) anchors.verticalCenter: parent.verticalCenter - visible: { - const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) || - (CompositorService.isHyprland && hyprlandLayoutCount > 1) - return keyboardVisible && WeatherService.weather.available + visible: MprisController.activePlayer + } + + Row { + spacing: Theme.spacingS + visible: MprisController.activePlayer + anchors.verticalCenter: parent.verticalCenter + + Item { + width: 20 + height: Theme.iconSize + anchors.verticalCenter: parent.verticalCenter + + Loader { + active: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing + + sourceComponent: Component { + Ref { + service: CavaService + } + } + } + + Timer { + running: !CavaService.cavaAvailable && MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing + interval: 256 + repeat: true + onTriggered: { + CavaService.values = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20, Math.random() * 45 + 15, Math.random() * 55 + 25] + } + } + + Row { + anchors.centerIn: parent + spacing: 1.5 + + Repeater { + model: 6 + + Rectangle { + width: 2 + height: { + if (MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing && CavaService.values.length > index) { + const rawLevel = CavaService.values[index] || 0 + const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100 + const maxHeight = Theme.iconSize - 2 + const minHeight = 3 + return minHeight + (scaledLevel / 100) * (maxHeight - minHeight) + } + return 3 + } + radius: 1.5 + color: "white" + anchors.verticalCenter: parent.verticalCenter + + Behavior on height { + NumberAnimation { + duration: Anims.durShort + easing.type: Easing.BezierSpline + easing.bezierCurve: Anims.standardDecel + } + } + } + } + } } + + StyledText { + text: { + const player = MprisController.activePlayer + if (!player?.trackTitle) + return "" + const title = player.trackTitle + const artist = player.trackArtist || "" + return artist ? title + " • " + artist : title + } + font.pixelSize: Theme.fontSizeLarge + color: "white" + opacity: 0.9 + anchors.verticalCenter: parent.verticalCenter + elide: Text.ElideRight + width: Math.min(implicitWidth, 400) + wrapMode: Text.NoWrap + maximumLineCount: 1 + } + + Row { + spacing: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + + Rectangle { + width: 20 + height: 20 + radius: 10 + anchors.verticalCenter: parent.verticalCenter + color: prevArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent" + visible: MprisController.activePlayer + opacity: (MprisController.activePlayer?.canGoPrevious ?? false) ? 1 : 0.3 + + DankIcon { + anchors.centerIn: parent + name: "skip_previous" + size: 12 + color: "white" + } + + MouseArea { + id: prevArea + anchors.fill: parent + enabled: MprisController.activePlayer?.canGoPrevious ?? false + hoverEnabled: enabled + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: MprisController.activePlayer?.previous() + } + } + + Rectangle { + width: 24 + height: 24 + radius: 12 + anchors.verticalCenter: parent.verticalCenter + color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? Qt.rgba(255, 255, 255, 0.9) : Qt.rgba(255, 255, 255, 0.2) + visible: MprisController.activePlayer + + DankIcon { + anchors.centerIn: parent + name: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow" + size: 14 + color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "black" : "white" + } + + MouseArea { + anchors.fill: parent + enabled: MprisController.activePlayer + hoverEnabled: enabled + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: MprisController.activePlayer?.togglePlaying() + } + } + + Rectangle { + width: 20 + height: 20 + radius: 10 + anchors.verticalCenter: parent.verticalCenter + color: nextArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent" + visible: MprisController.activePlayer + opacity: (MprisController.activePlayer?.canGoNext ?? false) ? 1 : 0.3 + + DankIcon { + anchors.centerIn: parent + name: "skip_next" + size: 12 + color: "white" + } + + MouseArea { + id: nextArea + anchors.fill: parent + enabled: MprisController.activePlayer?.canGoNext ?? false + hoverEnabled: enabled + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: MprisController.activePlayer?.next() + } + } + } + } + + Rectangle { + width: 1 + height: 24 + color: Qt.rgba(255, 255, 255, 0.2) + anchors.verticalCenter: parent.verticalCenter + visible: MprisController.activePlayer && WeatherService.weather.available } Row { @@ -1060,6 +1247,7 @@ Item { } function onReadyToLaunch() { + root.sessionLock.locked = false GreeterState.unlocking = true const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex] if (sessionCmd) { diff --git a/Modules/Greetd/GreeterSurface.qml b/Modules/Greetd/GreeterSurface.qml index a8af5df4..bc62426a 100644 --- a/Modules/Greetd/GreeterSurface.qml +++ b/Modules/Greetd/GreeterSurface.qml @@ -1,34 +1,18 @@ -pragma ComponentBehavior: Bound - import QtQuick import Quickshell import Quickshell.Wayland import Quickshell.Services.Greetd -import qs.Common -Variants { - model: Quickshell.screens +WlSessionLockSurface { + id: root - PanelWindow { - id: root + required property WlSessionLock lock - property var modelData + color: "transparent" - screen: modelData - anchors { - left: true - right: true - top: true - bottom: true - } - exclusionMode: ExclusionMode.Normal - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive - color: "transparent" - - GreeterContent { - anchors.fill: parent - screenName: root.screen?.name ?? "" - } + GreeterContent { + anchors.fill: parent + screenName: root.screen?.name ?? "" + sessionLock: root.lock } } diff --git a/Modules/HyprWorkspaces/HyprlandOverview.qml b/Modules/HyprWorkspaces/HyprlandOverview.qml deleted file mode 100644 index 94d76738..00000000 --- a/Modules/HyprWorkspaces/HyprlandOverview.qml +++ /dev/null @@ -1,284 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Io -import Quickshell.Wayland -import Quickshell.Hyprland -import qs.Common -import qs.Services - -Scope { - id: overviewScope - - property bool overviewOpen: false - - Loader { - id: hyprlandLoader - active: overviewScope.overviewOpen - asynchronous: false - - sourceComponent: Variants { - id: overviewVariants - model: Quickshell.screens - - PanelWindow { - id: root - required property var modelData - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) - property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) - - screen: modelData - visible: overviewScope.overviewOpen - color: "transparent" - - WlrLayershell.namespace: "quickshell:overview" - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive - - anchors { - top: true - left: true - right: true - bottom: true - } - - HyprlandFocusGrab { - id: grab - windows: [root] - active: false - property bool hasBeenActivated: false - onActiveChanged: { - if (active) { - hasBeenActivated = true - } - } - onCleared: () => { - if (hasBeenActivated && overviewScope.overviewOpen) { - overviewScope.overviewOpen = false - } - } - } - - Connections { - target: overviewScope - function onOverviewOpenChanged() { - if (overviewScope.overviewOpen) { - grab.hasBeenActivated = false - delayedGrabTimer.start() - } else { - delayedGrabTimer.stop() - grab.active = false - grab.hasBeenActivated = false - } - } - } - - Connections { - target: root - function onMonitorIsFocusedChanged() { - if (overviewScope.overviewOpen && root.monitorIsFocused && !grab.active) { - grab.hasBeenActivated = false - grab.active = true - } else if (overviewScope.overviewOpen && !root.monitorIsFocused && grab.active) { - grab.active = false - } - } - } - - Timer { - id: delayedGrabTimer - interval: 150 - repeat: false - onTriggered: { - if (overviewScope.overviewOpen && root.monitorIsFocused) { - grab.active = true - } - } - } - - Timer { - id: closeTimer - interval: Theme.expressiveDurations.expressiveDefaultSpatial + 120 - onTriggered: { - root.visible = false - } - } - - Rectangle { - id: background - anchors.fill: parent - color: "black" - opacity: overviewScope.overviewOpen ? 0.5 : 0 - - Behavior on opacity { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized - } - } - - MouseArea { - anchors.fill: parent - onClicked: mouse => { - const localPos = mapToItem(contentContainer, mouse.x, mouse.y) - if (localPos.x < 0 || localPos.x > contentContainer.width || localPos.y < 0 || localPos.y > contentContainer.height) { - overviewScope.overviewOpen = false - closeTimer.restart() - } - } - } - } - - Item { - id: contentContainer - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 100 - width: childrenRect.width - height: childrenRect.height - - opacity: overviewScope.overviewOpen ? 1 : 0 - transform: [scaleTransform, motionTransform] - - Scale { - id: scaleTransform - origin.x: contentContainer.width / 2 - origin.y: contentContainer.height / 2 - xScale: overviewScope.overviewOpen ? 1 : 0.96 - yScale: overviewScope.overviewOpen ? 1 : 0.96 - - Behavior on xScale { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized - } - } - - Behavior on yScale { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized - } - } - } - - Translate { - id: motionTransform - x: 0 - y: overviewScope.overviewOpen ? 0 : Theme.spacingL - - Behavior on y { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized - } - } - } - - Behavior on opacity { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized - } - } - - Loader { - id: overviewLoader - active: overviewScope.overviewOpen - asynchronous: false - - sourceComponent: OverviewWidget { - panelWindow: root - overviewOpen: overviewScope.overviewOpen - } - } - } - - FocusScope { - id: focusScope - anchors.fill: parent - visible: overviewScope.overviewOpen - focus: overviewScope.overviewOpen && root.monitorIsFocused - - Keys.onEscapePressed: event => { - if (!root.monitorIsFocused) return - overviewScope.overviewOpen = false - closeTimer.restart() - event.accepted = true - } - - Keys.onPressed: event => { - if (!root.monitorIsFocused) return - - if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) { - if (!overviewLoader.item) return - - const thisMonitorWorkspaceIds = overviewLoader.item.thisMonitorWorkspaceIds - if (thisMonitorWorkspaceIds.length === 0) return - - const currentId = root.monitor.activeWorkspace?.id ?? thisMonitorWorkspaceIds[0] - const currentIndex = thisMonitorWorkspaceIds.indexOf(currentId) - - let targetIndex - if (event.key === Qt.Key_Left) { - targetIndex = currentIndex - 1 - if (targetIndex < 0) targetIndex = thisMonitorWorkspaceIds.length - 1 - } else { - targetIndex = currentIndex + 1 - if (targetIndex >= thisMonitorWorkspaceIds.length) targetIndex = 0 - } - - const targetId = thisMonitorWorkspaceIds[targetIndex] - Hyprland.dispatch("workspace " + targetId) - event.accepted = true - } - } - - onVisibleChanged: { - if (visible && overviewScope.overviewOpen && root.monitorIsFocused) { - Qt.callLater(() => focusScope.forceActiveFocus()) - } - } - - Connections { - target: root - function onMonitorIsFocusedChanged() { - if (root.monitorIsFocused && overviewScope.overviewOpen) { - Qt.callLater(() => focusScope.forceActiveFocus()) - } - } - } - } - - onVisibleChanged: { - if (visible && overviewScope.overviewOpen) { - Qt.callLater(() => focusScope.forceActiveFocus()) - } else if (!visible) { - grab.active = false - } - } - - Connections { - target: overviewScope - function onOverviewOpenChanged() { - if (overviewScope.overviewOpen) { - closeTimer.stop() - root.visible = true - Qt.callLater(() => focusScope.forceActiveFocus()) - } else { - closeTimer.restart() - grab.active = false - } - } - } - } - } - } -} diff --git a/Modules/HyprWorkspaces/OverviewWidget.qml b/Modules/HyprWorkspaces/OverviewWidget.qml deleted file mode 100644 index 1b6233f2..00000000 --- a/Modules/HyprWorkspaces/OverviewWidget.qml +++ /dev/null @@ -1,428 +0,0 @@ -import QtQuick -import QtQuick.Effects -import QtQuick.Layouts -import Quickshell -import Quickshell.Wayland -import Quickshell.Hyprland -import qs.Common -import qs.Services -import qs.Widgets - -Item { - id: root - required property var panelWindow - required property bool overviewOpen - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) - readonly property int workspacesShown: SettingsData.overviewRows * SettingsData.overviewColumns - - readonly property var allWorkspaces: Hyprland.workspaces?.values || [] - readonly property var allWorkspaceIds: { - const workspaces = allWorkspaces - if (!workspaces || workspaces.length === 0) return [] - try { - const ids = workspaces.map(ws => ws?.id).filter(id => id !== null && id !== undefined) - return ids.sort((a, b) => a - b) - } catch (e) { - return [] - } - } - - readonly property var thisMonitorWorkspaceIds: { - const workspaces = allWorkspaces - const mon = monitor - if (!workspaces || workspaces.length === 0 || !mon) return [] - try { - const filtered = workspaces.filter(ws => ws?.monitor?.name === mon.name) - return filtered.map(ws => ws?.id).filter(id => id !== null && id !== undefined).sort((a, b) => a - b) - } catch (e) { - return [] - } - } - - readonly property var displayedWorkspaceIds: { - if (!allWorkspaceIds || allWorkspaceIds.length === 0) { - const result = [] - for (let i = 1; i <= workspacesShown; i++) { - result.push(i) - } - return result - } - - try { - const maxExisting = Math.max(...allWorkspaceIds) - const totalNeeded = Math.max(workspacesShown, allWorkspaceIds.length) - const result = [] - - for (let i = 1; i <= maxExisting; i++) { - result.push(i) - } - - let nextId = maxExisting + 1 - while (result.length < totalNeeded) { - result.push(nextId) - nextId++ - } - - return result - } catch (e) { - const result = [] - for (let i = 1; i <= workspacesShown; i++) { - result.push(i) - } - return result - } - } - - readonly property int minWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[0] : 1 - readonly property int maxWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[displayedWorkspaceIds.length - 1] : workspacesShown - readonly property int displayWorkspaceCount: displayedWorkspaceIds.length - - function getWorkspaceMonitorName(workspaceId) { - if (!allWorkspaces || !workspaceId) return "" - try { - const ws = allWorkspaces.find(w => w?.id === workspaceId) - return ws?.monitor?.name ?? "" - } catch (e) { - return "" - } - } - - function workspaceHasWindows(workspaceId) { - if (!workspaceId) return false - try { - const workspace = allWorkspaces.find(ws => ws?.id === workspaceId) - if (!workspace) return false - const toplevels = workspace?.toplevels?.values || [] - return toplevels.length > 0 - } catch (e) { - return false - } - } - - property bool monitorIsFocused: monitor?.focused ?? false - property real scale: SettingsData.overviewScale - property color activeBorderColor: Theme.primary - - property real workspaceImplicitWidth: ((monitor.width / monitor.scale) * root.scale) - property real workspaceImplicitHeight: ((monitor.height / monitor.scale) * root.scale) - - property int workspaceZ: 0 - property int windowZ: 1 - property int monitorLabelZ: 2 - property int windowDraggingZ: 99999 - property real workspaceSpacing: 5 - - property int draggingFromWorkspace: -1 - property int draggingTargetWorkspace: -1 - - implicitWidth: overviewBackground.implicitWidth + Theme.spacingL * 2 - implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2 - - Component.onCompleted: { - Hyprland.refreshToplevels() - Hyprland.refreshWorkspaces() - Hyprland.refreshMonitors() - } - - onOverviewOpenChanged: { - if (overviewOpen) { - Hyprland.refreshToplevels() - Hyprland.refreshWorkspaces() - Hyprland.refreshMonitors() - } - } - - Rectangle { - id: overviewBackground - property real padding: 10 - anchors.fill: parent - anchors.margins: Theme.spacingL - - implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 - implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 - radius: Theme.cornerRadius - color: Theme.surfaceContainer - - layer.enabled: true - layer.effect: MultiEffect { - shadowEnabled: true - shadowBlur: 0.5 - shadowHorizontalOffset: 0 - shadowVerticalOffset: 4 - shadowColor: Theme.shadowStrong - shadowOpacity: 1 - blurMax: 32 - } - - ColumnLayout { - id: workspaceColumnLayout - - z: root.workspaceZ - anchors.centerIn: parent - spacing: workspaceSpacing - - Repeater { - model: SettingsData.overviewRows - delegate: RowLayout { - id: row - property int rowIndex: index - spacing: workspaceSpacing - - Repeater { - model: SettingsData.overviewColumns - Rectangle { - id: workspace - property int colIndex: index - property int workspaceIndex: rowIndex * SettingsData.overviewColumns + colIndex - property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1 - property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false - property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null - property bool isActive: workspaceObj?.active ?? false - property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true - property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false - property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : "" - property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3) - property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1) - property color hoveredBorderColor: Theme.surfaceVariant - property bool hoveredWhileDragging: false - property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows - - visible: workspaceValue !== -1 - - implicitWidth: root.workspaceImplicitWidth - implicitHeight: root.workspaceImplicitHeight - color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor - radius: Theme.cornerRadius - border.width: 2 - border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : "transparent") - - StyledText { - anchors.centerIn: parent - text: workspaceValue - font.pixelSize: Theme.fontSizeXLarge * 6 - font.weight: Font.DemiBold - color: Theme.withAlpha(Theme.surfaceText, workspaceExists ? 0.2 : 0.1) - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - MouseArea { - id: workspaceArea - anchors.fill: parent - acceptedButtons: Qt.LeftButton - onClicked: { - if (root.draggingTargetWorkspace === -1) { - root.overviewOpen = false - Hyprland.dispatch(`workspace ${workspaceValue}`) - } - } - } - - DropArea { - anchors.fill: parent - onEntered: { - root.draggingTargetWorkspace = workspaceValue - if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return - hoveredWhileDragging = true - } - onExited: { - hoveredWhileDragging = false - if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 - } - } - } - } - } - } - } - - Item { - id: windowSpace - anchors.centerIn: parent - implicitWidth: workspaceColumnLayout.implicitWidth - implicitHeight: workspaceColumnLayout.implicitHeight - - Repeater { - model: ScriptModel { - values: { - const workspaces = root.allWorkspaces - const minId = root.minWorkspaceId - const maxId = root.maxWorkspaceId - - if (!workspaces || workspaces.length === 0) return [] - - try { - const result = [] - for (const workspace of workspaces) { - const wsId = workspace?.id ?? -1 - if (wsId >= minId && wsId <= maxId) { - const toplevels = workspace?.toplevels?.values || [] - for (const toplevel of toplevels) { - result.push(toplevel) - } - } - } - return result - } catch (e) { - console.error("OverviewWidget filter error:", e) - return [] - } - } - } - delegate: OverviewWindow { - id: window - required property var modelData - - overviewOpen: root.overviewOpen - readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1 - - function getWorkspaceIndex() { - if (!root.displayedWorkspaceIds || root.displayedWorkspaceIds.length === 0) return 0 - if (!windowWorkspaceId || windowWorkspaceId < 0) return 0 - try { - for (let i = 0; i < root.displayedWorkspaceIds.length; i++) { - if (root.displayedWorkspaceIds[i] === windowWorkspaceId) { - return i - } - } - return 0 - } catch (e) { - return 0 - } - } - - readonly property int workspaceIndex: getWorkspaceIndex() - readonly property int workspaceColIndex: workspaceIndex % SettingsData.overviewColumns - readonly property int workspaceRowIndex: Math.floor(workspaceIndex / SettingsData.overviewColumns) - - toplevel: modelData - scale: root.scale - availableWorkspaceWidth: root.workspaceImplicitWidth - availableWorkspaceHeight: root.workspaceImplicitHeight - widgetMonitorId: root.monitor.id - - xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - - z: atInitPosition ? root.windowZ : root.windowDraggingZ - property bool atInitPosition: (initX == x && initY == y) - - Drag.hotSpot.x: width / 2 - Drag.hotSpot.y: height / 2 - - MouseArea { - id: dragArea - anchors.fill: parent - hoverEnabled: true - onEntered: window.hovered = true - onExited: window.hovered = false - acceptedButtons: Qt.LeftButton | Qt.MiddleButton - drag.target: parent - - onPressed: (mouse) => { - root.draggingFromWorkspace = windowData?.workspace.id - window.pressed = true - window.Drag.active = true - window.Drag.source = window - window.Drag.hotSpot.x = mouse.x - window.Drag.hotSpot.y = mouse.y - } - - onReleased: { - const targetWorkspace = root.draggingTargetWorkspace - window.pressed = false - window.Drag.active = false - root.draggingFromWorkspace = -1 - root.draggingTargetWorkspace = -1 - - if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { - Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace},address:${windowData?.address}`) - Qt.callLater(() => { - Hyprland.refreshToplevels() - Hyprland.refreshWorkspaces() - Qt.callLater(() => { - window.x = window.initX - window.y = window.initY - }) - }) - } else { - window.x = window.initX - window.y = window.initY - } - } - - onClicked: (event) => { - if (!windowData) return - - if (event.button === Qt.LeftButton) { - root.overviewOpen = false - Hyprland.dispatch(`focuswindow address:${windowData.address}`) - event.accepted = true - } else if (event.button === Qt.MiddleButton) { - Hyprland.dispatch(`closewindow address:${windowData.address}`) - event.accepted = true - } - } - } - } - } - } - - Item { - id: monitorLabelSpace - anchors.centerIn: parent - implicitWidth: workspaceColumnLayout.implicitWidth - implicitHeight: workspaceColumnLayout.implicitHeight - z: root.monitorLabelZ - - Repeater { - model: SettingsData.overviewRows - delegate: Item { - id: labelRow - property int rowIndex: index - y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex - width: parent.width - height: root.workspaceImplicitHeight - - Repeater { - model: SettingsData.overviewColumns - delegate: Item { - id: labelItem - property int colIndex: index - property int workspaceIndex: labelRow.rowIndex * SettingsData.overviewColumns + colIndex - property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1 - property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false - property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : "" - - x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex - width: root.workspaceImplicitWidth - height: root.workspaceImplicitHeight - - Rectangle { - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: Theme.spacingS - width: monitorNameText.contentWidth + Theme.spacingS * 2 - height: monitorNameText.contentHeight + Theme.spacingXS * 2 - radius: Theme.cornerRadius - color: Theme.surface - visible: labelItem.workspaceExists && labelItem.workspaceMonitorName !== "" - - StyledText { - id: monitorNameText - anchors.centerIn: parent - text: labelItem.workspaceMonitorName - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.Medium - color: Theme.surfaceText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - } - } - } - } - } - } -} diff --git a/Modules/HyprWorkspaces/OverviewWindow.qml b/Modules/HyprWorkspaces/OverviewWindow.qml deleted file mode 100644 index b3de9861..00000000 --- a/Modules/HyprWorkspaces/OverviewWindow.qml +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick -import QtQuick.Effects -import QtQuick.Layouts -import Quickshell -import Quickshell.Wayland -import qs.Common - -Item { - id: root - property var toplevel - property var scale - required property bool overviewOpen - property var availableWorkspaceWidth - property var availableWorkspaceHeight - property bool restrictToWorkspace: true - - readonly property var windowData: toplevel?.lastIpcObject || null - readonly property var monitorObj: toplevel?.monitor - readonly property var monitorData: monitorObj?.lastIpcObject || null - - property real initX: Math.max(((windowData?.at?.[0] ?? 0) - (monitorData?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset - property real initY: Math.max(((windowData?.at?.[1] ?? 0) - (monitorData?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset - property real xOffset: 0 - property real yOffset: 0 - property int widgetMonitorId: 0 - - property var targetWindowWidth: (windowData?.size?.[0] ?? 100) * scale - property var targetWindowHeight: (windowData?.size?.[1] ?? 100) * scale - property bool hovered: false - property bool pressed: false - - property var iconToWindowRatio: 0.25 - property var iconToWindowRatioCompact: 0.45 - property var entry: DesktopEntries.heuristicLookup(windowData?.class) - property var iconPath: Quickshell.iconPath(entry?.icon ?? windowData?.class ?? "application-x-executable", "image-missing") - property bool compactMode: Theme.fontSizeSmall * 4 > targetWindowHeight || Theme.fontSizeSmall * 4 > targetWindowWidth - - x: initX - y: initY - width: Math.min((windowData?.size?.[0] ?? 100) * root.scale, availableWorkspaceWidth) - height: Math.min((windowData?.size?.[1] ?? 100) * root.scale, availableWorkspaceHeight) - opacity: (monitorObj?.id ?? -1) == widgetMonitorId ? 1 : 0.4 - - Rectangle { - id: maskRect - width: root.width - height: root.height - radius: Theme.cornerRadius - visible: false - layer.enabled: true - } - - layer.enabled: true - layer.effect: MultiEffect { - maskEnabled: true - maskSource: maskRect - maskSpreadAtMin: 1 - maskThresholdMin: 0.5 - } - - Behavior on x { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel - } - } - Behavior on y { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel - } - } - Behavior on width { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel - } - } - Behavior on height { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel - } - } - - ScreencopyView { - id: windowPreview - anchors.fill: parent - captureSource: root.overviewOpen ? root.toplevel?.wayland : null - live: true - - Rectangle { - anchors.fill: parent - radius: Theme.cornerRadius - color: pressed ? Theme.withAlpha(Theme.surfaceContainerHigh, 0.5) : - hovered ? Theme.withAlpha(Theme.surfaceVariant, 0.3) : - Theme.withAlpha(Theme.surfaceContainer, 0.1) - border.color: Theme.withAlpha(Theme.outline, 0.3) - border.width: 1 - } - - ColumnLayout { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.right: parent.right - spacing: Theme.fontSizeSmall * 0.5 - - Image { - id: windowIcon - property var iconSize: { - return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1) - } - Layout.alignment: Qt.AlignHCenter - source: root.iconPath - width: iconSize - height: iconSize - sourceSize: Qt.size(iconSize, iconSize) - - Behavior on width { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel - } - } - Behavior on height { - NumberAnimation { - duration: Theme.expressiveDurations.expressiveDefaultSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel - } - } - } - } - } -} diff --git a/Modules/Notifications/Center/NotificationCard.qml b/Modules/Notifications/Center/NotificationCard.qml index 70edf3e7..dac71d05 100644 --- a/Modules/Notifications/Center/NotificationCard.qml +++ b/Modules/Notifications/Center/NotificationCard.qml @@ -537,7 +537,7 @@ Rectangle { StyledText { id: clearText - text: I18n.tr("Dismiss") + text: I18n.tr("Clear") color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText font.pixelSize: Theme.fontSizeSmall font.weight: Font.Medium @@ -630,7 +630,7 @@ Rectangle { StyledText { id: clearText - text: I18n.tr("Dismiss") + text: I18n.tr("Clear") color: clearButton.isHovered ? Theme.primary : Theme.surfaceVariantText font.pixelSize: Theme.fontSizeSmall font.weight: Font.Medium diff --git a/Modules/Notifications/Center/NotificationHeader.qml b/Modules/Notifications/Center/NotificationHeader.qml index 7a6ae535..d3187676 100644 --- a/Modules/Notifications/Center/NotificationHeader.qml +++ b/Modules/Notifications/Center/NotificationHeader.qml @@ -99,7 +99,7 @@ Item { } StyledText { - text: I18n.tr("Clear") + text: I18n.tr("Clear All") font.pixelSize: Theme.fontSizeSmall color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText font.weight: Font.Medium diff --git a/Modules/Notifications/Popup/NotificationPopup.qml b/Modules/Notifications/Popup/NotificationPopup.qml index f5ff70b2..3146f4a2 100644 --- a/Modules/Notifications/Popup/NotificationPopup.qml +++ b/Modules/Notifications/Popup/NotificationPopup.qml @@ -21,7 +21,7 @@ PanelWindow { property bool exiting: false property bool _isDestroying: false property bool _finalized: false - readonly property string clearText: I18n.tr("Dismiss") + readonly property string clearText: I18n.tr("Clear") signal entered signal exitFinished @@ -512,7 +512,7 @@ PanelWindow { anchors.fill: parent hoverEnabled: true - acceptedButtons: Qt.LeftButton | Qt.RightButton + acceptedButtons: Qt.LeftButton propagateComposedEvents: true z: -1 onEntered: { @@ -523,20 +523,9 @@ PanelWindow { if (notificationData && notificationData.popup && notificationData.timer) notificationData.timer.restart() } - onClicked: (mouse) => { - if (!notificationData || win.exiting) - return - - if (mouse.button === Qt.RightButton) { - NotificationService.dismissNotification(notificationData) - } else if (mouse.button === Qt.LeftButton) { - if (notificationData.actions && notificationData.actions.length > 0) { - notificationData.actions[0].invoke() - NotificationService.dismissNotification(notificationData) - } else { - notificationData.popup = false - } - } + onClicked: { + if (notificationData && !win.exiting) + notificationData.popup = false } } } diff --git a/PLUGINS/LauncherExample/LauncherExampleLauncher.qml b/PLUGINS/LauncherExample/LauncherExampleLauncher.qml index 39ba58d0..67964d2c 100644 --- a/PLUGINS/LauncherExample/LauncherExampleLauncher.qml +++ b/PLUGINS/LauncherExample/LauncherExampleLauncher.qml @@ -26,35 +26,35 @@ Item { const baseItems = [ { name: "Test Item 1", - icon: "material:lightbulb", + icon: "lightbulb", comment: "This is a test item that shows a toast notification", action: "toast:Test Item 1 executed!", categories: ["LauncherExample"] }, { - name: "Test Item 2", - icon: "material:star", + name: "Test Item 2", + icon: "star", comment: "Another test item with different action", action: "toast:Test Item 2 clicked!", categories: ["LauncherExample"] }, { name: "Test Item 3", - icon: "material:favorite", + icon: "favorite", comment: "Third test item for demonstration", action: "toast:Test Item 3 activated!", categories: ["LauncherExample"] }, { name: "Example Copy Action", - icon: "material:content_copy", + icon: "content_copy", comment: "Demonstrates copying text to clipboard", action: "copy:This text was copied by the launcher plugin!", categories: ["LauncherExample"] }, { name: "Example Script Action", - icon: "material:terminal", + icon: "terminal", comment: "Demonstrates running a simple command", action: "script:echo 'Hello from launcher plugin!'", categories: ["LauncherExample"] diff --git a/PLUGINS/LauncherExample/README.md b/PLUGINS/LauncherExample/README.md index d752d0ad..ff744d45 100644 --- a/PLUGINS/LauncherExample/README.md +++ b/PLUGINS/LauncherExample/README.md @@ -88,41 +88,13 @@ function executeItem(item): void ```javascript { name: "Item Name", // Display name - icon: "icon_name", // Icon (optional, see Icon Types below) + icon: "icon_name", // Material icon comment: "Description", // Subtitle text action: "type:data", // Action to execute categories: ["PluginName"] // Category array } ``` -**Icon Types**: - -The `icon` field supports three formats: - -1. **Material Design Icons** - Use `material:` prefix: - ```javascript - icon: "material:lightbulb" // Material Symbols Rounded font - ``` - Examples: `material:star`, `material:favorite`, `material:settings` - -2. **Desktop Theme Icons** - Use icon name directly: - ```javascript - icon: "firefox" // Uses system icon theme - ``` - Examples: `firefox`, `chrome`, `folder`, `text-editor` - -3. **No Icon** - Omit the `icon` field entirely: - ```javascript - { - name: "😀 Grinning Face", - // No icon field - comment: "Copy emoji", - action: "copy:😀", - categories: ["MyPlugin"] - } - ``` - Perfect for emoji pickers or text-only items where the icon area should be hidden - **Action Format**: `type:data` where: - `type` - Action handler (toast, copy, script, etc.) - `data` - Action-specific data diff --git a/PLUGINS/README.md b/PLUGINS/README.md index 99ae3d6d..c3f9e575 100644 --- a/PLUGINS/README.md +++ b/PLUGINS/README.md @@ -1221,51 +1221,11 @@ Item { Each item returned by `getItems()` must include: - `name` (string): Display name shown in launcher -- `icon` (string, optional): Icon specification (see Icon Types below) +- `icon` (string): Material Design icon name - `comment` (string): Description/subtitle text - `action` (string): Action identifier in `type:data` format - `categories` (array): Array containing your plugin name -### Icon Types - -The `icon` field supports three formats: - -**1. Material Design Icons** - Use the `material:` prefix: -```javascript -{ - name: "My Item", - icon: "material:lightbulb", // Material Symbols Rounded font - comment: "Uses Material Design icon", - action: "toast:Hello!", - categories: ["MyPlugin"] -} -``` -Available icons: Any icon from Material Symbols font (e.g., `lightbulb`, `star`, `favorite`, `settings`, `terminal`, `translate`, `sentiment_satisfied`) - -**2. Desktop Theme Icons** - Use icon name directly: -```javascript -{ - name: "Firefox", - icon: "firefox", // Uses system icon theme - comment: "Launches Firefox browser", - action: "exec:firefox", - categories: ["MyPlugin"] -} -``` -Uses the user's installed icon theme. Common examples: `firefox`, `chrome`, `folder`, `text-editor` - -**3. No Icon** - Omit the `icon` field entirely: -```javascript -{ - name: "😀 Grinning Face", - // No icon field - emoji/unicode in name displays without icon area - comment: "Copy emoji to clipboard", - action: "copy:😀", - categories: ["MyPlugin"] -} -``` -When `icon` is omitted, the launcher hides the icon area and displays only the text, giving full width to the item name. Perfect for emoji pickers or text-only items. - ### Trigger System Triggers control when your plugin's items appear in the launcher: diff --git a/README.md b/README.md index af898d66..ee198721 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ There are a lot of possible configurations that you can enable/disable in the fl #### Other Distributions - via manual installation -#### 1. Install Quickshell (Varies by Distribution) +**1. Install Quickshell (Varies by Distribution)** ```bash # Arch paru -S quickshell-git @@ -267,61 +267,43 @@ sudo dnf copr enable avengemedia/danklinux && sudo dnf install quickshell-git # ! TODO - document other distros ``` -#### 2. Install fonts +**2. Install fonts** *Inter Variable* and *Fira Code* are not strictly required, but they are the default fonts of dms. -#### 2.1 Install Material Symbols +**2.1 Install Material Symbols** ```bash sudo curl -L "https://github.com/google/material-design-icons/raw/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.ttf" -o /usr/share/fonts/MaterialSymbolsRounded.ttf ``` -#### 2.2 Install Inter Variable +**2.2 Install Inter Variable** ```bash sudo curl -L "https://github.com/rsms/inter/raw/refs/tags/v4.1/docs/font-files/InterVariable.ttf" -o /usr/share/fonts/InterVariable.ttf ``` -#### 2.3 Install Fira Code (monospace font) +**2.3 Install Fira Code (monospace font)** ```bash sudo curl -L "https://github.com/tonsky/FiraCode/releases/latest/download/FiraCode-Regular.ttf" -o /usr/share/fonts/FiraCode-Regular.ttf ``` -#### 2.4 Refresh font cache +**2.4 Refresh font cache** ```bash fc-cache -fv ``` -#### 3. Install the shell - -#### 3.1. Clone latest QML +**3. Install the shell** +**3.1. Clone latest master** ```bash mkdir ~/.config/quickshell && git clone https://github.com/AvengeMedia/DankMaterialShell.git ~/.config/quickshell/dms ``` -**FOR Stable Version, Checkout the latest tag** - -```bash -cd ~/.config/quickshell/dms -# You'll have to re-run this, to update to the latest version. -git checkout $(git describe --tags --abbrev=0) -``` - -#### 3.2. Install latest dms CLI - +**3.2. Install latest dms CLI** ```bash sudo sh -c "curl -L https://github.com/AvengeMedia/danklinux/releases/latest/download/dms-$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').gz | gunzip | tee /usr/local/bin/dms > /dev/null && chmod +x /usr/local/bin/dms" ``` -**Note:** this is the latest *stable* dms CLI. If you are using QML/master (not pinned to a tag), then you may periodically be missing features, etc. -If preferred, you can build the dms-cli yourself (requires GO 1.24+) +**4. Optional Features (system monitoring, clipboard history, brightness controls, etc.)** -```bash -git clone https://github.com/AvengeMedia/danklinux.git && cd danklinux -make && sudo make install -``` - -#### 4. Optional Features (system monitoring, clipboard history, brightness controls, etc.) - -#### 4.1 Core optional dependencies +**4.1 Core optional dependencies** ```bash # Arch Linux sudo pacman -S cava wl-clipboard cliphist brightnessctl qt6-multimedia @@ -335,7 +317,7 @@ Note: by enabling and installing the avengemedia/dms copr above, these core depe *Other distros will just need to find sources for the above packages* -#### 4.2 - dgop manual installation +**4.2 - dgop manual installation** `dgop` is available via AUR and a nix flake, other distributions can install it manually. @@ -411,6 +393,9 @@ binds { Mod+C hotkey-overlay-title="Control Center" { spawn "dms" "ipc" "call" "control-center" "toggle"; } + Mod+Y hotkey-overlay-title="Browse Wallpapers" { + spawn "dms" "ipc" "call" "dankdash" "wallpaper"; + } XF86AudioRaiseVolume allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "increment" "3"; } @@ -478,8 +463,8 @@ bind = SUPER, comma, exec, dms ipc call settings toggle bind = SUPER, P, exec, dms ipc call notepad toggle bind = SUPERALT, L, exec, dms ipc call lock lock bind = SUPER, X, exec, dms ipc call powermenu toggle -bind = SUPER, C, exec, dms ipc call control-center toggle -bind = SUPER, TAB, exec, dms ipc call hypr toggleOverview +bind = SUPER, C, exec, dms ipc call control-center toggle +bind = SUPER, Y, exec, dms ipc call dankdash wallpaper # Audio controls (function keys) bindl = , XF86AudioRaiseVolume, exec, dms ipc call audio increment 3 diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml index 72433d83..27851c7f 100644 --- a/Services/CompositorService.qml +++ b/Services/CompositorService.qml @@ -1,4 +1,5 @@ pragma Singleton + pragma ComponentBehavior: Bound import QtQuick @@ -16,300 +17,67 @@ Singleton { readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE") readonly property string niriSocket: Quickshell.env("NIRI_SOCKET") + property bool useNiriSorting: isNiri && NiriService - property var sortedToplevels: sortedToplevelsCache - property var sortedToplevelsCache: [] - - property bool _sortScheduled: false - property bool _refreshScheduled: false - property bool _hasRefreshedOnce: false - - property var _coordCache: ({}) - - Timer { - id: refreshTimer - interval: 40 - repeat: false - onTriggered: { - try { - Hyprland.refreshToplevels() - } catch(e) {} - _refreshScheduled = false - _hasRefreshedOnce = true - scheduleSort() - } - } - - function scheduleSort() { - if (_sortScheduled) return - _sortScheduled = true - Qt.callLater(function() { - _sortScheduled = false - sortedToplevelsCache = computeSortedToplevels() - }) - } - - function scheduleRefresh() { - if (!isHyprland) return - if (_refreshScheduled) return - _refreshScheduled = true - refreshTimer.restart() - } - - Connections { - target: ToplevelManager.toplevels - function onValuesChanged() { root.scheduleSort() } - } - Connections { - target: Hyprland.toplevels - function onValuesChanged() { - root._hasRefreshedOnce = false - root.scheduleSort() - } - } - Connections { - target: Hyprland.workspaces - function onValuesChanged() { root.scheduleSort() } - } - Connections { - target: Hyprland - function onFocusedWorkspaceChanged() { root.scheduleSort() } - } - - Component.onCompleted: { - detectCompositor() - scheduleSort() - Qt.callLater(() => NiriService.generateNiriLayoutConfig()) - } - - function computeSortedToplevels() { - if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) + property var sortedToplevels: { + if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) { return [] + } - if (useNiriSorting) + if (useNiriSorting) { return NiriService.sortToplevels(ToplevelManager.toplevels.values) - - if (isHyprland) - return sortHyprlandToplevelsSafe() - - return Array.from(ToplevelManager.toplevels.values) - } - - function _get(o, path, fallback) { - try { - let v = o - for (let i = 0; i < path.length; i++) { - if (v === null || v === undefined) return fallback - v = v[path[i]] - } - return (v === undefined || v === null) ? fallback : v - } catch (e) { return fallback } - } - - function sortHyprlandToplevelsSafe() { - if (!Hyprland.toplevels || !Hyprland.toplevels.values) return [] - if (_refreshScheduled) return sortedToplevelsCache - - const items = Array.from(Hyprland.toplevels.values) - - function _get(o, path, fb) { - try { - let v = o - for (let k of path) { if (v == null) return fb; v = v[k] } - return (v == null) ? fb : v - } catch(e) { return fb } } - let snap = [] - let missingAnyPosition = false - let hasNewWindow = false - for (let i = 0; i < items.length; i++) { - const t = items[i] - if (!t) continue + if (isHyprland) { + const hyprlandToplevels = Array.from(Hyprland.toplevels.values) - const addr = t.address || "" - const li = t.lastIpcObject || null + const sortedHyprland = hyprlandToplevels.sort((a, b) => { + if (a.monitor && b.monitor) { + const monitorCompare = a.monitor.name.localeCompare(b.monitor.name) + if (monitorCompare !== 0) { + return monitorCompare + } + } - const monName = _get(li, ["monitor"], null) ?? _get(t, ["monitor", "name"], "") - const monX = _get(t, ["monitor", "x"], Number.MAX_SAFE_INTEGER) - const monY = _get(t, ["monitor", "y"], Number.MAX_SAFE_INTEGER) + if (a.workspace && b.workspace) { + const workspaceCompare = a.workspace.id - b.workspace.id + if (workspaceCompare !== 0) { + return workspaceCompare + } + } - const wsId = _get(li, ["workspace", "id"], null) ?? _get(t, ["workspace", "id"], Number.MAX_SAFE_INTEGER) + if (a.lastIpcObject && b.lastIpcObject && a.lastIpcObject.at && b.lastIpcObject.at) { + const aX = a.lastIpcObject.at[0] + const bX = b.lastIpcObject.at[0] + const aY = a.lastIpcObject.at[1] + const bY = b.lastIpcObject.at[1] - const at = _get(li, ["at"], null) - let atX = (at !== null && at !== undefined && typeof at[0] === "number") ? at[0] : NaN - let atY = (at !== null && at !== undefined && typeof at[1] === "number") ? at[1] : NaN + const xCompare = aX - bX + if (Math.abs(xCompare) > 10) { + return xCompare + } + return aY - bY + } - if (!(atX === atX) || !(atY === atY)) { - const cached = _coordCache[addr] - if (cached) { - atX = cached.x - atY = cached.y - } else { - if (addr) hasNewWindow = true - missingAnyPosition = true - atX = 1e9 - atY = 1e9 - } - } else if (addr) { - _coordCache[addr] = { x: atX, y: atY } - } + if (a.lastIpcObject && !b.lastIpcObject) { + return -1 + } + if (!a.lastIpcObject && b.lastIpcObject) { + return 1 + } - const relX = Number.isFinite(monX) ? (atX - monX) : atX - const relY = Number.isFinite(monY) ? (atY - monY) : atY + if (a.title && b.title) { + return a.title.localeCompare(b.title) + } - snap.push({ - monKey: String(monName), - monOrderX: Number.isFinite(monX) ? monX : Number.MAX_SAFE_INTEGER, - monOrderY: Number.isFinite(monY) ? monY : Number.MAX_SAFE_INTEGER, - wsId: (typeof wsId === "number") ? wsId : Number.MAX_SAFE_INTEGER, - x: relX, - y: relY, - title: t.title || "", - address: addr, - wayland: t.wayland - }) + return 0 + }) + + return sortedHyprland.map(hyprToplevel => hyprToplevel.wayland).filter(wayland => wayland !== null) } - if (missingAnyPosition && hasNewWindow) { - _hasRefreshedOnce = false - scheduleRefresh() - } - - const groups = new Map() - for (const it of snap) { - const key = it.monKey + "::" + it.wsId - if (!groups.has(key)) groups.set(key, []) - groups.get(key).push(it) - } - - let groupList = [] - for (const [key, arr] of groups) { - const repr = arr[0] - groupList.push({ - key, - monKey: repr.monKey, - monOrderX: repr.monOrderX, - monOrderY: repr.monOrderY, - wsId: repr.wsId, - items: arr - }) - } - - groupList.sort((a, b) => { - if (a.monOrderX !== b.monOrderX) return a.monOrderX - b.monOrderX - if (a.monOrderY !== b.monOrderY) return a.monOrderY - b.monOrderY - if (a.monKey !== b.monKey) return a.monKey.localeCompare(b.monKey) - if (a.wsId !== b.wsId) return a.wsId - b.wsId - return 0 - }) - - const COLUMN_THRESHOLD = 48 - const JITTER_Y = 6 - - let ordered = [] - for (const g of groupList) { - const arr = g.items - - const xs = arr.map(it => it.x).filter(x => Number.isFinite(x)).sort((a, b) => a - b) - let colCenters = [] - if (xs.length > 0) { - for (const x of xs) { - if (colCenters.length === 0) { - colCenters.push(x) - } else { - const last = colCenters[colCenters.length - 1] - if (x - last >= COLUMN_THRESHOLD) { - colCenters.push(x) - } - } - } - } else { - colCenters = [0] - } - - for (const it of arr) { - let bestCol = 0 - let bestDist = Number.POSITIVE_INFINITY - for (let ci = 0; ci < colCenters.length; ci++) { - const d = Math.abs(it.x - colCenters[ci]) - if (d < bestDist) { - bestDist = d - bestCol = ci - } - } - it._col = bestCol - } - - arr.sort((a, b) => { - if (a._col !== b._col) return a._col - b._col - - const dy = a.y - b.y - if (Math.abs(dy) > JITTER_Y) return dy - - if (a.title !== b.title) return a.title.localeCompare(b.title) - if (a.address !== b.address) return a.address.localeCompare(b.address) - return 0 - }) - - ordered.push.apply(ordered, arr) - } - - return ordered.map(x => x.wayland).filter(w => w !== null && w !== undefined) - } - - function filterCurrentWorkspace(toplevels, screen) { - if (useNiriSorting) return NiriService.filterCurrentWorkspace(toplevels, screen) - if (isHyprland) return filterHyprlandCurrentWorkspaceSafe(toplevels, screen) - return toplevels - } - - function filterHyprlandCurrentWorkspaceSafe(toplevels, screenName) { - if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels) return toplevels - - let currentWorkspaceId = null - try { - const hy = Array.from(Hyprland.toplevels.values) - for (const t of hy) { - const mon = _get(t, ["monitor", "name"], "") - const wsId = _get(t, ["workspace", "id"], null) - const active = !!_get(t, ["activated"], false) - if (mon === screenName && wsId !== null) { - if (active) { currentWorkspaceId = wsId; break } - if (currentWorkspaceId === null) currentWorkspaceId = wsId - } - } - - if (currentWorkspaceId === null && Hyprland.workspaces) { - const wss = Array.from(Hyprland.workspaces.values) - const focusedId = _get(Hyprland, ["focusedWorkspace", "id"], null) - for (const ws of wss) { - const monName = _get(ws, ["monitor"], "") - const wsId = _get(ws, ["id"], null) - if (monName === screenName && wsId !== null) { - if (focusedId !== null && wsId === focusedId) { currentWorkspaceId = wsId; break } - if (currentWorkspaceId === null) currentWorkspaceId = wsId - } - } - } - } catch (e) { - console.warn("CompositorService: workspace snapshot failed:", e) - } - - if (currentWorkspaceId === null) return toplevels - - // Map wayland → wsId snapshot - let map = new Map() - try { - const hy = Array.from(Hyprland.toplevels.values) - for (const t of hy) { - const wsId = _get(t, ["workspace", "id"], null) - if (t && t.wayland && wsId !== null) map.set(t.wayland, wsId) - } - } catch (e) {} - - return toplevels.filter(w => map.get(w) === currentWorkspaceId) + return ToplevelManager.toplevels.values } Timer { @@ -323,30 +91,86 @@ Singleton { } } + function filterCurrentWorkspace(toplevels, screen) { + if (useNiriSorting) { + return NiriService.filterCurrentWorkspace(toplevels, screen) + } + if (isHyprland) { + return filterHyprlandCurrentWorkspace(toplevels, screen) + } + return toplevels + } + + function filterHyprlandCurrentWorkspace(toplevels, screenName) { + if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels) { + return toplevels + } + + let currentWorkspaceId = null + const hyprlandToplevels = Array.from(Hyprland.toplevels.values) + + for (const hyprToplevel of hyprlandToplevels) { + if (hyprToplevel.monitor && hyprToplevel.monitor.name === screenName && hyprToplevel.workspace) { + if (hyprToplevel.activated) { + currentWorkspaceId = hyprToplevel.workspace.id + break + } + if (currentWorkspaceId === null) { + currentWorkspaceId = hyprToplevel.workspace.id + } + } + } + + if (currentWorkspaceId === null && Hyprland.workspaces) { + const workspaces = Array.from(Hyprland.workspaces.values) + for (const workspace of workspaces) { + if (workspace.monitor && workspace.monitor === screenName) { + if (Hyprland.focusedWorkspace && workspace.id === Hyprland.focusedWorkspace.id) { + currentWorkspaceId = workspace.id + break + } + if (currentWorkspaceId === null) { + currentWorkspaceId = workspace.id + } + } + } + } + + if (currentWorkspaceId === null) { + return toplevels + } + + return toplevels.filter(toplevel => { + for (const hyprToplevel of hyprlandToplevels) { + if (hyprToplevel.wayland === toplevel) { + return hyprToplevel.workspace && hyprToplevel.workspace.id === currentWorkspaceId + } + } + return false + }) + } + function detectCompositor() { if (hyprlandSignature && hyprlandSignature.length > 0) { isHyprland = true isNiri = false compositor = "hyprland" console.log("CompositorService: Detected Hyprland") - try { - Hyprland.refreshToplevels() - } catch(e) {} return } if (niriSocket && niriSocket.length > 0) { - Proc.runCommand("niriSocketCheck", ["test", "-S", niriSocket], (output, exitCode) => { + Proc.runCommand("niriSocketCheck", ["test", "-S", root.niriSocket], (output, exitCode) => { if (exitCode === 0) { - isNiri = true - isHyprland = false - compositor = "niri" - console.log("CompositorService: Detected Niri with socket:", niriSocket) + root.isNiri = true + root.isHyprland = false + root.compositor = "niri" + console.log("CompositorService: Detected Niri with socket:", root.niriSocket) NiriService.generateNiriBinds() } else { - isHyprland = false - isNiri = true - compositor = "niri" + root.isHyprland = false + root.isNiri = true + root.compositor = "niri" console.warn("CompositorService: Niri socket check failed, defaulting to Niri anyway") } }, 0) @@ -359,14 +183,22 @@ Singleton { } function powerOffMonitors() { - if (isNiri) return NiriService.powerOffMonitors() - if (isHyprland) return Hyprland.dispatch("dpms off") + if (isNiri) { + return NiriService.powerOffMonitors() + } + if (isHyprland) { + return Hyprland.dispatch("dpms off") + } console.warn("CompositorService: Cannot power off monitors, unknown compositor") } function powerOnMonitors() { - if (isNiri) return NiriService.powerOnMonitors() - if (isHyprland) return Hyprland.dispatch("dpms on") + if (isNiri) { + return NiriService.powerOnMonitors() + } + if (isHyprland) { + return Hyprland.dispatch("dpms on") + } console.warn("CompositorService: Cannot power on monitors, unknown compositor") } -} +} \ No newline at end of file diff --git a/Services/DMSService.qml b/Services/DMSService.qml index 6e805445..559f883e 100644 --- a/Services/DMSService.qml +++ b/Services/DMSService.qml @@ -41,7 +41,6 @@ Singleton { signal loginctlStateUpdate(var data) signal loginctlEvent(var event) signal capabilitiesReceived() - signal credentialsRequest(var data) Component.onCompleted: { if (socketPath && socketPath.length > 0) { @@ -262,8 +261,6 @@ Singleton { capabilitiesReceived() } else if (service === "network") { networkStateUpdate(data) - } else if (service === "network.credentials") { - credentialsRequest(data) } else if (service === "loginctl") { if (data.event) { loginctlEvent(data) diff --git a/Services/NetworkManagerService.qml b/Services/NetworkManagerService.qml index 6589a7d4..6cb6d995 100644 --- a/Services/NetworkManagerService.qml +++ b/Services/NetworkManagerService.qml @@ -79,21 +79,8 @@ Singleton { property int refCount: 0 property bool stateInitialized: false - property string credentialsToken: "" - property string credentialsSSID: "" - property string credentialsSetting: "" - property var credentialsFields: [] - property var credentialsHints: [] - property string credentialsReason: "" - property bool credentialsRequested: false - - property string pendingConnectionSSID: "" - property var pendingConnectionStartTime: 0 - property bool wasConnecting: false - signal networksUpdated signal connectionChanged - signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason) readonly property string socketPath: Quickshell.env("DMS_SOCKET") @@ -133,10 +120,6 @@ Singleton { function onCapabilitiesChanged() { checkDMSCapabilities() } - - function onCredentialsRequest(data) { - handleCredentialsRequest(data) - } } function checkDMSCapabilities() { @@ -160,18 +143,6 @@ Singleton { } } - function handleCredentialsRequest(data) { - credentialsToken = data.token || "" - credentialsSSID = data.ssid || "" - credentialsSetting = data.setting || "802-11-wireless-security" - credentialsFields = data.fields || ["psk"] - credentialsHints = data.hints || [] - credentialsReason = data.reason || "Credentials required" - credentialsRequested = true - - credentialsNeeded(credentialsToken, credentialsSSID, credentialsSetting, credentialsFields, credentialsHints, credentialsReason) - } - function addRef() { refCount++ if (refCount === 1 && networkAvailable) { @@ -206,9 +177,6 @@ Singleton { } function updateState(state) { - const previousConnecting = isConnecting - const previousConnectingSSID = connectingSSID - networkStatus = state.networkStatus || "disconnected" primaryConnection = state.primaryConnection || "" @@ -257,45 +225,6 @@ Singleton { connectionError = state.lastError || "" lastConnectionError = state.lastError || "" - if (pendingConnectionSSID) { - if (wifiConnected && currentWifiSSID === pendingConnectionSSID && wifiIP) { - if (DMSService.verboseLogs) { - const elapsed = Date.now() - pendingConnectionStartTime - console.log("NetworkManagerService: Successfully connected to", pendingConnectionSSID, "in", elapsed, "ms") - } - ToastService.showInfo(`Connected to ${pendingConnectionSSID}`) - - if (userPreference === "wifi" || userPreference === "auto") { - setConnectionPriority("wifi") - } - - pendingConnectionSSID = "" - connectionStatus = "connected" - } else if (previousConnecting && !isConnecting && !wifiConnected) { - const elapsed = Date.now() - pendingConnectionStartTime - - if (elapsed < 5000) { - if (DMSService.verboseLogs) { - console.log("NetworkManagerService: Quick connection failure, likely authentication error") - } - connectionStatus = "invalid_password" - } else { - if (DMSService.verboseLogs) { - console.log("NetworkManagerService: Connection failed for", pendingConnectionSSID) - } - if (connectionError === "connection-failed") { - ToastService.showError(I18n.tr("Connection failed. Check password and try again.")) - } else if (connectionError) { - ToastService.showError(I18n.tr("Failed to connect to ") + pendingConnectionSSID) - } - connectionStatus = "failed" - pendingConnectionSSID = "" - } - } - } - - wasConnecting = isConnecting - connectionChanged() } @@ -313,11 +242,11 @@ Singleton { connectionError = response.error lastConnectionError = response.error connectionStatus = "failed" - ToastService.showError(I18n.tr("Failed to activate configuration")) + ToastService.showError(`Failed to activate configuration`) } else { connectionError = "" connectionStatus = "connected" - ToastService.showInfo(I18n.tr("Configuration activated")) + ToastService.showInfo(`Configuration activated`) } isConnecting = false @@ -351,47 +280,42 @@ Singleton { function connectToWifi(ssid, password = "", username = "", anonymousIdentity = "", domainSuffixMatch = "") { if (!networkAvailable || isConnecting) return - pendingConnectionSSID = ssid - pendingConnectionStartTime = Date.now() + connectingSSID = ssid connectionError = "" connectionStatus = "connecting" - credentialsRequested = false const params = { ssid: ssid } - - if (DMSService.apiVersion >= 7) { - if (password || username) { - params.password = password - if (username) params.username = username - if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity - if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch - params.interactive = false - } else { - params.interactive = true - } - } else { - if (password) params.password = password - if (username) params.username = username - if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity - if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch - } + if (password) params.password = password + if (username) params.username = username + if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity + if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch DMSService.sendRequest("network.wifi.connect", params, response => { if (response.error) { - if (DMSService.verboseLogs) { - console.log("NetworkManagerService: Connection request failed:", response.error) - } - connectionError = response.error lastConnectionError = response.error - pendingConnectionSSID = "" - connectionStatus = "failed" - ToastService.showError(I18n.tr("Failed to start connection to ") + ssid) + connectionStatus = response.error.includes("password") || response.error.includes("authentication") + ? "invalid_password" + : "failed" + + if (connectionStatus === "invalid_password") { + passwordDialogShouldReopen = true + ToastService.showError(`Invalid password for ${ssid}`) + } else { + ToastService.showError(`Failed to connect to ${ssid}`) + } } else { - if (DMSService.verboseLogs) { - console.log("NetworkManagerService: Connection request sent for", ssid) + connectionError = "" + connectionStatus = "connected" + ToastService.showInfo(`Connected to ${ssid}`) + + if (userPreference === "wifi" || userPreference === "auto") { + setConnectionPriority("wifi") } } + + isConnecting = false + connectingSSID = "" }) } @@ -400,60 +324,15 @@ Singleton { DMSService.sendRequest("network.wifi.disconnect", null, response => { if (response.error) { - ToastService.showError(I18n.tr("Failed to disconnect WiFi")) + ToastService.showError("Failed to disconnect WiFi") } else { - ToastService.showInfo(I18n.tr("Disconnected from WiFi")) + ToastService.showInfo("Disconnected from WiFi") currentWifiSSID = "" connectionStatus = "" } }) } - function submitCredentials(token, secrets, save) { - if (!networkAvailable || DMSService.apiVersion < 7) return - - const params = { - token: token, - secrets: secrets, - save: save || false - } - - if (DMSService.verboseLogs) { - console.log("NetworkManagerService: Submitting credentials for token", token) - } - - credentialsRequested = false - - DMSService.sendRequest("network.credentials.submit", params, response => { - if (response.error) { - console.warn("NetworkManagerService: Failed to submit credentials:", response.error) - } - }) - } - - function cancelCredentials(token) { - if (!networkAvailable || DMSService.apiVersion < 7) return - - const params = { - token: token, - cancel: true - } - - if (DMSService.verboseLogs) { - console.log("NetworkManagerService: Cancelling credentials for token", token) - } - - credentialsRequested = false - pendingConnectionSSID = "" - connectionStatus = "cancelled" - - DMSService.sendRequest("network.credentials.submit", params, response => { - if (response.error) { - console.warn("NetworkManagerService: Failed to cancel credentials:", response.error) - } - }) - } - function forgetWifiNetwork(ssid) { if (!networkAvailable) return @@ -462,7 +341,7 @@ Singleton { if (response.error) { console.warn("Failed to forget network:", response.error) } else { - ToastService.showInfo(I18n.tr("Forgot network ") + ssid) + ToastService.showInfo(`Forgot network ${ssid}`) savedConnections = savedConnections.filter(s => s.ssid !== ssid) savedWifiNetworks = savedWifiNetworks.filter(s => s.ssid !== ssid) @@ -495,7 +374,7 @@ Singleton { console.warn("Failed to toggle WiFi:", response.error) } else if (response.result) { wifiEnabled = response.result.enabled - ToastService.showInfo(wifiEnabled ? I18n.tr("WiFi enabled") : I18n.tr("WiFi disabled")) + ToastService.showInfo(wifiEnabled ? "WiFi enabled" : "WiFi disabled") } }) } @@ -505,9 +384,9 @@ Singleton { DMSService.sendRequest("network.wifi.enable", null, response => { if (response.error) { - ToastService.showError(I18n.tr("Failed to enable WiFi")) + ToastService.showError("Failed to enable WiFi") } else { - ToastService.showInfo(I18n.tr("WiFi enabled")) + ToastService.showInfo("WiFi enabled") } }) } diff --git a/Services/NetworkService.qml b/Services/NetworkService.qml index c8b8c3d2..35fb492c 100644 --- a/Services/NetworkService.qml +++ b/Services/NetworkService.qml @@ -69,17 +69,8 @@ Singleton { property bool subscriptionConnected: activeService?.subscriptionConnected ?? false - property string credentialsToken: activeService?.credentialsToken ?? "" - property string credentialsSSID: activeService?.credentialsSSID ?? "" - property string credentialsSetting: activeService?.credentialsSetting ?? "" - property var credentialsFields: activeService?.credentialsFields ?? [] - property var credentialsHints: activeService?.credentialsHints ?? [] - property string credentialsReason: activeService?.credentialsReason ?? "" - property bool credentialsRequested: activeService?.credentialsRequested ?? false - signal networksUpdated signal connectionChanged - signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason) property bool usingLegacy: false property var activeService: null @@ -131,9 +122,6 @@ Singleton { if (activeService.connectionChanged) { activeService.connectionChanged.connect(root.connectionChanged) } - if (activeService.credentialsNeeded) { - activeService.credentialsNeeded.connect(root.credentialsNeeded) - } } } @@ -270,16 +258,4 @@ Singleton { activeService.connectToSpecificWiredConfig(uuid) } } - - function submitCredentials(token, secrets, save) { - if (activeService && activeService.submitCredentials) { - activeService.submitCredentials(token, secrets, save) - } - } - - function cancelCredentials(token) { - if (activeService && activeService.cancelCredentials) { - activeService.cancelCredentials(token) - } - } } diff --git a/Services/niri-binds.kdl b/Services/niri-binds.kdl index 7f91d405..d7dbe687 100644 --- a/Services/niri-binds.kdl +++ b/Services/niri-binds.kdl @@ -19,6 +19,10 @@ binds { spawn "dms" "ipc" "call" "notifications" "toggle"; } + Mod+Y hotkey-overlay-title="Browse Wallpapers" { + spawn "dms" "ipc" "call" "dankdash" "wallpaper"; + } + Mod+Shift+N hotkey-overlay-title="Notepad" { spawn "dms" "ipc" "call" "notepad" "toggle"; } diff --git a/Widgets/DankPopout.qml b/Widgets/DankPopout.qml index 5a45feec..b11506d8 100644 --- a/Widgets/DankPopout.qml +++ b/Widgets/DankPopout.qml @@ -25,6 +25,7 @@ PanelWindow { property list animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial property list animationExitCurve: Theme.expressiveCurves.emphasized property bool shouldBeVisible: false + property int keyboardFocusMode: WlrKeyboardFocus.OnDemand signal opened signal popoutClosed @@ -63,7 +64,7 @@ PanelWindow { color: "transparent" WlrLayershell.layer: WlrLayershell.Top WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + WlrLayershell.keyboardFocus: shouldBeVisible ? keyboardFocusMode : WlrKeyboardFocus.None anchors { top: true diff --git a/Widgets/DankTabBar.qml b/Widgets/DankTabBar.qml index b065a31e..bfca6e14 100644 --- a/Widgets/DankTabBar.qml +++ b/Widgets/DankTabBar.qml @@ -2,7 +2,7 @@ import QtQuick import qs.Common import qs.Widgets -Item { +FocusScope { id: tabBar property alias model: tabRepeater.model @@ -11,12 +11,101 @@ Item { property int tabHeight: 56 property bool showIcons: true property bool equalWidthTabs: true + property bool enableArrowNavigation: true + property Item nextFocusTarget: null + property Item previousFocusTarget: null signal tabClicked(int index) signal actionTriggered(int index) + focus: false + activeFocusOnTab: true height: tabHeight + KeyNavigation.tab: nextFocusTarget + KeyNavigation.down: nextFocusTarget + KeyNavigation.backtab: previousFocusTarget + KeyNavigation.up: previousFocusTarget + + Keys.onPressed: (event) => { + if (!tabBar.activeFocus || tabRepeater.count === 0) + return + + function findSelectableIndex(startIndex, step) { + let idx = startIndex + for (let i = 0; i < tabRepeater.count; i++) { + idx = (idx + step + tabRepeater.count) % tabRepeater.count + const item = tabRepeater.itemAt(idx) + if (item && !item.isAction) + return idx + } + return -1 + } + + const goToIndex = (nextIndex) => { + if (nextIndex >= 0 && nextIndex !== tabBar.currentIndex) { + tabBar.currentIndex = nextIndex + tabBar.tabClicked(nextIndex) + } + } + + const resolveTarget = (item) => { + if (!item) + return null + + if (item.focusTarget) + return resolveTarget(item.focusTarget) + + return item + } + + const focusItem = (item) => { + const target = resolveTarget(item) + if (!target) + return false + + if (target.requestFocus) { + Qt.callLater(() => target.requestFocus()) + return true + } + + if (target.forceActiveFocus) { + Qt.callLater(() => target.forceActiveFocus()) + return true + } + + return false + } + + if (event.key === Qt.Key_Right && tabBar.enableArrowNavigation) { + const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : -1 + const nextIndex = findSelectableIndex(baseIndex, 1) + if (nextIndex >= 0) { + goToIndex(nextIndex) + event.accepted = true + } + } else if (event.key === Qt.Key_Left && tabBar.enableArrowNavigation) { + const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : 0 + const nextIndex = findSelectableIndex(baseIndex, -1) + if (nextIndex >= 0) { + goToIndex(nextIndex) + event.accepted = true + } + } else if (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) { + if (focusItem(tabBar.previousFocusTarget)) { + event.accepted = true + } + } else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down) { + if (focusItem(tabBar.nextFocusTarget)) { + event.accepted = true + } + } else if (event.key === Qt.Key_Up) { + if (focusItem(tabBar.previousFocusTarget)) { + event.accepted = true + } + } + } + Row { id: tabRow anchors.fill: parent @@ -77,7 +166,6 @@ Item { if (tabItem.isAction) { tabBar.actionTriggered(index) } else { - tabBar.currentIndex = index tabBar.tabClicked(index) } } diff --git a/docs/IPC.md b/docs/IPC.md index 10d7f994..b902387d 100644 --- a/docs/IPC.md +++ b/docs/IPC.md @@ -501,6 +501,13 @@ Dashboard popup control with tab selection for overview, media, and weather info - Parameters: `tab` - Optional tab to open when showing: "" (default), "overview", "media", or "weather" - Returns: Success/failure message +### Target: `dankdash` +DankDash wallpaper browser control. + +**Functions:** +- `wallpaper` - Toggle DankDash popup on focused screen with wallpaper tab selected + - Returns: Success/failure message + ### Target: `file` File browser controls for selecting wallpapers and profile images. @@ -512,7 +519,7 @@ File browser controls for selecting wallpapers and profile images. - Both browsers support common image formats (jpg, jpeg, png, bmp, gif, webp) ### Target: `hypr` -Hyprland-specific controls including keybinds cheatsheet and workspace overview (Hyprland only). +Hyprland keybinds cheatsheet modal control (Hyprland only). **Functions:** - `openBinds` - Show Hyprland keybinds cheatsheet modal @@ -524,31 +531,13 @@ Hyprland-specific controls including keybinds cheatsheet and workspace overview - `toggleBinds` - Toggle Hyprland keybinds cheatsheet modal visibility - Returns: Success/failure message - Note: Returns "HYPR_NOT_AVAILABLE" if not running Hyprland -- `openOverview` - Show Hyprland workspace overview - - Returns: "OVERVIEW_OPEN_SUCCESS" or "HYPR_NOT_AVAILABLE" - - Displays all workspaces across all monitors with live window previews - - Allows drag-and-drop window movement between workspaces and monitors -- `closeOverview` - Hide Hyprland workspace overview - - Returns: "OVERVIEW_CLOSE_SUCCESS" or "HYPR_NOT_AVAILABLE" -- `toggleOverview` - Toggle Hyprland workspace overview visibility - - Returns: "OVERVIEW_OPEN_SUCCESS", "OVERVIEW_CLOSE_SUCCESS", or "HYPR_NOT_AVAILABLE" -**Keybinds Cheatsheet Description:** +**Description:** Displays an auto-categorized cheatsheet of all Hyprland keybinds parsed from `~/.config/hypr`. Keybinds are organized into three columns: - **Window / Monitor** - Window and monitor management keybinds (sorted by dispatcher) - **Workspace** - Workspace switching and management (sorted by dispatcher) - **Execute** - Application launchers and commands (sorted by keybind) -**Workspace Overview Description:** -Displays a live overview of all workspaces across all monitors with window previews: -- **Multi-monitor support** - Shows workspaces from all connected monitors with monitor name labels -- **Live window previews** - Real-time screen capture of all windows on each workspace -- **Drag-and-drop** - Move windows between workspaces and monitors by dragging -- **Keyboard navigation** - Use Left/Right arrow keys to switch between workspaces on current monitor -- **Visual indicators** - Active workspace highlighted when it contains windows -- **Click to switch** - Click any workspace to switch to it -- **Click outside or press Escape** - Close the overview - ### Modal Examples ```bash # Open application launcher @@ -581,6 +570,9 @@ dms ipc call dash open overview dms ipc call dash toggle media dms ipc call dash open weather +# Open wallpaper browser +dms ipc call dankdash wallpaper + # Open file browsers dms ipc call file browse wallpaper dms ipc call file browse profile @@ -588,11 +580,6 @@ dms ipc call file browse profile # Show Hyprland keybinds cheatsheet (Hyprland only) dms ipc call hypr toggleBinds dms ipc call hypr openBinds - -# Show Hyprland workspace overview (Hyprland only) -dms ipc call hypr toggleOverview -dms ipc call hypr openOverview -dms ipc call hypr closeOverview ``` ## Common Usage Patterns @@ -620,7 +607,6 @@ bind = SUPER, V, exec, qs -c dms ipc call clipboard toggle bind = SUPER, P, exec, qs -c dms ipc call notepad toggle bind = SUPER, X, exec, qs -c dms ipc call powermenu toggle bind = SUPER, slash, exec, qs -c dms ipc call hypr toggleBinds -bind = SUPER, Tab, exec, qs -c dms ipc call hypr toggleOverview bind = , XF86AudioRaiseVolume, exec, qs -c dms ipc call audio increment 3 bind = , XF86MonBrightnessUp, exec, qs -c dms ipc call brightness increment 5 "" ``` diff --git a/nix/niri.nix b/nix/niri.nix index b6ba132a..acb91409 100644 --- a/nix/niri.nix +++ b/nix/niri.nix @@ -59,11 +59,6 @@ in { allow-when-locked = true; action = dms-ipc "audio" "micmute"; }; - "Mod+Alt+N" = { - allow-when-locked = true; - action = dms-ipc "night" "toggle"; - hotkey-overlay.title = "Toggle Night Mode"; - }; } // lib.attrsets.optionalAttrs cfg.enableSystemMonitoring { "Mod+M" = { @@ -86,6 +81,13 @@ in { allow-when-locked = true; action = dms-ipc "brightness" "decrement" "5" ""; }; + } + // lib.attrsets.optionalAttrs cfg.enableNightMode { + "Mod+Alt+N" = { + allow-when-locked = true; + action = dms-ipc "night" "toggle"; + hotkey-overlay.title = "Toggle Night Mode"; + }; }; }) diff --git a/scripts/matugen-worker.sh b/scripts/matugen-worker.sh index 3b0e5d76..39d44f40 100755 --- a/scripts/matugen-worker.sh +++ b/scripts/matugen-worker.sh @@ -1,15 +1,14 @@ #!/usr/bin/env bash set -euo pipefail -if [ $# -lt 5 ]; then - echo "Usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL --run" >&2 +if [ $# -lt 4 ]; then + echo "Usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR --run" >&2 exit 1 fi STATE_DIR="$1" SHELL_DIR="$2" CONFIG_DIR="$3" -SYNC_MODE_WITH_PORTAL="$4" if [ ! -d "$STATE_DIR" ]; then echo "Error: STATE_DIR '$STATE_DIR' does not exist" >&2 @@ -26,10 +25,10 @@ if [ ! -d "$CONFIG_DIR" ]; then exit 1 fi -shift 4 +shift 3 # Remove STATE_DIR, SHELL_DIR, and CONFIG_DIR from arguments if [[ "${1:-}" != "--run" ]]; then - echo "usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL --run" >&2 + echo "usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR --run" >&2 exit 1 fi @@ -63,27 +62,6 @@ key_of() { echo "${kind}|${value}|${mode}|${icon}|${matugen_type}|${surface_base}|${run_user_templates}" | sha256sum | cut -d' ' -f1 } -set_system_color_scheme() { - local mode="$1" - - if [[ "$SYNC_MODE_WITH_PORTAL" != "true" ]]; then - return 0 - fi - - local target_scheme - if [[ "$mode" == "light" ]]; then - target_scheme="default" - else - target_scheme="prefer-dark" - fi - - if command -v gsettings >/dev/null 2>&1; then - gsettings set org.gnome.desktop.interface color-scheme "$target_scheme" >/dev/null 2>&1 || true - elif command -v dconf >/dev/null 2>&1; then - dconf write /org/gnome/desktop/interface/color-scheme "'$target_scheme'" >/dev/null 2>&1 || true - fi -} - build_once() { local json="$1" local kind value mode icon matugen_type surface_base run_user_templates @@ -259,7 +237,7 @@ EOF rm -f "$TMP_CONTENT_CFG" popd >/dev/null - echo "$JSON" | grep -q '"primary"' || { echo "matugen JSON missing primary" >&2; set_system_color_scheme "$mode"; return 2; } + echo "$JSON" | grep -q '"primary"' || { echo "matugen JSON missing primary" >&2; return 2; } printf "%s" "$JSON" > "$LAST_JSON" GTK_CSS="$CONFIG_DIR/gtk-3.0/gtk.css" @@ -311,8 +289,6 @@ EOF mv "$TMP" "$CONFIG_DIR/kitty/dank-theme.conf" fi fi - - set_system_color_scheme "$mode" } if command -v pywalfox >/dev/null 2>&1 && [[ -f "$HOME/.cache/wal/colors.json" ]]; then diff --git a/translations/en.json b/translations/en.json index 522f8f27..1e5d7469 100644 --- a/translations/en.json +++ b/translations/en.json @@ -62,7 +62,7 @@ { "term": "About", "context": "About", - "reference": "Modules/Settings/AboutTab.qml:251, Modals/Settings/SettingsSidebar.qml:44", + "reference": "Modals/Settings/SettingsSidebar.qml:44, Modules/Settings/AboutTab.qml:251", "comment": "" }, { @@ -116,7 +116,7 @@ { "term": "All", "context": "All", - "reference": "Services/AppSearchService.qml:217, Services/AppSearchService.qml:233, Modules/AppDrawer/AppDrawerPopout.qml:45, Modules/AppDrawer/CategorySelector.qml:11, Modules/AppDrawer/AppLauncher.qml:16, Modules/AppDrawer/AppLauncher.qml:27, Modules/AppDrawer/AppLauncher.qml:28, Modules/AppDrawer/AppLauncher.qml:45, Modules/AppDrawer/AppLauncher.qml:46, Modules/AppDrawer/AppLauncher.qml:80, Modals/Spotlight/SpotlightModal.qml:59", + "reference": "Services/AppSearchService.qml:217, Services/AppSearchService.qml:233, Modals/Spotlight/SpotlightModal.qml:59, Modules/AppDrawer/CategorySelector.qml:11, Modules/AppDrawer/AppLauncher.qml:12, Modules/AppDrawer/AppLauncher.qml:23, Modules/AppDrawer/AppLauncher.qml:24, Modules/AppDrawer/AppLauncher.qml:41, Modules/AppDrawer/AppLauncher.qml:42, Modules/AppDrawer/AppLauncher.qml:76, Modules/AppDrawer/AppDrawerPopout.qml:45", "comment": "" }, { @@ -128,7 +128,7 @@ { "term": "All displays", "context": "All displays", - "reference": "Modules/Settings/DisplaysTab.qml:670", + "reference": "Modules/Settings/DisplaysTab.qml:666", "comment": "" }, { @@ -158,7 +158,7 @@ { "term": "Anonymous Identity (optional)", "context": "Anonymous Identity (optional)", - "reference": "Modals/WifiPasswordModal.qml:365", + "reference": "Modals/WifiPasswordModal.qml:282", "comment": "" }, { @@ -299,6 +299,12 @@ "reference": "Modules/Settings/DockTab.qml:128", "comment": "" }, + { + "term": "Auto-location", + "context": "Auto-location", + "reference": "Modules/Settings/DisplaysTab.qml:364", + "comment": "" + }, { "term": "Auto-saving...", "context": "Auto-saving...", @@ -308,7 +314,7 @@ { "term": "Automatic Control", "context": "Automatic Control", - "reference": "Modules/Settings/DisplaysTab.qml:164", + "reference": "Modules/Settings/DisplaysTab.qml:162", "comment": "" }, { @@ -329,12 +335,6 @@ "reference": "Modules/Settings/PersonalizationTab.qml:647", "comment": "" }, - { - "term": "Automatically detect location based on IP address", - "context": "Automatically detect location based on IP address", - "reference": "Modules/Settings/DisplaysTab.qml:369", - "comment": "" - }, { "term": "Automatically determine your location using your IP address", "context": "Automatically determine your location using your IP address", @@ -380,7 +380,7 @@ { "term": "Available Screens (", "context": "Available Screens (", - "reference": "Modules/Settings/DisplaysTab.qml:513", + "reference": "Modules/Settings/DisplaysTab.qml:509", "comment": "" }, { @@ -524,7 +524,7 @@ { "term": "Cancel", "context": "Cancel", - "reference": "Modals/DankColorPickerModal.qml:510, Modals/WifiPasswordModal.qml:468, Modules/Settings/PluginsTab.qml:1220, Modals/FileBrowser/FileBrowserModal.qml:952", + "reference": "Modals/DankColorPickerModal.qml:510, Modals/WifiPasswordModal.qml:385, Modals/FileBrowser/FileBrowserModal.qml:952, Modules/Settings/PluginsTab.qml:1220", "comment": "" }, { @@ -578,13 +578,13 @@ { "term": "Clear", "context": "Clear", - "reference": "Modules/Notifications/Center/NotificationHeader.qml:102", + "reference": "Modules/Notifications/Popup/NotificationPopup.qml:24, Modules/Notifications/Center/NotificationCard.qml:540, Modules/Notifications/Center/NotificationCard.qml:633", "comment": "" }, { "term": "Clear All", "context": "Clear All", - "reference": "Modals/Clipboard/ClipboardHistoryModal.qml:157", + "reference": "Modals/Clipboard/ClipboardHistoryModal.qml:157, Modules/Notifications/Center/NotificationHeader.qml:102", "comment": "" }, { @@ -620,7 +620,7 @@ { "term": "Close", "context": "Close", - "reference": "Modals/NetworkInfoModal.qml:129, Modals/NetworkWiredInfoModal.qml:129, Modules/SystemUpdatePopout.qml:333, Modules/DankBar/Widgets/RunningApps.qml:717", + "reference": "Modules/SystemUpdatePopout.qml:333, Modals/NetworkWiredInfoModal.qml:129, Modals/NetworkInfoModal.qml:129, Modules/DankBar/Widgets/RunningApps.qml:711", "comment": "" }, { @@ -638,7 +638,7 @@ { "term": "Color temperature for night mode", "context": "Color temperature for night mode", - "reference": "Modules/Settings/DisplaysTab.qml:145", + "reference": "Modules/Settings/DisplaysTab.qml:143", "comment": "" }, { @@ -701,12 +701,6 @@ "reference": "Modules/Settings/AboutTab.qml:361", "comment": "" }, - { - "term": "Configuration activated", - "context": "Configuration activated", - "reference": "Services/NetworkManagerService.qml:320", - "comment": "" - }, { "term": "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.", "context": "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.", @@ -716,31 +710,25 @@ { "term": "Configure which displays show shell components", "context": "Configure which displays show shell components", - "reference": "Modules/Settings/DisplaysTab.qml:497", + "reference": "Modules/Settings/DisplaysTab.qml:493", "comment": "" }, { "term": "Connect", "context": "Connect", - "reference": "Modals/WifiPasswordModal.qml:505", + "reference": "Modals/WifiPasswordModal.qml:419", "comment": "" }, { "term": "Connect to Wi-Fi", "context": "Connect to Wi-Fi", - "reference": "Modals/WifiPasswordModal.qml:166", + "reference": "Modals/WifiPasswordModal.qml:114", "comment": "" }, { "term": "Connected Displays", "context": "Connected Displays", - "reference": "Modules/Settings/DisplaysTab.qml:490", - "comment": "" - }, - { - "term": "Connection failed. Check password and try again.", - "context": "Connection failed. Check password and try again.", - "reference": "Services/NetworkManagerService.qml:287", + "reference": "Modules/Settings/DisplaysTab.qml:486", "comment": "" }, { @@ -884,7 +872,7 @@ { "term": "DMS out of date", "context": "DMS out of date", - "reference": "Services/DMSService.qml:235", + "reference": "Services/DMSService.qml:234", "comment": "" }, { @@ -983,12 +971,6 @@ "reference": "Modules/ControlCenter/BuiltinPlugins/VpnWidget.qml:90, Modules/DankBar/Popouts/VpnPopout.qml:213", "comment": "" }, - { - "term": "Disconnected from WiFi", - "context": "Disconnected from WiFi", - "reference": "Services/NetworkManagerService.qml:405", - "comment": "" - }, { "term": "Disk", "context": "Disk", @@ -1001,12 +983,6 @@ "reference": "Modules/Settings/DankBarTab.qml:87", "comment": "" }, - { - "term": "Dismiss", - "context": "Dismiss", - "reference": "Modules/Notifications/Popup/NotificationPopup.qml:24, Modules/Notifications/Center/NotificationCard.qml:540, Modules/Notifications/Center/NotificationCard.qml:633", - "comment": "" - }, { "term": "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen", "context": "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen", @@ -1058,7 +1034,7 @@ { "term": "Do Not Disturb", "context": "Do Not Disturb", - "reference": "Modules/Notifications/Center/NotificationSettings.qml:131, Modules/Notifications/Center/NotificationHeader.qml:41", + "reference": "Modules/Notifications/Center/NotificationHeader.qml:41, Modules/Notifications/Center/NotificationSettings.qml:131", "comment": "" }, { @@ -1082,7 +1058,7 @@ { "term": "Domain (optional)", "context": "Domain (optional)", - "reference": "Modals/WifiPasswordModal.qml:397", + "reference": "Modals/WifiPasswordModal.qml:314", "comment": "" }, { @@ -1160,13 +1136,13 @@ { "term": "End", "context": "End", - "reference": "Modules/Settings/DisplaysTab.qml:318", + "reference": "Modules/Settings/DisplaysTab.qml:315", "comment": "" }, { "term": "Enter credentials for ", "context": "Enter credentials for ", - "reference": "Modals/WifiPasswordModal.qml:178", + "reference": "Modals/WifiPasswordModal.qml:121", "comment": "" }, { @@ -1190,7 +1166,7 @@ { "term": "Enter password for ", "context": "Enter password for ", - "reference": "Modals/WifiPasswordModal.qml:178", + "reference": "Modals/WifiPasswordModal.qml:121", "comment": "" }, { @@ -1211,30 +1187,6 @@ "reference": "Modals/FileBrowser/FileInfo.qml:200", "comment": "" }, - { - "term": "Failed to activate configuration", - "context": "Failed to activate configuration", - "reference": "Services/NetworkManagerService.qml:316", - "comment": "" - }, - { - "term": "Failed to connect to ", - "context": "Failed to connect to ", - "reference": "Services/NetworkManagerService.qml:289", - "comment": "" - }, - { - "term": "Failed to disconnect WiFi", - "context": "Failed to disconnect WiFi", - "reference": "Services/NetworkManagerService.qml:403", - "comment": "" - }, - { - "term": "Failed to enable WiFi", - "context": "Failed to enable WiFi", - "reference": "Services/NetworkManagerService.qml:508", - "comment": "" - }, { "term": "Failed to set profile image", "context": "Failed to set profile image", @@ -1247,12 +1199,6 @@ "reference": "Services/PortalService.qml:159", "comment": "" }, - { - "term": "Failed to start connection to ", - "context": "Failed to start connection to ", - "reference": "Services/NetworkManagerService.qml:389", - "comment": "" - }, { "term": "Feels Like", "context": "Feels Like", @@ -1292,7 +1238,7 @@ { "term": "Font Family", "context": "Font Family", - "reference": "Modules/Settings/ThemeColorsTab.qml:944, Modules/Notepad/NotepadSettings.qml:220", + "reference": "Modules/Notepad/NotepadSettings.qml:220, Modules/Settings/ThemeColorsTab.qml:944", "comment": "" }, { @@ -1334,13 +1280,7 @@ { "term": "Forget Network", "context": "Forget Network", - "reference": "Modules/ControlCenter/Details/NetworkDetail.qml:606", - "comment": "" - }, - { - "term": "Forgot network ", - "context": "Forgot network ", - "reference": "Services/NetworkManagerService.qml:465", + "reference": "Modules/ControlCenter/Details/NetworkDetail.qml:598", "comment": "" }, { @@ -1392,9 +1332,9 @@ "comment": "" }, { - "term": "Gamma control not available. Requires DMS API v6+.", - "context": "Gamma control not available. Requires DMS API v6+.", - "reference": "Modules/Settings/DisplaysTab.qml:119", + "term": "Geoclue service not running - cannot auto-detect location", + "context": "Geoclue service not running - cannot auto-detect location", + "reference": "Modules/Settings/DisplaysTab.qml:365", "comment": "" }, { @@ -1484,7 +1424,7 @@ { "term": "Hour", "context": "Hour", - "reference": "Modules/Settings/DisplaysTab.qml:255", + "reference": "Modules/Settings/DisplaysTab.qml:252", "comment": "" }, { @@ -1547,12 +1487,6 @@ "reference": "Modules/Settings/PersonalizationTab.qml:923", "comment": "" }, - { - "term": "Incorrect password", - "context": "Incorrect password", - "reference": "Modals/WifiPasswordModal.qml:189", - "comment": "" - }, { "term": "Individual Batteries", "context": "Individual Batteries", @@ -1640,13 +1574,13 @@ { "term": "Latitude", "context": "Latitude", - "reference": "Modules/Settings/DisplaysTab.qml:402, Modules/Settings/TimeWeatherTab.qml:665", + "reference": "Modules/Settings/DisplaysTab.qml:398, Modules/Settings/TimeWeatherTab.qml:665", "comment": "" }, { "term": "Launch", "context": "Launch", - "reference": "Modules/AppDrawer/AppDrawerPopout.qml:923, Modals/Spotlight/SpotlightContextMenu.qml:251", + "reference": "Modals/Spotlight/SpotlightContextMenu.qml:251, Modules/AppDrawer/AppDrawerPopout.qml:895", "comment": "" }, { @@ -1658,7 +1592,7 @@ { "term": "Launch on dGPU", "context": "Launch on dGPU", - "reference": "Modules/AppDrawer/AppDrawerPopout.qml:983, Modules/Dock/DockContextMenu.qml:417, Modals/Spotlight/SpotlightContextMenu.qml:312", + "reference": "Modals/Spotlight/SpotlightContextMenu.qml:312, Modules/AppDrawer/AppDrawerPopout.qml:955, Modules/Dock/DockContextMenu.qml:417", "comment": "" }, { @@ -1748,7 +1682,7 @@ { "term": "Longitude", "context": "Longitude", - "reference": "Modules/Settings/DisplaysTab.qml:425, Modules/Settings/TimeWeatherTab.qml:716", + "reference": "Modules/Settings/DisplaysTab.qml:421, Modules/Settings/TimeWeatherTab.qml:716", "comment": "" }, { @@ -1766,7 +1700,7 @@ { "term": "Manual Coordinates", "context": "Manual Coordinates", - "reference": "Modules/Settings/DisplaysTab.qml:390", + "reference": "Modules/Settings/DisplaysTab.qml:386", "comment": "" }, { @@ -1790,7 +1724,7 @@ { "term": "Matugen Palette", "context": "Matugen Palette", - "reference": "Modules/Settings/PersonalizationTab.qml:1276, Modules/Settings/ThemeColorsTab.qml:629", + "reference": "Modules/Settings/ThemeColorsTab.qml:629, Modules/Settings/PersonalizationTab.qml:1276", "comment": "" }, { @@ -1856,7 +1790,7 @@ { "term": "Minute", "context": "Minute", - "reference": "Modules/Settings/DisplaysTab.qml:263", + "reference": "Modules/Settings/DisplaysTab.qml:260", "comment": "" }, { @@ -1928,13 +1862,13 @@ { "term": "Network Info", "context": "Network Info", - "reference": "Modules/ControlCenter/Details/NetworkDetail.qml:340, Modules/ControlCenter/Details/NetworkDetail.qml:583", + "reference": "Modules/ControlCenter/Details/NetworkDetail.qml:340, Modules/ControlCenter/Details/NetworkDetail.qml:575", "comment": "" }, { "term": "Network Information", "context": "Network Information", - "reference": "Modals/NetworkInfoModal.qml:59, Modals/NetworkWiredInfoModal.qml:59", + "reference": "Modals/NetworkWiredInfoModal.qml:59, Modals/NetworkInfoModal.qml:59", "comment": "" }, { @@ -2054,7 +1988,7 @@ { "term": "Notepad", "context": "Notepad", - "reference": "DMSShell.qml:413, Modules/Settings/DankBarTab.qml:175", + "reference": "DMSShell.qml:407, Modules/Settings/DankBarTab.qml:175", "comment": "" }, { @@ -2090,7 +2024,7 @@ { "term": "Notification Popups", "context": "Notification Popups", - "reference": "Modules/Settings/WidgetTweaksTab.qml:585, Modules/Settings/DisplaysTab.qml:24", + "reference": "Modules/Settings/DisplaysTab.qml:24, Modules/Settings/WidgetTweaksTab.qml:585", "comment": "" }, { @@ -2144,7 +2078,7 @@ { "term": "Only adjust gamma based on time or location rules.", "context": "Only adjust gamma based on time or location rules.", - "reference": "Modules/Settings/DisplaysTab.qml:165", + "reference": "Modules/Settings/DisplaysTab.qml:163", "comment": "" }, { @@ -2192,7 +2126,7 @@ { "term": "Password", "context": "Password", - "reference": "Modals/WifiPasswordModal.qml:274", + "reference": "Modals/WifiPasswordModal.qml:203", "comment": "" }, { @@ -2234,7 +2168,7 @@ { "term": "Pin to Dock", "context": "Pin to Dock", - "reference": "Modules/AppDrawer/AppDrawerPopout.qml:786, Modules/Dock/DockContextMenu.qml:370, Modals/Spotlight/SpotlightContextMenu.qml:110, Modals/Spotlight/SpotlightContextMenu.qml:113", + "reference": "Modals/Spotlight/SpotlightContextMenu.qml:110, Modals/Spotlight/SpotlightContextMenu.qml:113, Modules/AppDrawer/AppDrawerPopout.qml:758, Modules/Dock/DockContextMenu.qml:370", "comment": "" }, { @@ -2510,7 +2444,7 @@ { "term": "Save", "context": "Save", - "reference": "Modules/Notepad/Notepad.qml:480, Modules/Notepad/NotepadTextEditor.qml:511, Modals/FileBrowser/FileBrowserModal.qml:818", + "reference": "Modals/FileBrowser/FileBrowserModal.qml:818, Modules/Notepad/NotepadTextEditor.qml:511, Modules/Notepad/Notepad.qml:480", "comment": "" }, { @@ -2636,7 +2570,7 @@ { "term": "Select the palette algorithm used for wallpaper-based colors", "context": "Select the palette algorithm used for wallpaper-based colors", - "reference": "Modules/Settings/PersonalizationTab.qml:1277, Modules/Settings/ThemeColorsTab.qml:630", + "reference": "Modules/Settings/ThemeColorsTab.qml:630, Modules/Settings/PersonalizationTab.qml:1277", "comment": "" }, { @@ -2672,7 +2606,7 @@ { "term": "Settings", "context": "Settings", - "reference": "Services/AppSearchService.qml:176, Modules/DankDash/DankDashPopout.qml:142, Modals/Settings/SettingsModal.qml:165", + "reference": "Services/AppSearchService.qml:176, Modals/Settings/SettingsModal.qml:165, Modules/DankDash/DankDashPopout.qml:142", "comment": "" }, { @@ -2720,13 +2654,13 @@ { "term": "Show on all connected displays", "context": "Show on all connected displays", - "reference": "Modules/Settings/DisplaysTab.qml:671", + "reference": "Modules/Settings/DisplaysTab.qml:667", "comment": "" }, { "term": "Show on screens:", "context": "Show on screens:", - "reference": "Modules/Settings/DisplaysTab.qml:655", + "reference": "Modules/Settings/DisplaysTab.qml:651", "comment": "" }, { @@ -2744,7 +2678,7 @@ { "term": "Show password", "context": "Show password", - "reference": "Modals/WifiPasswordModal.qml:440", + "reference": "Modals/WifiPasswordModal.qml:357", "comment": "" }, { @@ -2834,7 +2768,7 @@ { "term": "Start", "context": "Start", - "reference": "Modules/Settings/DisplaysTab.qml:275", + "reference": "Modules/Settings/DisplaysTab.qml:272", "comment": "" }, { @@ -2882,7 +2816,7 @@ { "term": "Switch User", "context": "Switch User", - "reference": "Modules/Greetd/GreeterContent.qml:549", + "reference": "Modules/Greetd/GreeterContent.qml:567", "comment": "" }, { @@ -2996,7 +2930,7 @@ { "term": "Temperature", "context": "Temperature", - "reference": "Modules/Settings/DisplaysTab.qml:144", + "reference": "Modules/Settings/DisplaysTab.qml:142", "comment": "" }, { @@ -3074,7 +3008,7 @@ { "term": "To update, run the following command:", "context": "To update, run the following command:", - "reference": "Services/DMSService.qml:236", + "reference": "Services/DMSService.qml:235", "comment": "" }, { @@ -3134,7 +3068,7 @@ { "term": "Unpin from Dock", "context": "Unpin from Dock", - "reference": "Modules/AppDrawer/AppDrawerPopout.qml:786, Modules/Dock/DockContextMenu.qml:370, Modals/Spotlight/SpotlightContextMenu.qml:113", + "reference": "Modals/Spotlight/SpotlightContextMenu.qml:113, Modules/AppDrawer/AppDrawerPopout.qml:758, Modules/Dock/DockContextMenu.qml:370", "comment": "" }, { @@ -3191,12 +3125,6 @@ "reference": "Modules/Settings/TimeWeatherTab.qml:545", "comment": "" }, - { - "term": "Use IP Location", - "context": "Use IP Location", - "reference": "Modules/Settings/DisplaysTab.qml:368", - "comment": "" - }, { "term": "Use Monospace Font", "context": "Use Monospace Font", @@ -3215,6 +3143,12 @@ "reference": "Modules/Settings/WidgetTweaksTab.qml:194", "comment": "" }, + { + "term": "Use automatic location detection (geoclue2)", + "context": "Use automatic location detection (geoclue2)", + "reference": "Modules/Settings/DisplaysTab.qml:365", + "comment": "" + }, { "term": "Use custom command for update your system", "context": "Use custom command for update your system", @@ -3254,13 +3188,13 @@ { "term": "Username", "context": "Username", - "reference": "Modals/WifiPasswordModal.qml:237", + "reference": "Modals/WifiPasswordModal.qml:166", "comment": "" }, { "term": "Uses sunrise/sunset times to automatically adjust night mode based on your location.", "context": "Uses sunrise/sunset times to automatically adjust night mode based on your location.", - "reference": "Modules/Settings/DisplaysTab.qml:446", + "reference": "Modules/Settings/DisplaysTab.qml:442", "comment": "" }, { @@ -3353,18 +3287,6 @@ "reference": "Modules/Settings/LauncherTab.qml:517", "comment": "" }, - { - "term": "WiFi disabled", - "context": "WiFi disabled", - "reference": "Services/NetworkManagerService.qml:498", - "comment": "" - }, - { - "term": "WiFi enabled", - "context": "WiFi enabled", - "reference": "Services/NetworkManagerService.qml:498, Services/NetworkManagerService.qml:510", - "comment": "" - }, { "term": "WiFi is off", "context": "WiFi is off", diff --git a/translations/poexports/ja.json b/translations/poexports/ja.json index d173441e..01d02d75 100644 --- a/translations/poexports/ja.json +++ b/translations/poexports/ja.json @@ -167,9 +167,6 @@ "Automatically cycle through wallpapers in the same folder": { "Automatically cycle through wallpapers in the same folder": "同じフォルダ内の壁紙を自動的に切り替える" }, - "Automatically detect location based on IP address": { - "Automatically detect location based on IP address": "IPアドレスに基づいて位置を自動的に検出" - }, "Automatically determine your location using your IP address": { "Automatically determine your location using your IP address": "IPアドレスを使用して現在地を自動的に特定します" }, @@ -353,9 +350,6 @@ "Compositor:": { "Compositor:": "コンポジター:" }, - "Configuration activated": { - "Configuration activated": "" - }, "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": { "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": "名前付きワークスペースのアイコンを設定します。両方が有効になっている場合、アイコンが数字よりも優先されます。" }, @@ -371,9 +365,6 @@ "Connected Displays": { "Connected Displays": "接続されたディスプレイ" }, - "Connection failed. Check password and try again.": { - "Connection failed. Check password and try again.": "" - }, "Contrast": { "Contrast": "コントラスト" }, @@ -494,18 +485,12 @@ "Disconnect": { "Disconnect": "切断" }, - "Disconnected from WiFi": { - "Disconnected from WiFi": "" - }, "Disk": { "Disk": "ディスク" }, "Disk Usage": { "Disk Usage": "ディスク使用率" }, - "Dismiss": { - "Dismiss": "" - }, "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": { "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "画面の上、下、左、右の端に配置できる、ピン留めされた実行中のアプリケーションを含むドックを表示します" }, @@ -608,27 +593,6 @@ "F1/I: Toggle • F10: Help": { "F1/I: Toggle • F10: Help": "F1/I: 切り替え • F10: ヘルプ" }, - "Failed to activate configuration": { - "Failed to activate configuration": "" - }, - "Failed to connect to ": { - "Failed to connect to ": "" - }, - "Failed to disconnect WiFi": { - "Failed to disconnect WiFi": "" - }, - "Failed to enable WiFi": { - "Failed to enable WiFi": "" - }, - "Failed to set profile image": { - "Failed to set profile image": "プロフィール画像の設定に失敗しました" - }, - "Failed to set profile image: ": { - "Failed to set profile image: ": "プロフィール画像の設定に失敗しました: " - }, - "Failed to start connection to ": { - "Failed to start connection to ": "" - }, "Feels Like": { "Feels Like": "どうやら" }, @@ -671,9 +635,6 @@ "Forget Network": { "Forget Network": "ネットワークを忘れる" }, - "Forgot network ": { - "Forgot network ": "" - }, "Format Legend": { "Format Legend": "フォーマット凡例" }, @@ -698,9 +659,6 @@ "Gamma Control": { "Gamma Control": "ガンマ設定" }, - "Gamma control not available. Requires DMS API v6+.": { - "Gamma control not available. Requires DMS API v6+.": "ガンマ制御は使用できません。DMS API v6+ が必要です。" - }, "Geoclue service not running - cannot auto-detect location": { "Geoclue service not running - cannot auto-detect location": "ジオクルー サービスが実行されていない - 位置を自動検出できません" }, @@ -779,9 +737,6 @@ "Include Transitions": { "Include Transitions": "トランジションを含める" }, - "Incorrect password": { - "Incorrect password": "" - }, "Individual Batteries": { "Individual Batteries": "バッテリーごと" }, @@ -1115,9 +1070,6 @@ "Percentage": { "Percentage": "百分率" }, - "Permission denied to set profile image.": { - "Permission denied to set profile image.": "プロフィール画像の設定権限が拒否されました。" - }, "Personalization": { "Personalization": "パーソナライゼーション" }, @@ -1196,12 +1148,6 @@ "Process": { "Process": "プロセス" }, - "Profile Image Error": { - "Profile Image Error": "プロフィール画像エラー" - }, - "Profile image is too large. Please use a smaller image.": { - "Profile image is too large. Please use a smaller image.": "プロフィール画像が大きすぎます。より小さい画像を使用してください。" - }, "QML, JavaScript, Go": { "QML, JavaScript, Go": "QML, JavaScript, Go" }, @@ -1328,9 +1274,6 @@ "Select which transitions to include in randomization": { "Select which transitions to include in randomization": "含めたいトランジションをランダム化に選択" }, - "Selected image file not found.": { - "Selected image file not found.": "選択した画像ファイルが見つかりませんでした。" - }, "Separator": { "Separator": "区切り" }, @@ -1601,9 +1544,6 @@ "Use Fahrenheit instead of Celsius for temperature": { "Use Fahrenheit instead of Celsius for temperature": "温度は摂氏ではなく華氏を使用" }, - "Use IP Location": { - "Use IP Location": "IP ロケーションの使用" - }, "Use Monospace Font": { "Use Monospace Font": "等幅フォントを使用" }, @@ -1685,12 +1625,6 @@ "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": { "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "有効にすると、アプリはアルファベット順に並べ替えられます。無効にすると、アプリは使用頻度で並べ替えられます。" }, - "WiFi disabled": { - "WiFi disabled": "" - }, - "WiFi enabled": { - "WiFi enabled": "" - }, "WiFi is off": { "WiFi is off": "Wi-Fiはオフ中" }, diff --git a/translations/poexports/pt.json b/translations/poexports/pt.json index 0e458cb9..4348e99e 100644 --- a/translations/poexports/pt.json +++ b/translations/poexports/pt.json @@ -167,9 +167,6 @@ "Automatically cycle through wallpapers in the same folder": { "Automatically cycle through wallpapers in the same folder": "Circular automaticamente dentre os papéis de parede na mesma pasta" }, - "Automatically detect location based on IP address": { - "Automatically detect location based on IP address": "" - }, "Automatically determine your location using your IP address": { "Automatically determine your location using your IP address": "Detectar automaticamente a sua localização usando o seu endereço de IP" }, @@ -353,9 +350,6 @@ "Compositor:": { "Compositor:": "Compositor:" }, - "Configuration activated": { - "Configuration activated": "" - }, "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": { "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": "Configurações de ícones para área de trabalho nomeadas. Ícones tem prioridade sobre números, isso quando ambos estão habilitados. " }, @@ -371,9 +365,6 @@ "Connected Displays": { "Connected Displays": "Telas Conectadas" }, - "Connection failed. Check password and try again.": { - "Connection failed. Check password and try again.": "" - }, "Contrast": { "Contrast": "Contraste" }, @@ -494,18 +485,12 @@ "Disconnect": { "Disconnect": "Desconectar" }, - "Disconnected from WiFi": { - "Disconnected from WiFi": "" - }, "Disk": { "Disk": "Disco" }, "Disk Usage": { "Disk Usage": "Uso de Disco" }, - "Dismiss": { - "Dismiss": "" - }, "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": { "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "Exibir um dock com aplicativos que estão sendo utilizados, e que pode ser posicionada no superior, inferior, esquerda ou direita dos cantos da tela" }, @@ -608,27 +593,6 @@ "F1/I: Toggle • F10: Help": { "F1/I: Toggle • F10: Help": "F1/I: Ativar • F10: Ajuda" }, - "Failed to activate configuration": { - "Failed to activate configuration": "" - }, - "Failed to connect to ": { - "Failed to connect to ": "" - }, - "Failed to disconnect WiFi": { - "Failed to disconnect WiFi": "" - }, - "Failed to enable WiFi": { - "Failed to enable WiFi": "" - }, - "Failed to set profile image": { - "Failed to set profile image": "" - }, - "Failed to set profile image: ": { - "Failed to set profile image: ": "" - }, - "Failed to start connection to ": { - "Failed to start connection to ": "" - }, "Feels Like": { "Feels Like": "Sensação Térmica" }, @@ -671,9 +635,6 @@ "Forget Network": { "Forget Network": "Esquecer Internet" }, - "Forgot network ": { - "Forgot network ": "" - }, "Format Legend": { "Format Legend": "Formatar Legenda" }, @@ -698,9 +659,6 @@ "Gamma Control": { "Gamma Control": "Controle de Gama" }, - "Gamma control not available. Requires DMS API v6+.": { - "Gamma control not available. Requires DMS API v6+.": "" - }, "Geoclue service not running - cannot auto-detect location": { "Geoclue service not running - cannot auto-detect location": "" }, @@ -779,9 +737,6 @@ "Include Transitions": { "Include Transitions": "Incluir Transições" }, - "Incorrect password": { - "Incorrect password": "" - }, "Individual Batteries": { "Individual Batteries": "Baterias Individuais" }, @@ -1115,9 +1070,6 @@ "Percentage": { "Percentage": "Porcetagem" }, - "Permission denied to set profile image.": { - "Permission denied to set profile image.": "" - }, "Personalization": { "Personalization": "Personalização" }, @@ -1196,12 +1148,6 @@ "Process": { "Process": "Processo" }, - "Profile Image Error": { - "Profile Image Error": "" - }, - "Profile image is too large. Please use a smaller image.": { - "Profile image is too large. Please use a smaller image.": "" - }, "QML, JavaScript, Go": { "QML, JavaScript, Go": "QML, JavaScript, Go" }, @@ -1328,9 +1274,6 @@ "Select which transitions to include in randomization": { "Select which transitions to include in randomization": "Selecionar quais transições incluir na randomização" }, - "Selected image file not found.": { - "Selected image file not found.": "" - }, "Separator": { "Separator": "Separador" }, @@ -1601,9 +1544,6 @@ "Use Fahrenheit instead of Celsius for temperature": { "Use Fahrenheit instead of Celsius for temperature": "Usar Fahrenheit em vez de Celsius para temperatura" }, - "Use IP Location": { - "Use IP Location": "" - }, "Use Monospace Font": { "Use Monospace Font": "Usar Fonte Monoespaçada" }, @@ -1685,12 +1625,6 @@ "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": { "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "Quando ativado, apps são ordenados alfabeticamente. Quando desativado, apps são ordenados por frequência de uso" }, - "WiFi disabled": { - "WiFi disabled": "" - }, - "WiFi enabled": { - "WiFi enabled": "" - }, "WiFi is off": { "WiFi is off": "WiFi desligado" }, diff --git a/translations/poexports/zh_CN.json b/translations/poexports/zh_CN.json index 663fd2be..c8ab15ac 100644 --- a/translations/poexports/zh_CN.json +++ b/translations/poexports/zh_CN.json @@ -167,9 +167,6 @@ "Automatically cycle through wallpapers in the same folder": { "Automatically cycle through wallpapers in the same folder": "自动轮换文件夹中的壁纸" }, - "Automatically detect location based on IP address": { - "Automatically detect location based on IP address": "根据 IP 地址自动检测位置" - }, "Automatically determine your location using your IP address": { "Automatically determine your location using your IP address": "通过 IP 地址自动检测您的位置" }, @@ -353,9 +350,6 @@ "Compositor:": { "Compositor:": "合成器:" }, - "Configuration activated": { - "Configuration activated": "" - }, "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": { "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": "为已命名工作区配置图标。当数字和图标同时启用时,图标优先生效。" }, @@ -371,9 +365,6 @@ "Connected Displays": { "Connected Displays": "已连接显示器" }, - "Connection failed. Check password and try again.": { - "Connection failed. Check password and try again.": "" - }, "Contrast": { "Contrast": "对比度" }, @@ -494,18 +485,12 @@ "Disconnect": { "Disconnect": "断开连接" }, - "Disconnected from WiFi": { - "Disconnected from WiFi": "" - }, "Disk": { "Disk": "磁盘" }, "Disk Usage": { "Disk Usage": "磁盘占用" }, - "Dismiss": { - "Dismiss": "" - }, "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": { "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "显示一个包含固定和运行中应用的程序坞,可放置在屏幕四边任意位置" }, @@ -608,27 +593,6 @@ "F1/I: Toggle • F10: Help": { "F1/I: Toggle • F10: Help": "F1/I: 切换 • F10: 帮助" }, - "Failed to activate configuration": { - "Failed to activate configuration": "" - }, - "Failed to connect to ": { - "Failed to connect to ": "" - }, - "Failed to disconnect WiFi": { - "Failed to disconnect WiFi": "" - }, - "Failed to enable WiFi": { - "Failed to enable WiFi": "" - }, - "Failed to set profile image": { - "Failed to set profile image": "无法设置个人资料图片" - }, - "Failed to set profile image: ": { - "Failed to set profile image: ": "无法设置个人资料图片:" - }, - "Failed to start connection to ": { - "Failed to start connection to ": "" - }, "Feels Like": { "Feels Like": "体感温度" }, @@ -671,9 +635,6 @@ "Forget Network": { "Forget Network": "取消保存" }, - "Forgot network ": { - "Forgot network ": "" - }, "Format Legend": { "Format Legend": "参考格式" }, @@ -698,9 +659,6 @@ "Gamma Control": { "Gamma Control": "伽马控制" }, - "Gamma control not available. Requires DMS API v6+.": { - "Gamma control not available. Requires DMS API v6+.": "伽玛控制不可用。需要 DMS API v6+。" - }, "Geoclue service not running - cannot auto-detect location": { "Geoclue service not running - cannot auto-detect location": "Geoclue 服务未运行 - 无法自动检测位置" }, @@ -779,9 +737,6 @@ "Include Transitions": { "Include Transitions": "包含过渡效果" }, - "Incorrect password": { - "Incorrect password": "" - }, "Individual Batteries": { "Individual Batteries": "分别显示电池" }, @@ -1115,9 +1070,6 @@ "Percentage": { "Percentage": "占用率" }, - "Permission denied to set profile image.": { - "Permission denied to set profile image.": "因权限问题,无法设置个人资料图片。" - }, "Personalization": { "Personalization": "个性化" }, @@ -1196,12 +1148,6 @@ "Process": { "Process": "进程" }, - "Profile Image Error": { - "Profile Image Error": "个人资料图片错误" - }, - "Profile image is too large. Please use a smaller image.": { - "Profile image is too large. Please use a smaller image.": "个人资料图片太大。请选择更小的图片。" - }, "QML, JavaScript, Go": { "QML, JavaScript, Go": "QML, JavaScript, Go" }, @@ -1328,9 +1274,6 @@ "Select which transitions to include in randomization": { "Select which transitions to include in randomization": "选择在随机轮换中使用的过渡效果" }, - "Selected image file not found.": { - "Selected image file not found.": "找不到选定的图片文件。" - }, "Separator": { "Separator": "分隔符" }, @@ -1601,9 +1544,6 @@ "Use Fahrenheit instead of Celsius for temperature": { "Use Fahrenheit instead of Celsius for temperature": "使用华氏度显示温度" }, - "Use IP Location": { - "Use IP Location": "使用IP所在位置" - }, "Use Monospace Font": { "Use Monospace Font": "使用等宽字体" }, @@ -1685,12 +1625,6 @@ "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": { "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "启用后,应用按字母顺序排序;禁用则按使用频率排序" }, - "WiFi disabled": { - "WiFi disabled": "" - }, - "WiFi enabled": { - "WiFi enabled": "" - }, "WiFi is off": { "WiFi is off": "Wi-Fi 已关闭" }, diff --git a/translations/template.json b/translations/template.json index ed21318c..a82e8493 100644 --- a/translations/template.json +++ b/translations/template.json @@ -349,6 +349,13 @@ "reference": "", "comment": "" }, + { + "term": "Auto-location", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, { "term": "Auto-saving...", "translation": "", @@ -384,13 +391,6 @@ "reference": "", "comment": "" }, - { - "term": "Automatically detect location based on IP address", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Automatically determine your location using your IP address", "translation": "", @@ -818,13 +818,6 @@ "reference": "", "comment": "" }, - { - "term": "Configuration activated", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.", "translation": "", @@ -860,13 +853,6 @@ "reference": "", "comment": "" }, - { - "term": "Connection failed. Check password and try again.", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Contrast", "translation": "", @@ -1147,13 +1133,6 @@ "reference": "", "comment": "" }, - { - "term": "Disconnected from WiFi", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Disk", "translation": "", @@ -1168,13 +1147,6 @@ "reference": "", "comment": "" }, - { - "term": "Dismiss", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen", "translation": "", @@ -1413,34 +1385,6 @@ "reference": "", "comment": "" }, - { - "term": "Failed to activate configuration", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, - { - "term": "Failed to connect to ", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, - { - "term": "Failed to disconnect WiFi", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, - { - "term": "Failed to enable WiFi", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Failed to set profile image", "translation": "", @@ -1455,13 +1399,6 @@ "reference": "", "comment": "" }, - { - "term": "Failed to start connection to ", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Feels Like", "translation": "", @@ -1560,13 +1497,6 @@ "reference": "", "comment": "" }, - { - "term": "Forgot network ", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Format Legend", "translation": "", @@ -1624,7 +1554,7 @@ "comment": "" }, { - "term": "Gamma control not available. Requires DMS API v6+.", + "term": "Geoclue service not running - cannot auto-detect location", "translation": "", "context": "", "reference": "", @@ -1805,13 +1735,6 @@ "reference": "", "comment": "" }, - { - "term": "Incorrect password", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Individual Batteries", "translation": "", @@ -3723,13 +3646,6 @@ "reference": "", "comment": "" }, - { - "term": "Use IP Location", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Use Monospace Font", "translation": "", @@ -3751,6 +3667,13 @@ "reference": "", "comment": "" }, + { + "term": "Use automatic location detection (geoclue2)", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, { "term": "Use custom command for update your system", "translation": "", @@ -3912,20 +3835,6 @@ "reference": "", "comment": "" }, - { - "term": "WiFi disabled", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, - { - "term": "WiFi enabled", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "WiFi is off", "translation": "", diff --git a/wall.patch b/wall.patch new file mode 100644 index 00000000..71d73467 --- /dev/null +++ b/wall.patch @@ -0,0 +1,5884 @@ +diff --git a/.github/workflows/poeditor-export.yml b/.github/workflows/poeditor-export.yml +index 1231465..20f0629 100644 +--- a/.github/workflows/poeditor-export.yml ++++ b/.github/workflows/poeditor-export.yml +@@ -17,91 +17,9 @@ jobs: + steps: + - uses: actions/checkout@v4 + +- - name: Setup Python +- uses: actions/setup-python@v4 +- with: +- python-version: '3.x' +- + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq + +- - name: Extract source strings from codebase +- env: +- API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }} +- PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }} +- run: | +- set -euo pipefail +- +- echo "::group::Extracting strings from QML files" +- python3 translations/extract_translations.py +- echo "::endgroup::" +- +- echo "::group::Checking for changes in en.json" +- if [[ -f "translations/en.json" ]]; then +- jq -S . "translations/en.json" > /tmp/en_new.json +- if [[ -f "translations/en.json.orig" ]]; then +- jq -S . "translations/en.json.orig" > /tmp/en_old.json +- else +- git show HEAD:translations/en.json > /tmp/en_old.json 2>/dev/null || echo "[]" > /tmp/en_old.json +- jq -S . /tmp/en_old.json > /tmp/en_old.json.tmp && mv /tmp/en_old.json.tmp /tmp/en_old.json +- fi +- +- if diff -q /tmp/en_new.json /tmp/en_old.json >/dev/null 2>&1; then +- echo "No changes in source strings" +- echo "source_changed=false" >> "$GITHUB_OUTPUT" +- else +- echo "Detected changes in source strings" +- echo "source_changed=true" >> "$GITHUB_OUTPUT" +- +- echo "::group::Uploading source strings to POEditor" +- RESP=$(curl -sS -X POST https://api.poeditor.com/v2/projects/upload \ +- -F api_token="$API_TOKEN" \ +- -F id="$PROJECT_ID" \ +- -F updating="terms" \ +- -F file=@"translations/en.json") +- +- STATUS=$(echo "$RESP" | jq -r '.response.status') +- if [[ "$STATUS" != "success" ]]; then +- echo "::warning::POEditor upload failed: $RESP" +- else +- TERMS_ADDED=$(echo "$RESP" | jq -r '.result.terms.added // 0') +- TERMS_UPDATED=$(echo "$RESP" | jq -r '.result.terms.updated // 0') +- TERMS_DELETED=$(echo "$RESP" | jq -r '.result.terms.deleted // 0') +- echo "Terms added: $TERMS_ADDED, updated: $TERMS_UPDATED, deleted: $TERMS_DELETED" +- fi +- echo "::endgroup::" +- fi +- else +- echo "::warning::translations/en.json not found" +- echo "source_changed=false" >> "$GITHUB_OUTPUT" +- fi +- echo "::endgroup::" +- id: extract +- +- - name: Commit and push source strings +- if: steps.extract.outputs.source_changed == 'true' +- run: | +- set -euo pipefail +- +- git config user.name "github-actions[bot]" +- git config user.email "github-actions[bot]@users.noreply.github.com" +- +- git add translations/en.json translations/template.json +- git commit -m "i18n: update source strings from codebase" +- +- for attempt in 1 2 3; do +- if git push; then +- echo "Successfully pushed source string updates" +- exit 0 +- fi +- echo "Push attempt $attempt failed, pulling and retrying..." +- git pull --rebase +- sleep $((attempt*2)) +- done +- +- echo "Failed to push after retries" >&2 +- exit 1 +- + - name: Export and update translations from POEditor + env: + API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }} +diff --git a/Common/SettingsData.qml b/Common/SettingsData.qml +index 0b89a6a..c2adee9 100644 +--- a/Common/SettingsData.qml ++++ b/Common/SettingsData.qml +@@ -207,9 +207,6 @@ Singleton { + property bool dankBarAutoHide: false + property bool dankBarOpenOnOverview: false + property bool dankBarVisible: true +- property int overviewRows: 2 +- property int overviewColumns: 5 +- property real overviewScale: 0.16 + property real dankBarSpacing: 4 + property real dankBarBottomGap: 0 + property real dankBarInnerPadding: 4 +diff --git a/Common/Theme.qml b/Common/Theme.qml +index 2ef7434..69cfa16 100644 +--- a/Common/Theme.qml ++++ b/Common/Theme.qml +@@ -441,10 +441,7 @@ Singleton { + if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) + SessionData.setLightMode(light) + if (!isGreeterMode) { +- // Skip with matugen becuase, our script runner will do it. +- if (!matugenAvailable) { +- PortalService.setLightMode(light) +- } ++ PortalService.setLightMode(light) + generateSystemThemesFromCurrentTheme() + } + } +@@ -722,16 +719,15 @@ Singleton { + + Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`]) + workerRunning = true +- const syncModeWithPortal = (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) ? "true" : "false" + if (rawWallpaperPath.startsWith("we:")) { + console.log("Theme: Starting matugen worker (WE wallpaper)") + systemThemeGenerator.command = [ + "sh", "-c", +- `sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' '${syncModeWithPortal}' --run` ++ `sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' --run` + ] + } else { + console.log("Theme: Starting matugen worker") +- systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, syncModeWithPortal, "--run"] ++ systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, "--run"] + } + systemThemeGenerator.running = true + } +diff --git a/DMSGreeter.qml b/DMSGreeter.qml +index 0a1fc86..0f83353 100644 +--- a/DMSGreeter.qml ++++ b/DMSGreeter.qml +@@ -1,11 +1,29 @@ + import QtQuick + import Quickshell ++import Quickshell.Wayland + import Quickshell.Services.Greetd + import qs.Common + import qs.Modules.Greetd + +-Scope { ++Item { + id: root + +- GreeterSurface {} ++ WlSessionLock { ++ id: sessionLock ++ locked: false ++ ++ Component.onCompleted: { ++ Qt.callLater(() => { locked = true }) ++ } ++ ++ onLockedChanged: { ++ if (!locked) { ++ console.log("Greetd session unlocked, exiting") ++ } ++ } ++ ++ GreeterSurface { ++ lock: sessionLock ++ } ++ } + } +diff --git a/DMSShell.qml b/DMSShell.qml +index 7f9a56b..12dbb98 100644 +--- a/DMSShell.qml ++++ b/DMSShell.qml +@@ -22,7 +22,6 @@ import qs.Modules.ProcessList + import qs.Modules.Settings + import qs.Modules.DankBar + import qs.Modules.DankBar.Popouts +-import qs.Modules.HyprWorkspaces + import qs.Modules.Plugins + import qs.Services + +@@ -64,11 +63,8 @@ Item { + + property var currentPosition: SettingsData.dankBarPosition + property bool initialized: false +- property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader + + sourceComponent: DankBar { +- hyprlandOverviewLoader: dankBarLoader.hyprlandOverviewLoaderRef +- + onColorPickerRequested: { + if (colorPickerModal.shouldBeVisible) { + colorPickerModal.close() +@@ -197,19 +193,17 @@ Item { + } + } + +- WifiPasswordModal { +- id: wifiPasswordModal ++ LazyLoader { ++ id: wifiPasswordModalLoader + +- Component.onCompleted: { +- PopoutService.wifiPasswordModal = wifiPasswordModal +- } +- } ++ active: false + +- Connections { +- target: NetworkService ++ WifiPasswordModal { ++ id: wifiPasswordModal + +- function onCredentialsNeeded(token, ssid, setting, fields, hints, reason) { +- wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason) ++ Component.onCompleted: { ++ PopoutService.wifiPasswordModal = wifiPasswordModal ++ } + } + } + +@@ -502,7 +496,6 @@ Item { + notepadSlideoutVariants: notepadSlideoutVariants + hyprKeybindsModalLoader: hyprKeybindsModalLoader + dankBarLoader: dankBarLoader +- hyprlandOverviewLoader: hyprlandOverviewLoader + } + + Variants { +@@ -545,12 +538,4 @@ Item { + modelData: item + } + } +- +- LazyLoader { +- id: hyprlandOverviewLoader +- active: CompositorService.isHyprland +- component: HyprlandOverview { +- id: hyprlandOverview +- } +- } + } +diff --git a/DMSShellIPC.qml b/DMSShellIPC.qml +index abc8474..16cff3d 100644 +--- a/DMSShellIPC.qml ++++ b/DMSShellIPC.qml +@@ -15,7 +15,6 @@ Item { + required property var notepadSlideoutVariants + required property var hyprKeybindsModalLoader + required property var dankBarLoader +- required property var hyprlandOverviewLoader + + IpcHandler { + function open() { +@@ -348,30 +347,6 @@ Item { + return "HYPR_KEYBINDS_TOGGLE_FAILED" + } + +- function toggleOverview(): string { +- if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) { +- return "HYPR_NOT_AVAILABLE" +- } +- root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen +- return root.hyprlandOverviewLoader.item.overviewOpen ? "OVERVIEW_OPEN_SUCCESS" : "OVERVIEW_CLOSE_SUCCESS" +- } +- +- function closeOverview(): string { +- if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) { +- return "HYPR_NOT_AVAILABLE" +- } +- root.hyprlandOverviewLoader.item.overviewOpen = false +- return "OVERVIEW_CLOSE_SUCCESS" +- } +- +- function openOverview(): string { +- if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) { +- return "HYPR_NOT_AVAILABLE" +- } +- root.hyprlandOverviewLoader.item.overviewOpen = true +- return "OVERVIEW_OPEN_SUCCESS" +- } +- + target: "hypr" + } + } +diff --git a/Modals/Spotlight/SpotlightResults.qml b/Modals/Spotlight/SpotlightResults.qml +index a0404e8..449e273 100644 +--- a/Modals/Spotlight/SpotlightResults.qml ++++ b/Modals/Spotlight/SpotlightResults.qml +@@ -7,10 +7,6 @@ import qs.Widgets + Rectangle { + id: resultsContainer + +- // DEVELOPER NOTE: This component renders the Spotlight launcher (accessed via Mod+Space). +- // Changes to launcher behavior, especially item rendering, filtering, or model structure, +- // likely require corresponding updates in Modules/AppDrawer/AppLauncher.qml and vice versa. +- + property var appLauncher: null + property var contextMenu: null + +@@ -94,32 +90,19 @@ Rectangle { + width: resultsList.iconSize + height: resultsList.iconSize + anchors.verticalCenter: parent.verticalCenter +- visible: model.icon !== undefined && model.icon !== "" +- +- property string iconValue: model.icon || "" +- property bool isMaterial: iconValue.indexOf("material:") === 0 +- property string materialName: isMaterial ? iconValue.substring(9) : "" +- +- DankIcon { +- anchors.centerIn: parent +- name: parent.materialName +- size: resultsList.iconSize +- color: Theme.surfaceText +- visible: parent.isMaterial +- } + + IconImage { + id: listIconImg + + anchors.fill: parent +- source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) ++ source: Quickshell.iconPath(model.icon, true) + asynchronous: true +- visible: !parent.isMaterial && status === Image.Ready ++ visible: status === Image.Ready + } + + Rectangle { + anchors.fill: parent +- visible: !parent.isMaterial && !listIconImg.visible ++ visible: !listIconImg.visible + color: Theme.surfaceLight + radius: Theme.cornerRadius + border.width: 1 +@@ -137,7 +120,7 @@ Rectangle { + + Column { + anchors.verticalCenter: parent.verticalCenter +- width: (model.icon !== undefined && model.icon !== "") ? (parent.width - resultsList.iconSize - Theme.spacingL) : parent.width ++ width: parent.width - resultsList.iconSize - Theme.spacingL + spacing: Theme.spacingXS + + StyledText { +@@ -272,33 +255,20 @@ Rectangle { + width: iconSize + height: iconSize + anchors.horizontalCenter: parent.horizontalCenter +- visible: model.icon !== undefined && model.icon !== "" +- +- property string iconValue: model.icon || "" +- property bool isMaterial: iconValue.indexOf("material:") === 0 +- property string materialName: isMaterial ? iconValue.substring(9) : "" +- +- DankIcon { +- anchors.centerIn: parent +- name: parent.materialName +- size: parent.iconSize +- color: Theme.surfaceText +- visible: parent.isMaterial +- } + + IconImage { + id: gridIconImg + + anchors.fill: parent +- source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) ++ source: Quickshell.iconPath(model.icon, true) + smooth: true + asynchronous: true +- visible: !parent.isMaterial && status === Image.Ready ++ visible: status === Image.Ready + } + + Rectangle { + anchors.fill: parent +- visible: !parent.isMaterial && !gridIconImg.visible ++ visible: !gridIconImg.visible + color: Theme.surfaceLight + radius: Theme.cornerRadius + border.width: 1 +diff --git a/Modals/WifiPasswordModal.qml b/Modals/WifiPasswordModal.qml +index dcadbea..4516fe3 100644 +--- a/Modals/WifiPasswordModal.qml ++++ b/Modals/WifiPasswordModal.qml +@@ -15,23 +15,12 @@ DankModal { + property string wifiAnonymousIdentityInput: "" + property string wifiDomainInput: "" + +- property bool isPromptMode: false +- property string promptToken: "" +- property string promptReason: "" +- property var promptFields: [] +- property string promptSetting: "" +- + function show(ssid) { + wifiPasswordSSID = ssid + wifiPasswordInput = "" + wifiUsernameInput = "" + wifiAnonymousIdentityInput = "" + wifiDomainInput = "" +- isPromptMode = false +- promptToken = "" +- promptReason = "" +- promptFields = [] +- promptSetting = "" + + const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid) + requiresEnterprise = network?.enterprise || false +@@ -48,41 +37,6 @@ DankModal { + }) + } + +- function showFromPrompt(token, ssid, setting, fields, hints, reason) { +- wifiPasswordSSID = ssid +- isPromptMode = true +- promptToken = token +- promptReason = reason +- promptFields = fields || [] +- promptSetting = setting || "802-11-wireless-security" +- +- requiresEnterprise = setting === "802-1x" +- +- if (reason === "wrong-password") { +- wifiPasswordInput = "" +- wifiUsernameInput = "" +- } else { +- wifiPasswordInput = "" +- wifiUsernameInput = "" +- wifiAnonymousIdentityInput = "" +- wifiDomainInput = "" +- } +- +- open() +- Qt.callLater(() => { +- if (contentLoader.item) { +- if (reason === "wrong-password" && contentLoader.item.passwordInput) { +- contentLoader.item.passwordInput.text = "" +- contentLoader.item.passwordInput.forceActiveFocus() +- } else if (requiresEnterprise && contentLoader.item.usernameInput) { +- contentLoader.item.usernameInput.forceActiveFocus() +- } else if (contentLoader.item.passwordInput) { +- contentLoader.item.passwordInput.forceActiveFocus() +- } +- } +- }) +- } +- + shouldBeVisible: false + width: 420 + height: requiresEnterprise ? 430 : 230 +@@ -106,9 +60,6 @@ DankModal { + }) + } + onBackgroundClicked: () => { +- if (isPromptMode) { +- NetworkService.cancelCredentials(promptToken) +- } + close() + wifiPasswordInput = "" + wifiUsernameInput = "" +@@ -139,9 +90,6 @@ DankModal { + anchors.fill: parent + focus: true + Keys.onEscapePressed: event => { +- if (isPromptMode) { +- NetworkService.cancelCredentials(promptToken) +- } + close() + wifiPasswordInput = "" + wifiUsernameInput = "" +@@ -169,28 +117,12 @@ DankModal { + font.weight: Font.Medium + } + +- Column { ++ StyledText { ++ text: requiresEnterprise ? I18n.tr("Enter credentials for ") + wifiPasswordSSID : I18n.tr("Enter password for ") + wifiPasswordSSID ++ font.pixelSize: Theme.fontSizeMedium ++ color: Theme.surfaceTextMedium + width: parent.width +- spacing: Theme.spacingXS +- +- StyledText { +- text: { +- const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for ") +- return prefix + wifiPasswordSSID +- } +- font.pixelSize: Theme.fontSizeMedium +- color: Theme.surfaceTextMedium +- width: parent.width +- elide: Text.ElideRight +- } +- +- StyledText { +- visible: isPromptMode && promptReason === "wrong-password" +- text: I18n.tr("Incorrect password") +- font.pixelSize: Theme.fontSizeSmall +- color: Theme.error +- width: parent.width +- } ++ elide: Text.ElideRight + } + } + +@@ -199,9 +131,6 @@ DankModal { + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: () => { +- if (isPromptMode) { +- NetworkService.cancelCredentials(promptToken) +- } + close() + wifiPasswordInput = "" + wifiUsernameInput = "" +@@ -279,26 +208,14 @@ DankModal { + wifiPasswordInput = text + } + onAccepted: () => { +- if (isPromptMode) { +- const secrets = {} +- if (promptSetting === "802-11-wireless-security") { +- secrets["psk"] = passwordInput.text +- } else if (promptSetting === "802-1x") { +- if (usernameInput.text) secrets["identity"] = usernameInput.text +- if (passwordInput.text) secrets["password"] = passwordInput.text +- if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput +- } +- NetworkService.submitCredentials(promptToken, secrets, true) +- } else { +- const username = requiresEnterprise ? usernameInput.text : "" +- NetworkService.connectToWifi( +- wifiPasswordSSID, +- passwordInput.text, +- username, +- wifiAnonymousIdentityInput, +- wifiDomainInput +- ) +- } ++ const username = requiresEnterprise ? usernameInput.text : "" ++ NetworkService.connectToWifi( ++ wifiPasswordSSID, ++ passwordInput.text, ++ username, ++ wifiAnonymousIdentityInput, ++ wifiDomainInput ++ ) + close() + wifiPasswordInput = "" + wifiUsernameInput = "" +@@ -478,9 +395,6 @@ DankModal { + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: () => { +- if (isPromptMode) { +- NetworkService.cancelCredentials(promptToken) +- } + close() + wifiPasswordInput = "" + wifiUsernameInput = "" +@@ -516,26 +430,14 @@ DankModal { + cursorShape: Qt.PointingHandCursor + enabled: parent.enabled + onClicked: () => { +- if (isPromptMode) { +- const secrets = {} +- if (promptSetting === "802-11-wireless-security") { +- secrets["psk"] = passwordInput.text +- } else if (promptSetting === "802-1x") { +- if (usernameInput.text) secrets["identity"] = usernameInput.text +- if (passwordInput.text) secrets["password"] = passwordInput.text +- if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput +- } +- NetworkService.submitCredentials(promptToken, secrets, true) +- } else { +- const username = requiresEnterprise ? usernameInput.text : "" +- NetworkService.connectToWifi( +- wifiPasswordSSID, +- passwordInput.text, +- username, +- wifiAnonymousIdentityInput, +- wifiDomainInput +- ) +- } ++ const username = requiresEnterprise ? usernameInput.text : "" ++ NetworkService.connectToWifi( ++ wifiPasswordSSID, ++ passwordInput.text, ++ username, ++ wifiAnonymousIdentityInput, ++ wifiDomainInput ++ ) + close() + wifiPasswordInput = "" + wifiUsernameInput = "" +diff --git a/Modules/AppDrawer/AppDrawerPopout.qml b/Modules/AppDrawer/AppDrawerPopout.qml +index a1d36b9..ecbf326 100644 +--- a/Modules/AppDrawer/AppDrawerPopout.qml ++++ b/Modules/AppDrawer/AppDrawerPopout.qml +@@ -404,29 +404,16 @@ DankPopout { + width: appList.iconSize + height: appList.iconSize + anchors.verticalCenter: parent.verticalCenter +- visible: model.icon !== undefined && model.icon !== "" +- +- property string iconValue: model.icon || "" +- property bool isMaterial: iconValue.indexOf("material:") === 0 +- property string materialName: isMaterial ? iconValue.substring(9) : "" +- +- DankIcon { +- anchors.centerIn: parent +- name: parent.materialName +- size: appList.iconSize - Theme.spacingM +- color: Theme.surfaceText +- visible: parent.isMaterial +- } + + IconImage { + id: listIconImg + + anchors.fill: parent + anchors.margins: Theme.spacingXS +- source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) ++ source: Quickshell.iconPath(model.icon, true) + smooth: true + asynchronous: true +- visible: !parent.isMaterial && status === Image.Ready ++ visible: status === Image.Ready + } + + Rectangle { +@@ -434,7 +421,7 @@ DankPopout { + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.bottomMargin: Theme.spacingM +- visible: !parent.isMaterial && listIconImg.status !== Image.Ready ++ visible: !listIconImg.visible + color: Theme.surfaceLight + radius: Theme.cornerRadius + border.width: 0 +@@ -448,12 +435,11 @@ DankPopout { + font.weight: Font.Bold + } + } +- + } + + Column { + anchors.verticalCenter: parent.verticalCenter +- width: (model.icon !== undefined && model.icon !== "") ? (parent.width - appList.iconSize - Theme.spacingL) : parent.width ++ width: parent.width - appList.iconSize - Theme.spacingL + spacing: Theme.spacingXS + + StyledText { +@@ -527,7 +513,6 @@ DankPopout { + property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns + property int baseCellHeight: baseCellWidth + 20 + property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns +- + property int remainingSpace: width - (actualColumns * cellWidth) + + signal keyboardNavigationReset +@@ -593,19 +578,6 @@ DankPopout { + width: iconSize + height: iconSize + anchors.horizontalCenter: parent.horizontalCenter +- visible: model.icon !== undefined && model.icon !== "" +- +- property string iconValue: model.icon || "" +- property bool isMaterial: iconValue.indexOf("material:") === 0 +- property string materialName: isMaterial ? iconValue.substring(9) : "" +- +- DankIcon { +- anchors.centerIn: parent +- name: parent.materialName +- size: parent.iconSize - Theme.spacingL +- color: Theme.surfaceText +- visible: parent.isMaterial +- } + + IconImage { + id: gridIconImg +@@ -614,10 +586,10 @@ DankPopout { + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.bottomMargin: Theme.spacingS +- source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true) ++ source: Quickshell.iconPath(model.icon, true) + smooth: true + asynchronous: true +- visible: !parent.isMaterial && status === Image.Ready ++ visible: status === Image.Ready + } + + Rectangle { +@@ -625,7 +597,7 @@ DankPopout { + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.bottomMargin: Theme.spacingS +- visible: !parent.isMaterial && gridIconImg.status !== Image.Ready ++ visible: !gridIconImg.visible + color: Theme.surfaceLight + radius: Theme.cornerRadius + border.width: 0 +diff --git a/Modules/AppDrawer/AppLauncher.qml b/Modules/AppDrawer/AppLauncher.qml +index 0cd13b5..b5c5f0a 100644 +--- a/Modules/AppDrawer/AppLauncher.qml ++++ b/Modules/AppDrawer/AppLauncher.qml +@@ -8,10 +8,6 @@ import qs.Widgets + Item { + id: root + +- // DEVELOPER NOTE: This component manages the AppDrawer launcher (accessed via DankBar icon). +- // Changes to launcher behavior, especially item rendering, filtering, or model structure, +- // likely require corresponding updates in Modals/Spotlight/SpotlightResults.qml and vice versa. +- + property string searchQuery: "" + property string selectedCategory: I18n.tr("All") + property string viewMode: "list" // "list" or "grid" +@@ -167,7 +163,7 @@ Item { + filteredModel.append({ + "name": app.name || "", + "exec": app.execString || app.exec || app.action || "", +- "icon": app.icon !== undefined ? app.icon : (isPluginItem ? "" : "application-x-executable"), ++ "icon": app.icon || "application-x-executable", + "comment": app.comment || "", + "categories": app.categories || [], + "isPlugin": isPluginItem, +diff --git a/Modules/ControlCenter/Details/NetworkDetail.qml b/Modules/ControlCenter/Details/NetworkDetail.qml +index 43a1d75..3e2188b 100644 +--- a/Modules/ControlCenter/Details/NetworkDetail.qml ++++ b/Modules/ControlCenter/Details/NetworkDetail.qml +@@ -509,11 +509,7 @@ Rectangle { + onClicked: function(event) { + if (modelData.ssid !== NetworkService.currentWifiSSID) { + if (modelData.secured && !modelData.saved) { +- if (DMSService.apiVersion >= 7) { +- NetworkService.connectToWifi(modelData.ssid) +- } else if (PopoutService.wifiPasswordModal) { +- PopoutService.wifiPasswordModal.show(modelData.ssid) +- } ++ wifiPasswordModal.show(modelData.ssid) + } else { + NetworkService.connectToWifi(modelData.ssid) + } +@@ -567,11 +563,7 @@ Rectangle { + NetworkService.disconnectWifi() + } else { + if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) { +- if (DMSService.apiVersion >= 7) { +- NetworkService.connectToWifi(networkContextMenu.currentSSID) +- } else if (PopoutService.wifiPasswordModal) { +- PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID) +- } ++ wifiPasswordModal.show(networkContextMenu.currentSSID) + } else { + NetworkService.connectToWifi(networkContextMenu.currentSSID) + } +@@ -626,6 +618,10 @@ Rectangle { + } + } + ++ WifiPasswordModal { ++ id: wifiPasswordModal ++ } ++ + NetworkInfoModal { + id: networkInfoModal + } +diff --git a/Modules/DankBar/DankBar.qml b/Modules/DankBar/DankBar.qml +index b41b91e..4cfd098 100644 +--- a/Modules/DankBar/DankBar.qml ++++ b/Modules/DankBar/DankBar.qml +@@ -23,7 +23,6 @@ Item { + signal colorPickerRequested + + property alias barVariants: barVariants +- property var hyprlandOverviewLoader: null + + function triggerControlCenterOnFocusedScreen() { + let focusedScreenName = "" +@@ -49,6 +48,30 @@ Item { + return false + } + ++ function triggerWallpaperBrowserOnFocusedScreen() { ++ let focusedScreenName = "" ++ if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) { ++ focusedScreenName = Hyprland.focusedWorkspace.monitor.name ++ } else if (CompositorService.isNiri && NiriService.currentOutput) { ++ focusedScreenName = NiriService.currentOutput ++ } ++ ++ if (!focusedScreenName && barVariants.instances.length > 0) { ++ const firstBar = barVariants.instances[0] ++ firstBar.triggerWallpaperBrowser() ++ return true ++ } ++ ++ for (var i = 0; i < barVariants.instances.length; i++) { ++ const barInstance = barVariants.instances[i] ++ if (barInstance.modelData && barInstance.modelData.name === focusedScreenName) { ++ barInstance.triggerWallpaperBrowser() ++ return true ++ } ++ } ++ return false ++ } ++ + Variants { + id: barVariants + model: SettingsData.getFilteredScreens("dankBar") +@@ -57,6 +80,7 @@ Item { + id: barWindow + + property var controlCenterButtonRef: null ++ property var clockButtonRef: null + + function triggerControlCenter() { + controlCenterLoader.active = true +@@ -79,6 +103,27 @@ Item { + } + } + ++ function triggerWallpaperBrowser() { ++ dankDashPopoutLoader.active = true ++ if (!dankDashPopoutLoader.item) { ++ return ++ } ++ ++ if (clockButtonRef && dankDashPopoutLoader.item.setTriggerPosition) { ++ const globalPos = clockButtonRef.mapToGlobal(0, 0) ++ const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, clockButtonRef.width) ++ const section = clockButtonRef.section || "center" ++ dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen) ++ } else { ++ dankDashPopoutLoader.item.triggerScreen = barWindow.screen ++ } ++ ++ if (!dankDashPopoutLoader.item.dashVisible) { ++ dankDashPopoutLoader.item.currentTabIndex = 2 ++ } ++ dankDashPopoutLoader.item.dashVisible = !dankDashPopoutLoader.item.dashVisible ++ } ++ + readonly property var dBarLayer: { + switch (Quickshell.env("DMS_DANKBAR_LAYER")) { + case "bottom": +@@ -773,7 +818,6 @@ Item { + section: topBarContent.getWidgetSection(parent) + popupTarget: appDrawerLoader.item + parentScreen: barWindow.screen +- hyprlandOverviewLoader: root.hyprlandOverviewLoader + onClicked: { + appDrawerLoader.active = true + appDrawerLoader.item?.toggle() +@@ -787,7 +831,6 @@ Item { + WorkspaceSwitcher { + screenName: barWindow.screenName + widgetHeight: barWindow.widgetThickness +- hyprlandOverviewLoader: root.hyprlandOverviewLoader + } + } + +@@ -825,6 +868,17 @@ Item { + return dankDashPopoutLoader.item + } + parentScreen: barWindow.screen ++ ++ Component.onCompleted: { ++ barWindow.clockButtonRef = this ++ } ++ ++ Component.onDestruction: { ++ if (barWindow.clockButtonRef === this) { ++ barWindow.clockButtonRef = null ++ } ++ } ++ + onClockClicked: { + dankDashPopoutLoader.active = true + if (dankDashPopoutLoader.item) { +@@ -874,7 +928,7 @@ Item { + dankDashPopoutLoader.active = true + if (dankDashPopoutLoader.item) { + dankDashPopoutLoader.item.dashVisible = !dankDashPopoutLoader.item.dashVisible +- dankDashPopoutLoader.item.currentTabIndex = 2 ++ dankDashPopoutLoader.item.currentTabIndex = 3 + } + } + } +@@ -1208,4 +1262,15 @@ Item { + } + } + } ++ ++ IpcHandler { ++ target: "dankdash" ++ ++ function wallpaper(): string { ++ if (root.triggerWallpaperBrowserOnFocusedScreen()) { ++ return "SUCCESS: Toggled wallpaper browser" ++ } ++ return "ERROR: Failed to toggle wallpaper browser" ++ } ++ } + } +diff --git a/Modules/DankBar/Widgets/FocusedApp.qml b/Modules/DankBar/Widgets/FocusedApp.qml +index 327c833..8765f48 100644 +--- a/Modules/DankBar/Widgets/FocusedApp.qml ++++ b/Modules/DankBar/Widgets/FocusedApp.qml +@@ -74,20 +74,14 @@ Rectangle { + return false + } + +- try { +- if (!Hyprland.toplevels) return false +- const hyprlandToplevels = Array.from(Hyprland.toplevels.values) +- const activeHyprToplevel = hyprlandToplevels.find(t => t?.wayland === activeWindow) ++ const hyprlandToplevels = Array.from(Hyprland.toplevels.values) ++ const activeHyprToplevel = hyprlandToplevels.find(t => t.wayland === activeWindow) + +- if (!activeHyprToplevel || !activeHyprToplevel.workspace) { +- return false +- } +- +- return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id +- } catch (e) { +- console.error("FocusedApp: hasWindowsOnCurrentWorkspace error:", e) ++ if (!activeHyprToplevel || !activeHyprToplevel.workspace) { + return false + } ++ ++ return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id + } + + return activeWindow && activeWindow.title +diff --git a/Modules/DankBar/Widgets/LauncherButton.qml b/Modules/DankBar/Widgets/LauncherButton.qml +index 685c6d4..f5e0526 100644 +--- a/Modules/DankBar/Widgets/LauncherButton.qml ++++ b/Modules/DankBar/Widgets/LauncherButton.qml +@@ -17,7 +17,6 @@ Item { + property var parentScreen: null + property real widgetThickness: 30 + property real barThickness: 48 +- property var hyprlandOverviewLoader: null + readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30)) + + signal clicked() +@@ -36,8 +35,6 @@ Item { + if (mouse.button === Qt.RightButton) { + if (CompositorService.isNiri) { + NiriService.toggleOverview() +- } else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) { +- root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen + } + return + } +diff --git a/Modules/DankBar/Widgets/RunningApps.qml b/Modules/DankBar/Widgets/RunningApps.qml +index dfe0413..a3426b7 100644 +--- a/Modules/DankBar/Widgets/RunningApps.qml ++++ b/Modules/DankBar/Widgets/RunningApps.qml +@@ -30,28 +30,22 @@ Rectangle { + if (!SettingsData.runningAppsGroupByApp) { + return []; + } +- try { +- const appGroups = new Map(); +- sortedToplevels.forEach((toplevel, index) => { +- if (!toplevel) return; +- const appId = toplevel?.appId || "unknown"; +- if (!appGroups.has(appId)) { +- appGroups.set(appId, { +- appId: appId, +- windows: [] +- }); +- } +- appGroups.get(appId).windows.push({ +- toplevel: toplevel, +- windowId: index, +- windowTitle: toplevel?.title || "(Unnamed)" ++ const appGroups = new Map(); ++ sortedToplevels.forEach((toplevel, index) => { ++ const appId = toplevel.appId || "unknown"; ++ if (!appGroups.has(appId)) { ++ appGroups.set(appId, { ++ appId: appId, ++ windows: [] + }); ++ } ++ appGroups.get(appId).windows.push({ ++ toplevel: toplevel, ++ windowId: index, ++ windowTitle: toplevel.title || "(Unnamed)" + }); +- return Array.from(appGroups.values()); +- } catch (e) { +- console.error("RunningApps: groupedWindows error:", e); +- return []; +- } ++ }); ++ return Array.from(appGroups.values()); + } + readonly property int windowCount: SettingsData.runningAppsGroupByApp ? groupedWindows.length : sortedToplevels.length + readonly property int calculatedSize: { +diff --git a/Modules/DankBar/Widgets/WorkspaceSwitcher.qml b/Modules/DankBar/Widgets/WorkspaceSwitcher.qml +index f1432a1..5afc27c 100644 +--- a/Modules/DankBar/Widgets/WorkspaceSwitcher.qml ++++ b/Modules/DankBar/Widgets/WorkspaceSwitcher.qml +@@ -15,7 +15,6 @@ Rectangle { + property string screenName: "" + property real widgetHeight: 30 + property real barThickness: 48 +- property var hyprlandOverviewLoader: null + readonly property var sortedToplevels: { + return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, parentScreen?.name); + } +@@ -245,17 +244,11 @@ Rectangle { + + MouseArea { + anchors.fill: parent +- acceptedButtons: Qt.RightButton ++ acceptedButtons: Qt.NoButton + + property real scrollAccumulator: 0 + property real touchpadThreshold: 500 + +- onClicked: mouse => { +- if (mouse.button === Qt.RightButton && CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) { +- root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen +- } +- } +- + onWheel: wheel => { + const deltaY = wheel.angleDelta.y + const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0 +diff --git a/Modules/DankDash/DankDashPopout.qml b/Modules/DankDash/DankDashPopout.qml +index f65f2c9..5fbcf37 100644 +--- a/Modules/DankDash/DankDashPopout.qml ++++ b/Modules/DankDash/DankDashPopout.qml +@@ -16,6 +16,8 @@ DankPopout { + property var triggerScreen: null + property int currentTabIndex: 0 + ++ keyboardFocusMode: WlrKeyboardFocus.Exclusive ++ + function setTriggerPosition(x, y, width, section, screen) { + triggerSection = section + triggerScreen = screen +@@ -43,15 +45,49 @@ DankPopout { + shouldBeVisible: dashVisible + visible: shouldBeVisible + ++ property bool __focusArmed: false ++ property bool __contentReady: false ++ ++ function __tryFocusOnce() { ++ if (!__focusArmed) return ++ const win = root.window ++ if (!win || !win.visible) return ++ if (!contentLoader.item) return ++ ++ if (win.requestActivate) win.requestActivate() ++ contentLoader.item.forceActiveFocus(Qt.TabFocusReason) ++ ++ if (contentLoader.item.activeFocus) ++ __focusArmed = false ++ } + + onDashVisibleChanged: { + if (dashVisible) { ++ __focusArmed = true ++ __contentReady = !!contentLoader.item + open() ++ __tryFocusOnce() + } else { ++ __focusArmed = false ++ __contentReady = false + close() + } + } + ++ Connections { ++ target: contentLoader ++ function onLoaded() { ++ __contentReady = true ++ if (__focusArmed) __tryFocusOnce() ++ } ++ } ++ ++ Connections { ++ target: root.window ? root.window : null ++ enabled: !!root.window ++ function onVisibleChanged() { if (__focusArmed) __tryFocusOnce() } ++ } ++ + onBackgroundClicked: { + dashVisible = false + } +@@ -67,18 +103,12 @@ DankPopout { + + Component.onCompleted: { + if (root.shouldBeVisible) { +- forceActiveFocus() +- } +- } +- +- Keys.onPressed: function(event) { +- if (event.key === Qt.Key_Escape) { +- root.dashVisible = false +- event.accepted = true ++ mainContainer.forceActiveFocus() + } + } + + Connections { ++ target: root + function onShouldBeVisibleChanged() { + if (root.shouldBeVisible) { + Qt.callLater(function() { +@@ -86,7 +116,52 @@ DankPopout { + }) + } + } +- target: root ++ } ++ ++ Keys.onPressed: function(event) { ++ if (event.key === Qt.Key_Escape) { ++ root.dashVisible = false ++ event.accepted = true ++ return ++ } ++ ++ if (event.key === Qt.Key_Tab && !(event.modifiers & Qt.ShiftModifier)) { ++ let nextIndex = root.currentTabIndex + 1 ++ while (nextIndex < tabBar.model.length && tabBar.model[nextIndex] && tabBar.model[nextIndex].isAction) { ++ nextIndex++ ++ } ++ if (nextIndex >= tabBar.model.length) { ++ nextIndex = 0 ++ } ++ root.currentTabIndex = nextIndex ++ event.accepted = true ++ return ++ } ++ ++ if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) { ++ let prevIndex = root.currentTabIndex - 1 ++ while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) { ++ prevIndex-- ++ } ++ if (prevIndex < 0) { ++ prevIndex = tabBar.model.length - 1 ++ while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) { ++ prevIndex-- ++ } ++ } ++ if (prevIndex >= 0) { ++ root.currentTabIndex = prevIndex ++ } ++ event.accepted = true ++ return ++ } ++ ++ if (root.currentTabIndex === 2 && wallpaperTab.handleKeyEvent) { ++ if (wallpaperTab.handleKeyEvent(event)) { ++ event.accepted = true ++ return ++ } ++ } + } + + Rectangle { +@@ -128,11 +203,23 @@ DankPopout { + currentIndex: root.currentTabIndex + spacing: Theme.spacingS + equalWidthTabs: true ++ enableArrowNavigation: false ++ focus: false ++ activeFocusOnTab: false ++ nextFocusTarget: { ++ const item = pages.currentItem ++ if (!item) ++ return null ++ if (item.focusTarget) ++ return item.focusTarget ++ return item ++ } + + model: { + let tabs = [ + { icon: "dashboard", text: I18n.tr("Overview") }, +- { icon: "music_note", text: I18n.tr("Media") } ++ { icon: "music_note", text: I18n.tr("Media") }, ++ { icon: "wallpaper", text: I18n.tr("Wallpapers") } + ] + + if (SettingsData.weatherEnabled) { +@@ -148,7 +235,7 @@ DankPopout { + } + + onActionTriggered: function(index) { +- let settingsIndex = SettingsData.weatherEnabled ? 3 : 2 ++ let settingsIndex = SettingsData.weatherEnabled ? 4 : 3 + if (index === settingsIndex) { + dashVisible = false + settingsModal.show() +@@ -168,7 +255,8 @@ DankPopout { + implicitHeight: { + if (currentIndex === 0) return overviewTab.implicitHeight + if (currentIndex === 1) return mediaTab.implicitHeight +- if (SettingsData.weatherEnabled && currentIndex === 2) return weatherTab.implicitHeight ++ if (currentIndex === 2) return wallpaperTab.implicitHeight ++ if (SettingsData.weatherEnabled && currentIndex === 3) return weatherTab.implicitHeight + return overviewTab.implicitHeight + } + currentIndex: root.currentTabIndex +@@ -178,8 +266,8 @@ DankPopout { + + onSwitchToWeatherTab: { + if (SettingsData.weatherEnabled) { +- tabBar.currentIndex = 2 +- tabBar.tabClicked(2) ++ tabBar.currentIndex = 3 ++ tabBar.tabClicked(3) + } + } + +@@ -193,9 +281,16 @@ DankPopout { + id: mediaTab + } + ++ WallpaperTab { ++ id: wallpaperTab ++ active: root.currentTabIndex === 2 ++ tabBarItem: tabBar ++ keyForwardTarget: mainContainer ++ } ++ + WeatherTab { + id: weatherTab +- visible: SettingsData.weatherEnabled && root.currentTabIndex === 2 ++ visible: SettingsData.weatherEnabled && root.currentTabIndex === 3 + } + } + } +diff --git a/Modules/DankDash/WallpaperTab.qml b/Modules/DankDash/WallpaperTab.qml +new file mode 100644 +index 0000000..b650808 +--- /dev/null ++++ b/Modules/DankDash/WallpaperTab.qml +@@ -0,0 +1,523 @@ ++import Qt.labs.folderlistmodel ++import QtQuick ++import QtQuick.Controls ++import QtQuick.Effects ++import QtQuick.Layouts ++import Quickshell ++import Quickshell.Io ++import qs.Common ++import qs.Modals.FileBrowser ++import qs.Services ++import qs.Widgets ++ ++Item { ++ id: root ++ ++ implicitWidth: 700 ++ implicitHeight: 410 ++ ++ property var wallpaperList: [] ++ property string wallpaperDir: "" ++ property int currentPage: 0 ++ property int itemsPerPage: 16 ++ property int totalPages: Math.max(1, Math.ceil(wallpaperList.length / itemsPerPage)) ++ property bool active: false ++ property Item focusTarget: wallpaperGrid ++ property Item tabBarItem: null ++ property int gridIndex: 0 ++ property Item keyForwardTarget: null ++ property int lastPage: 0 ++ property bool enableAnimation: false ++ ++ signal requestTabChange(int newIndex) ++ ++ onCurrentPageChanged: { ++ if (currentPage !== lastPage) { ++ enableAnimation = false ++ lastPage = currentPage ++ } ++ } ++ ++ onVisibleChanged: { ++ if (visible && active) { ++ setInitialSelection() ++ } ++ } ++ ++ Component.onCompleted: { ++ loadWallpapers() ++ if (visible && active) { ++ setInitialSelection() ++ } ++ } ++ ++ onActiveChanged: { ++ if (active && visible) { ++ setInitialSelection() ++ } ++ } ++ ++ function handleKeyEvent(event) { ++ const columns = 4 ++ const rows = 4 ++ const currentRow = Math.floor(gridIndex / columns) ++ const currentCol = gridIndex % columns ++ const visibleCount = wallpaperGrid.model.length ++ ++ if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { ++ if (gridIndex >= 0) { ++ const item = wallpaperGrid.currentItem ++ if (item && item.wallpaperPath) { ++ SessionData.setWallpaper(item.wallpaperPath) ++ } ++ } ++ return true ++ } ++ ++ if (event.key === Qt.Key_Right) { ++ if (gridIndex + 1 < visibleCount) { ++ // Move right within current page ++ gridIndex++ ++ } else if (gridIndex === visibleCount - 1 && currentPage < totalPages - 1) { ++ // At last item in page, go to next page ++ gridIndex = 0 ++ currentPage++ ++ } ++ return true ++ } ++ ++ if (event.key === Qt.Key_Left) { ++ if (gridIndex > 0) { ++ // Move left within current page ++ gridIndex-- ++ } else if (gridIndex === 0 && currentPage > 0) { ++ // At first item in page, go to previous page (last item) ++ currentPage-- ++ gridIndex = Math.min(itemsPerPage - 1, wallpaperList.length - currentPage * itemsPerPage - 1) ++ } ++ return true ++ } ++ ++ if (event.key === Qt.Key_Down) { ++ if (gridIndex + columns < visibleCount) { ++ // Move down within current page ++ gridIndex += columns ++ } else if (gridIndex >= visibleCount - columns && currentPage < totalPages - 1) { ++ // In last row, go to next page ++ gridIndex = currentCol ++ currentPage++ ++ } ++ return true ++ } ++ ++ if (event.key === Qt.Key_Up) { ++ if (gridIndex >= columns) { ++ // Move up within current page ++ gridIndex -= columns ++ } else if (gridIndex < columns && currentPage > 0) { ++ // In first row, go to previous page (last row) ++ currentPage-- ++ const prevPageCount = Math.min(itemsPerPage, wallpaperList.length - currentPage * itemsPerPage) ++ const prevPageRows = Math.ceil(prevPageCount / columns) ++ gridIndex = (prevPageRows - 1) * columns + currentCol ++ gridIndex = Math.min(gridIndex, prevPageCount - 1) ++ } ++ return true ++ } ++ ++ if (event.key === Qt.Key_PageUp && currentPage > 0) { ++ gridIndex = 0 ++ currentPage-- ++ return true ++ } ++ ++ if (event.key === Qt.Key_PageDown && currentPage < totalPages - 1) { ++ gridIndex = 0 ++ currentPage++ ++ return true ++ } ++ ++ if (event.key === Qt.Key_Home && event.modifiers & Qt.ControlModifier) { ++ gridIndex = 0 ++ currentPage = 0 ++ return true ++ } ++ ++ if (event.key === Qt.Key_End && event.modifiers & Qt.ControlModifier) { ++ gridIndex = 0 ++ currentPage = totalPages - 1 ++ return true ++ } ++ ++ return false ++ } ++ ++ function setInitialSelection() { ++ if (!SessionData.wallpaperPath) { ++ gridIndex = 0 ++ return ++ } ++ ++ const startIndex = currentPage * itemsPerPage ++ const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length) ++ const pageWallpapers = wallpaperList.slice(startIndex, endIndex) ++ ++ for (let i = 0; i < pageWallpapers.length; i++) { ++ if (pageWallpapers[i] === SessionData.wallpaperPath) { ++ gridIndex = i ++ return ++ } ++ } ++ gridIndex = 0 ++ } ++ ++ onWallpaperListChanged: { ++ if (visible && active) { ++ setInitialSelection() ++ } ++ } ++ ++ function loadWallpapers() { ++ const currentWallpaper = SessionData.wallpaperPath ++ ++ // Try current wallpaper path / fallback to wallpaperLastPath ++ if (!currentWallpaper || currentWallpaper.startsWith("#") || currentWallpaper.startsWith("we:")) { ++ if (CacheData.wallpaperLastPath && CacheData.wallpaperLastPath !== "") { ++ wallpaperDir = CacheData.wallpaperLastPath ++ } else { ++ wallpaperDir = "" ++ wallpaperList = [] ++ } ++ return ++ } ++ ++ wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/')) ++ } ++ ++ function updateWallpaperList() { ++ if (!wallpaperFolderModel || wallpaperFolderModel.count === 0) { ++ wallpaperList = [] ++ currentPage = 0 ++ gridIndex = 0 ++ return ++ } ++ ++ // Build list from FolderListModel ++ const files = [] ++ for (let i = 0; i < wallpaperFolderModel.count; i++) { ++ const filePath = wallpaperFolderModel.get(i, "filePath") ++ if (filePath) { ++ // Remove file:// prefix if present ++ const cleanPath = filePath.toString().replace(/^file:\/\//, '') ++ files.push(cleanPath) ++ } ++ } ++ ++ wallpaperList = files ++ ++ const currentPath = SessionData.wallpaperPath ++ const selectedIndex = currentPath ? wallpaperList.indexOf(currentPath) : -1 ++ ++ if (selectedIndex >= 0) { ++ currentPage = Math.floor(selectedIndex / itemsPerPage) ++ gridIndex = selectedIndex % itemsPerPage ++ } else { ++ const maxPage = Math.max(0, Math.ceil(files.length / itemsPerPage) - 1) ++ currentPage = Math.min(Math.max(0, currentPage), maxPage) ++ gridIndex = 0 ++ } ++ } ++ ++ Connections { ++ target: SessionData ++ function onWallpaperPathChanged() { ++ loadWallpapers() ++ } ++ } ++ ++ FolderListModel { ++ id: wallpaperFolderModel ++ ++ showDirsFirst: false ++ showDotAndDotDot: false ++ showHidden: false ++ nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] ++ showFiles: true ++ showDirs: false ++ sortField: FolderListModel.Name ++ folder: wallpaperDir ? "file://" + wallpaperDir : "" ++ ++ onStatusChanged: { ++ if (status === FolderListModel.Ready) { ++ updateWallpaperList() ++ } ++ } ++ ++ onCountChanged: { ++ if (status === FolderListModel.Ready) { ++ updateWallpaperList() ++ } ++ } ++ } ++ ++ Loader { ++ id: wallpaperBrowserLoader ++ active: false ++ asynchronous: true ++ ++ sourceComponent: FileBrowserModal { ++ Component.onCompleted: { ++ open() ++ } ++ browserTitle: "Select Wallpaper Directory" ++ browserIcon: "folder_open" ++ browserType: "wallpaper" ++ showHiddenFiles: false ++ fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] ++ allowStacking: true ++ ++ onFileSelected: (path) => { ++ // Set the selected wallpaper ++ const cleanPath = path.replace(/^file:\/\//, '') ++ SessionData.setWallpaper(cleanPath) ++ ++ // Extract directory from the selected file and load all wallpapers ++ const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/')) ++ if (dirPath) { ++ wallpaperDir = dirPath ++ CacheData.wallpaperLastPath = dirPath ++ CacheData.saveCache() ++ } ++ close() ++ } ++ ++ onDialogClosed: { ++ Qt.callLater(() => wallpaperBrowserLoader.active = false) ++ } ++ } ++ } ++ ++ Column { ++ anchors.fill: parent ++ spacing: 0 ++ ++ Item { ++ width: parent.width ++ height: parent.height - 50 ++ ++ GridView { ++ id: wallpaperGrid ++ anchors.centerIn: parent ++ width: parent.width - Theme.spacingS ++ height: parent.height - Theme.spacingS ++ cellWidth: width / 4 ++ cellHeight: height / 4 ++ clip: true ++ enabled: root.active ++ interactive: root.active ++ boundsBehavior: Flickable.StopAtBounds ++ keyNavigationEnabled: false ++ activeFocusOnTab: false ++ highlightFollowsCurrentItem: true ++ highlightMoveDuration: enableAnimation ? Theme.shortDuration : 0 ++ focus: false ++ ++ highlight: Item { ++ z: 1000 ++ Rectangle { ++ anchors.fill: parent ++ anchors.margins: Theme.spacingXS ++ color: "transparent" ++ border.width: 3 ++ border.color: Theme.primary ++ radius: Theme.cornerRadius ++ } ++ } ++ ++ model: { ++ const startIndex = currentPage * itemsPerPage ++ const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length) ++ return wallpaperList.slice(startIndex, endIndex) ++ } ++ ++ onModelChanged: { ++ const clampedIndex = model.length > 0 ? Math.min(Math.max(0, gridIndex), model.length - 1) : 0 ++ if (gridIndex !== clampedIndex) { ++ gridIndex = clampedIndex ++ } ++ } ++ ++ onCountChanged: { ++ if (count > 0) { ++ const clampedIndex = Math.min(gridIndex, count - 1) ++ currentIndex = clampedIndex ++ positionViewAtIndex(clampedIndex, GridView.Contain) ++ } ++ enableAnimation = true ++ } ++ ++ Connections { ++ target: root ++ function onGridIndexChanged() { ++ if (enableAnimation && wallpaperGrid.count > 0) { ++ wallpaperGrid.currentIndex = gridIndex ++ } ++ } ++ } ++ ++ delegate: Item { ++ width: wallpaperGrid.cellWidth ++ height: wallpaperGrid.cellHeight ++ ++ property string wallpaperPath: modelData || "" ++ property bool isSelected: SessionData.wallpaperPath === modelData ++ ++ Rectangle { ++ id: wallpaperCard ++ anchors.fill: parent ++ anchors.margins: Theme.spacingXS ++ color: Theme.surfaceContainerHighest ++ radius: Theme.cornerRadius ++ clip: true ++ ++ Rectangle { ++ anchors.fill: parent ++ color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent" ++ radius: parent.radius ++ ++ Behavior on color { ++ ColorAnimation { ++ duration: Theme.shortDuration ++ easing.type: Theme.standardEasing ++ } ++ } ++ } ++ ++ Image { ++ id: thumbnailImage ++ anchors.fill: parent ++ source: modelData ? `file://${modelData}` : "" ++ fillMode: Image.PreserveAspectCrop ++ asynchronous: true ++ cache: true ++ smooth: true ++ ++ layer.enabled: true ++ layer.effect: MultiEffect { ++ maskEnabled: true ++ maskThresholdMin: 0.5 ++ maskSpreadAtMin: 1.0 ++ maskSource: ShaderEffectSource { ++ sourceItem: Rectangle { ++ width: thumbnailImage.width ++ height: thumbnailImage.height ++ radius: Theme.cornerRadius ++ } ++ } ++ } ++ } ++ ++ BusyIndicator { ++ anchors.centerIn: parent ++ running: thumbnailImage.status === Image.Loading ++ visible: running ++ } ++ ++ StateLayer { ++ anchors.fill: parent ++ cornerRadius: parent.radius ++ stateColor: Theme.primary ++ } ++ ++ MouseArea { ++ id: wallpaperMouseArea ++ anchors.fill: parent ++ hoverEnabled: true ++ cursorShape: Qt.PointingHandCursor ++ ++ onClicked: { ++ gridIndex = index ++ if (modelData) { ++ SessionData.setWallpaper(modelData) ++ } ++ // Don't steal focus - let mainContainer keep it for keyboard nav ++ } ++ } ++ } ++ } ++ } ++ ++ StyledText { ++ anchors.centerIn: parent ++ visible: wallpaperList.length === 0 ++ text: "No wallpapers found\n\nClick the folder icon below to browse" ++ font.pixelSize: 14 ++ color: Theme.outline ++ horizontalAlignment: Text.AlignHCenter ++ } ++ } ++ ++ Row { ++ width: parent.width ++ height: 50 ++ spacing: Theme.spacingS ++ ++ Item { ++ width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2 ++ height: parent.height ++ } ++ ++ Row { ++ id: controlsRow ++ anchors.verticalCenter: parent.verticalCenter ++ spacing: Theme.spacingS ++ ++ DankActionButton { ++ anchors.verticalCenter: parent.verticalCenter ++ iconName: "skip_previous" ++ iconSize: 20 ++ buttonSize: 32 ++ enabled: currentPage > 0 ++ opacity: enabled ? 1.0 : 0.3 ++ onClicked: { ++ if (currentPage > 0) { ++ currentPage-- ++ } ++ } ++ } ++ ++ StyledText { ++ anchors.verticalCenter: parent.verticalCenter ++ text: wallpaperList.length > 0 ? `${wallpaperList.length} wallpapers • ${currentPage + 1} / ${totalPages}` : "No wallpapers" ++ font.pixelSize: 14 ++ color: Theme.surfaceText ++ opacity: 0.7 ++ } ++ ++ DankActionButton { ++ anchors.verticalCenter: parent.verticalCenter ++ iconName: "skip_next" ++ iconSize: 20 ++ buttonSize: 32 ++ enabled: currentPage < totalPages - 1 ++ opacity: enabled ? 1.0 : 0.3 ++ onClicked: { ++ if (currentPage < totalPages - 1) { ++ currentPage++ ++ } ++ } ++ } ++ } ++ ++ DankActionButton { ++ id: browseButton ++ anchors.verticalCenter: parent.verticalCenter ++ iconName: "folder_open" ++ iconSize: 20 ++ buttonSize: 32 ++ opacity: 0.7 ++ onClicked: wallpaperBrowserLoader.active = true ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/Modules/Greetd/GreeterContent.qml b/Modules/Greetd/GreeterContent.qml +index 7c2e720..0ace65f 100644 +--- a/Modules/Greetd/GreeterContent.qml ++++ b/Modules/Greetd/GreeterContent.qml +@@ -7,6 +7,7 @@ import Quickshell + import Quickshell.Io + import Quickshell.Services.Greetd + import Quickshell.Services.Pam ++import Quickshell.Services.Mpris + import qs.Common + import qs.Services + import qs.Widgets +@@ -15,6 +16,8 @@ import qs.Modules.Lock + Item { + id: root + ++ required property var sessionLock ++ + readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS") + property string screenName: "" + property string randomFact: "" +@@ -114,6 +117,21 @@ Item { + onTriggered: updateHyprlandLayout() + } + ++ // ! This was for development and testing, just leaving so people can see how I did it. ++ // Timer { ++ // id: autoUnlockTimer ++ // interval: 10000 ++ // running: true ++ // onTriggered: { ++ // root.sessionLock.locked = false ++ // GreeterState.unlocking = true ++ // const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex] ++ // if (sessionCmd) { ++ // GreetdMemory.setLastSessionId(sessionCmd.split(" ")[0]) ++ // Greetd.launch(sessionCmd.split(" "), [], true) ++ // } ++ // } ++ // } + + Connections { + target: GreetdMemory +@@ -655,11 +673,180 @@ Item { + height: 24 + color: Qt.rgba(255, 255, 255, 0.2) + anchors.verticalCenter: parent.verticalCenter +- visible: { +- const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) || +- (CompositorService.isHyprland && hyprlandLayoutCount > 1) +- return keyboardVisible && WeatherService.weather.available ++ visible: MprisController.activePlayer ++ } ++ ++ Row { ++ spacing: Theme.spacingS ++ visible: MprisController.activePlayer ++ anchors.verticalCenter: parent.verticalCenter ++ ++ Item { ++ width: 20 ++ height: Theme.iconSize ++ anchors.verticalCenter: parent.verticalCenter ++ ++ Loader { ++ active: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ++ ++ sourceComponent: Component { ++ Ref { ++ service: CavaService ++ } ++ } ++ } ++ ++ Timer { ++ running: !CavaService.cavaAvailable && MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ++ interval: 256 ++ repeat: true ++ onTriggered: { ++ CavaService.values = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20, Math.random() * 45 + 15, Math.random() * 55 + 25] ++ } ++ } ++ ++ Row { ++ anchors.centerIn: parent ++ spacing: 1.5 ++ ++ Repeater { ++ model: 6 ++ ++ Rectangle { ++ width: 2 ++ height: { ++ if (MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing && CavaService.values.length > index) { ++ const rawLevel = CavaService.values[index] || 0 ++ const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100 ++ const maxHeight = Theme.iconSize - 2 ++ const minHeight = 3 ++ return minHeight + (scaledLevel / 100) * (maxHeight - minHeight) ++ } ++ return 3 ++ } ++ radius: 1.5 ++ color: "white" ++ anchors.verticalCenter: parent.verticalCenter ++ ++ Behavior on height { ++ NumberAnimation { ++ duration: Anims.durShort ++ easing.type: Easing.BezierSpline ++ easing.bezierCurve: Anims.standardDecel ++ } ++ } ++ } ++ } ++ } + } ++ ++ StyledText { ++ text: { ++ const player = MprisController.activePlayer ++ if (!player?.trackTitle) ++ return "" ++ const title = player.trackTitle ++ const artist = player.trackArtist || "" ++ return artist ? title + " • " + artist : title ++ } ++ font.pixelSize: Theme.fontSizeLarge ++ color: "white" ++ opacity: 0.9 ++ anchors.verticalCenter: parent.verticalCenter ++ elide: Text.ElideRight ++ width: Math.min(implicitWidth, 400) ++ wrapMode: Text.NoWrap ++ maximumLineCount: 1 ++ } ++ ++ Row { ++ spacing: Theme.spacingXS ++ anchors.verticalCenter: parent.verticalCenter ++ ++ Rectangle { ++ width: 20 ++ height: 20 ++ radius: 10 ++ anchors.verticalCenter: parent.verticalCenter ++ color: prevArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent" ++ visible: MprisController.activePlayer ++ opacity: (MprisController.activePlayer?.canGoPrevious ?? false) ? 1 : 0.3 ++ ++ DankIcon { ++ anchors.centerIn: parent ++ name: "skip_previous" ++ size: 12 ++ color: "white" ++ } ++ ++ MouseArea { ++ id: prevArea ++ anchors.fill: parent ++ enabled: MprisController.activePlayer?.canGoPrevious ?? false ++ hoverEnabled: enabled ++ cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor ++ onClicked: MprisController.activePlayer?.previous() ++ } ++ } ++ ++ Rectangle { ++ width: 24 ++ height: 24 ++ radius: 12 ++ anchors.verticalCenter: parent.verticalCenter ++ color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? Qt.rgba(255, 255, 255, 0.9) : Qt.rgba(255, 255, 255, 0.2) ++ visible: MprisController.activePlayer ++ ++ DankIcon { ++ anchors.centerIn: parent ++ name: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow" ++ size: 14 ++ color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "black" : "white" ++ } ++ ++ MouseArea { ++ anchors.fill: parent ++ enabled: MprisController.activePlayer ++ hoverEnabled: enabled ++ cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor ++ onClicked: MprisController.activePlayer?.togglePlaying() ++ } ++ } ++ ++ Rectangle { ++ width: 20 ++ height: 20 ++ radius: 10 ++ anchors.verticalCenter: parent.verticalCenter ++ color: nextArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent" ++ visible: MprisController.activePlayer ++ opacity: (MprisController.activePlayer?.canGoNext ?? false) ? 1 : 0.3 ++ ++ DankIcon { ++ anchors.centerIn: parent ++ name: "skip_next" ++ size: 12 ++ color: "white" ++ } ++ ++ MouseArea { ++ id: nextArea ++ anchors.fill: parent ++ enabled: MprisController.activePlayer?.canGoNext ?? false ++ hoverEnabled: enabled ++ cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor ++ onClicked: MprisController.activePlayer?.next() ++ } ++ } ++ } ++ } ++ ++ Rectangle { ++ width: 1 ++ height: 24 ++ color: Qt.rgba(255, 255, 255, 0.2) ++ anchors.verticalCenter: parent.verticalCenter ++ visible: MprisController.activePlayer && WeatherService.weather.available + } + + Row { +@@ -1060,6 +1247,7 @@ Item { + } + + function onReadyToLaunch() { ++ root.sessionLock.locked = false + GreeterState.unlocking = true + const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex] + if (sessionCmd) { +diff --git a/Modules/Greetd/GreeterSurface.qml b/Modules/Greetd/GreeterSurface.qml +index a8af5df..bc62426 100644 +--- a/Modules/Greetd/GreeterSurface.qml ++++ b/Modules/Greetd/GreeterSurface.qml +@@ -1,34 +1,18 @@ +-pragma ComponentBehavior: Bound +- + import QtQuick + import Quickshell + import Quickshell.Wayland + import Quickshell.Services.Greetd +-import qs.Common +- +-Variants { +- model: Quickshell.screens + +- PanelWindow { +- id: root ++WlSessionLockSurface { ++ id: root + +- property var modelData ++ required property WlSessionLock lock + +- screen: modelData +- anchors { +- left: true +- right: true +- top: true +- bottom: true +- } +- exclusionMode: ExclusionMode.Normal +- WlrLayershell.layer: WlrLayer.Overlay +- WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive +- color: "transparent" ++ color: "transparent" + +- GreeterContent { +- anchors.fill: parent +- screenName: root.screen?.name ?? "" +- } ++ GreeterContent { ++ anchors.fill: parent ++ screenName: root.screen?.name ?? "" ++ sessionLock: root.lock + } + } +diff --git a/Modules/HyprWorkspaces/HyprlandOverview.qml b/Modules/HyprWorkspaces/HyprlandOverview.qml +deleted file mode 100644 +index 94d7673..0000000 +--- a/Modules/HyprWorkspaces/HyprlandOverview.qml ++++ /dev/null +@@ -1,284 +0,0 @@ +-import QtQuick +-import QtQuick.Controls +-import QtQuick.Layouts +-import Quickshell +-import Quickshell.Io +-import Quickshell.Wayland +-import Quickshell.Hyprland +-import qs.Common +-import qs.Services +- +-Scope { +- id: overviewScope +- +- property bool overviewOpen: false +- +- Loader { +- id: hyprlandLoader +- active: overviewScope.overviewOpen +- asynchronous: false +- +- sourceComponent: Variants { +- id: overviewVariants +- model: Quickshell.screens +- +- PanelWindow { +- id: root +- required property var modelData +- readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) +- property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) +- +- screen: modelData +- visible: overviewScope.overviewOpen +- color: "transparent" +- +- WlrLayershell.namespace: "quickshell:overview" +- WlrLayershell.layer: WlrLayer.Overlay +- WlrLayershell.exclusiveZone: -1 +- WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive +- +- anchors { +- top: true +- left: true +- right: true +- bottom: true +- } +- +- HyprlandFocusGrab { +- id: grab +- windows: [root] +- active: false +- property bool hasBeenActivated: false +- onActiveChanged: { +- if (active) { +- hasBeenActivated = true +- } +- } +- onCleared: () => { +- if (hasBeenActivated && overviewScope.overviewOpen) { +- overviewScope.overviewOpen = false +- } +- } +- } +- +- Connections { +- target: overviewScope +- function onOverviewOpenChanged() { +- if (overviewScope.overviewOpen) { +- grab.hasBeenActivated = false +- delayedGrabTimer.start() +- } else { +- delayedGrabTimer.stop() +- grab.active = false +- grab.hasBeenActivated = false +- } +- } +- } +- +- Connections { +- target: root +- function onMonitorIsFocusedChanged() { +- if (overviewScope.overviewOpen && root.monitorIsFocused && !grab.active) { +- grab.hasBeenActivated = false +- grab.active = true +- } else if (overviewScope.overviewOpen && !root.monitorIsFocused && grab.active) { +- grab.active = false +- } +- } +- } +- +- Timer { +- id: delayedGrabTimer +- interval: 150 +- repeat: false +- onTriggered: { +- if (overviewScope.overviewOpen && root.monitorIsFocused) { +- grab.active = true +- } +- } +- } +- +- Timer { +- id: closeTimer +- interval: Theme.expressiveDurations.expressiveDefaultSpatial + 120 +- onTriggered: { +- root.visible = false +- } +- } +- +- Rectangle { +- id: background +- anchors.fill: parent +- color: "black" +- opacity: overviewScope.overviewOpen ? 0.5 : 0 +- +- Behavior on opacity { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized +- } +- } +- +- MouseArea { +- anchors.fill: parent +- onClicked: mouse => { +- const localPos = mapToItem(contentContainer, mouse.x, mouse.y) +- if (localPos.x < 0 || localPos.x > contentContainer.width || localPos.y < 0 || localPos.y > contentContainer.height) { +- overviewScope.overviewOpen = false +- closeTimer.restart() +- } +- } +- } +- } +- +- Item { +- id: contentContainer +- anchors.horizontalCenter: parent.horizontalCenter +- anchors.top: parent.top +- anchors.topMargin: 100 +- width: childrenRect.width +- height: childrenRect.height +- +- opacity: overviewScope.overviewOpen ? 1 : 0 +- transform: [scaleTransform, motionTransform] +- +- Scale { +- id: scaleTransform +- origin.x: contentContainer.width / 2 +- origin.y: contentContainer.height / 2 +- xScale: overviewScope.overviewOpen ? 1 : 0.96 +- yScale: overviewScope.overviewOpen ? 1 : 0.96 +- +- Behavior on xScale { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized +- } +- } +- +- Behavior on yScale { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized +- } +- } +- } +- +- Translate { +- id: motionTransform +- x: 0 +- y: overviewScope.overviewOpen ? 0 : Theme.spacingL +- +- Behavior on y { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized +- } +- } +- } +- +- Behavior on opacity { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized +- } +- } +- +- Loader { +- id: overviewLoader +- active: overviewScope.overviewOpen +- asynchronous: false +- +- sourceComponent: OverviewWidget { +- panelWindow: root +- overviewOpen: overviewScope.overviewOpen +- } +- } +- } +- +- FocusScope { +- id: focusScope +- anchors.fill: parent +- visible: overviewScope.overviewOpen +- focus: overviewScope.overviewOpen && root.monitorIsFocused +- +- Keys.onEscapePressed: event => { +- if (!root.monitorIsFocused) return +- overviewScope.overviewOpen = false +- closeTimer.restart() +- event.accepted = true +- } +- +- Keys.onPressed: event => { +- if (!root.monitorIsFocused) return +- +- if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) { +- if (!overviewLoader.item) return +- +- const thisMonitorWorkspaceIds = overviewLoader.item.thisMonitorWorkspaceIds +- if (thisMonitorWorkspaceIds.length === 0) return +- +- const currentId = root.monitor.activeWorkspace?.id ?? thisMonitorWorkspaceIds[0] +- const currentIndex = thisMonitorWorkspaceIds.indexOf(currentId) +- +- let targetIndex +- if (event.key === Qt.Key_Left) { +- targetIndex = currentIndex - 1 +- if (targetIndex < 0) targetIndex = thisMonitorWorkspaceIds.length - 1 +- } else { +- targetIndex = currentIndex + 1 +- if (targetIndex >= thisMonitorWorkspaceIds.length) targetIndex = 0 +- } +- +- const targetId = thisMonitorWorkspaceIds[targetIndex] +- Hyprland.dispatch("workspace " + targetId) +- event.accepted = true +- } +- } +- +- onVisibleChanged: { +- if (visible && overviewScope.overviewOpen && root.monitorIsFocused) { +- Qt.callLater(() => focusScope.forceActiveFocus()) +- } +- } +- +- Connections { +- target: root +- function onMonitorIsFocusedChanged() { +- if (root.monitorIsFocused && overviewScope.overviewOpen) { +- Qt.callLater(() => focusScope.forceActiveFocus()) +- } +- } +- } +- } +- +- onVisibleChanged: { +- if (visible && overviewScope.overviewOpen) { +- Qt.callLater(() => focusScope.forceActiveFocus()) +- } else if (!visible) { +- grab.active = false +- } +- } +- +- Connections { +- target: overviewScope +- function onOverviewOpenChanged() { +- if (overviewScope.overviewOpen) { +- closeTimer.stop() +- root.visible = true +- Qt.callLater(() => focusScope.forceActiveFocus()) +- } else { +- closeTimer.restart() +- grab.active = false +- } +- } +- } +- } +- } +- } +-} +diff --git a/Modules/HyprWorkspaces/OverviewWidget.qml b/Modules/HyprWorkspaces/OverviewWidget.qml +deleted file mode 100644 +index 1b6233f..0000000 +--- a/Modules/HyprWorkspaces/OverviewWidget.qml ++++ /dev/null +@@ -1,428 +0,0 @@ +-import QtQuick +-import QtQuick.Effects +-import QtQuick.Layouts +-import Quickshell +-import Quickshell.Wayland +-import Quickshell.Hyprland +-import qs.Common +-import qs.Services +-import qs.Widgets +- +-Item { +- id: root +- required property var panelWindow +- required property bool overviewOpen +- readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) +- readonly property int workspacesShown: SettingsData.overviewRows * SettingsData.overviewColumns +- +- readonly property var allWorkspaces: Hyprland.workspaces?.values || [] +- readonly property var allWorkspaceIds: { +- const workspaces = allWorkspaces +- if (!workspaces || workspaces.length === 0) return [] +- try { +- const ids = workspaces.map(ws => ws?.id).filter(id => id !== null && id !== undefined) +- return ids.sort((a, b) => a - b) +- } catch (e) { +- return [] +- } +- } +- +- readonly property var thisMonitorWorkspaceIds: { +- const workspaces = allWorkspaces +- const mon = monitor +- if (!workspaces || workspaces.length === 0 || !mon) return [] +- try { +- const filtered = workspaces.filter(ws => ws?.monitor?.name === mon.name) +- return filtered.map(ws => ws?.id).filter(id => id !== null && id !== undefined).sort((a, b) => a - b) +- } catch (e) { +- return [] +- } +- } +- +- readonly property var displayedWorkspaceIds: { +- if (!allWorkspaceIds || allWorkspaceIds.length === 0) { +- const result = [] +- for (let i = 1; i <= workspacesShown; i++) { +- result.push(i) +- } +- return result +- } +- +- try { +- const maxExisting = Math.max(...allWorkspaceIds) +- const totalNeeded = Math.max(workspacesShown, allWorkspaceIds.length) +- const result = [] +- +- for (let i = 1; i <= maxExisting; i++) { +- result.push(i) +- } +- +- let nextId = maxExisting + 1 +- while (result.length < totalNeeded) { +- result.push(nextId) +- nextId++ +- } +- +- return result +- } catch (e) { +- const result = [] +- for (let i = 1; i <= workspacesShown; i++) { +- result.push(i) +- } +- return result +- } +- } +- +- readonly property int minWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[0] : 1 +- readonly property int maxWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[displayedWorkspaceIds.length - 1] : workspacesShown +- readonly property int displayWorkspaceCount: displayedWorkspaceIds.length +- +- function getWorkspaceMonitorName(workspaceId) { +- if (!allWorkspaces || !workspaceId) return "" +- try { +- const ws = allWorkspaces.find(w => w?.id === workspaceId) +- return ws?.monitor?.name ?? "" +- } catch (e) { +- return "" +- } +- } +- +- function workspaceHasWindows(workspaceId) { +- if (!workspaceId) return false +- try { +- const workspace = allWorkspaces.find(ws => ws?.id === workspaceId) +- if (!workspace) return false +- const toplevels = workspace?.toplevels?.values || [] +- return toplevels.length > 0 +- } catch (e) { +- return false +- } +- } +- +- property bool monitorIsFocused: monitor?.focused ?? false +- property real scale: SettingsData.overviewScale +- property color activeBorderColor: Theme.primary +- +- property real workspaceImplicitWidth: ((monitor.width / monitor.scale) * root.scale) +- property real workspaceImplicitHeight: ((monitor.height / monitor.scale) * root.scale) +- +- property int workspaceZ: 0 +- property int windowZ: 1 +- property int monitorLabelZ: 2 +- property int windowDraggingZ: 99999 +- property real workspaceSpacing: 5 +- +- property int draggingFromWorkspace: -1 +- property int draggingTargetWorkspace: -1 +- +- implicitWidth: overviewBackground.implicitWidth + Theme.spacingL * 2 +- implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2 +- +- Component.onCompleted: { +- Hyprland.refreshToplevels() +- Hyprland.refreshWorkspaces() +- Hyprland.refreshMonitors() +- } +- +- onOverviewOpenChanged: { +- if (overviewOpen) { +- Hyprland.refreshToplevels() +- Hyprland.refreshWorkspaces() +- Hyprland.refreshMonitors() +- } +- } +- +- Rectangle { +- id: overviewBackground +- property real padding: 10 +- anchors.fill: parent +- anchors.margins: Theme.spacingL +- +- implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 +- implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 +- radius: Theme.cornerRadius +- color: Theme.surfaceContainer +- +- layer.enabled: true +- layer.effect: MultiEffect { +- shadowEnabled: true +- shadowBlur: 0.5 +- shadowHorizontalOffset: 0 +- shadowVerticalOffset: 4 +- shadowColor: Theme.shadowStrong +- shadowOpacity: 1 +- blurMax: 32 +- } +- +- ColumnLayout { +- id: workspaceColumnLayout +- +- z: root.workspaceZ +- anchors.centerIn: parent +- spacing: workspaceSpacing +- +- Repeater { +- model: SettingsData.overviewRows +- delegate: RowLayout { +- id: row +- property int rowIndex: index +- spacing: workspaceSpacing +- +- Repeater { +- model: SettingsData.overviewColumns +- Rectangle { +- id: workspace +- property int colIndex: index +- property int workspaceIndex: rowIndex * SettingsData.overviewColumns + colIndex +- property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1 +- property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false +- property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null +- property bool isActive: workspaceObj?.active ?? false +- property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true +- property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false +- property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : "" +- property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3) +- property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1) +- property color hoveredBorderColor: Theme.surfaceVariant +- property bool hoveredWhileDragging: false +- property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows +- +- visible: workspaceValue !== -1 +- +- implicitWidth: root.workspaceImplicitWidth +- implicitHeight: root.workspaceImplicitHeight +- color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor +- radius: Theme.cornerRadius +- border.width: 2 +- border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : "transparent") +- +- StyledText { +- anchors.centerIn: parent +- text: workspaceValue +- font.pixelSize: Theme.fontSizeXLarge * 6 +- font.weight: Font.DemiBold +- color: Theme.withAlpha(Theme.surfaceText, workspaceExists ? 0.2 : 0.1) +- horizontalAlignment: Text.AlignHCenter +- verticalAlignment: Text.AlignVCenter +- } +- +- MouseArea { +- id: workspaceArea +- anchors.fill: parent +- acceptedButtons: Qt.LeftButton +- onClicked: { +- if (root.draggingTargetWorkspace === -1) { +- root.overviewOpen = false +- Hyprland.dispatch(`workspace ${workspaceValue}`) +- } +- } +- } +- +- DropArea { +- anchors.fill: parent +- onEntered: { +- root.draggingTargetWorkspace = workspaceValue +- if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return +- hoveredWhileDragging = true +- } +- onExited: { +- hoveredWhileDragging = false +- if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 +- } +- } +- } +- } +- } +- } +- } +- +- Item { +- id: windowSpace +- anchors.centerIn: parent +- implicitWidth: workspaceColumnLayout.implicitWidth +- implicitHeight: workspaceColumnLayout.implicitHeight +- +- Repeater { +- model: ScriptModel { +- values: { +- const workspaces = root.allWorkspaces +- const minId = root.minWorkspaceId +- const maxId = root.maxWorkspaceId +- +- if (!workspaces || workspaces.length === 0) return [] +- +- try { +- const result = [] +- for (const workspace of workspaces) { +- const wsId = workspace?.id ?? -1 +- if (wsId >= minId && wsId <= maxId) { +- const toplevels = workspace?.toplevels?.values || [] +- for (const toplevel of toplevels) { +- result.push(toplevel) +- } +- } +- } +- return result +- } catch (e) { +- console.error("OverviewWidget filter error:", e) +- return [] +- } +- } +- } +- delegate: OverviewWindow { +- id: window +- required property var modelData +- +- overviewOpen: root.overviewOpen +- readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1 +- +- function getWorkspaceIndex() { +- if (!root.displayedWorkspaceIds || root.displayedWorkspaceIds.length === 0) return 0 +- if (!windowWorkspaceId || windowWorkspaceId < 0) return 0 +- try { +- for (let i = 0; i < root.displayedWorkspaceIds.length; i++) { +- if (root.displayedWorkspaceIds[i] === windowWorkspaceId) { +- return i +- } +- } +- return 0 +- } catch (e) { +- return 0 +- } +- } +- +- readonly property int workspaceIndex: getWorkspaceIndex() +- readonly property int workspaceColIndex: workspaceIndex % SettingsData.overviewColumns +- readonly property int workspaceRowIndex: Math.floor(workspaceIndex / SettingsData.overviewColumns) +- +- toplevel: modelData +- scale: root.scale +- availableWorkspaceWidth: root.workspaceImplicitWidth +- availableWorkspaceHeight: root.workspaceImplicitHeight +- widgetMonitorId: root.monitor.id +- +- xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex +- yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex +- +- z: atInitPosition ? root.windowZ : root.windowDraggingZ +- property bool atInitPosition: (initX == x && initY == y) +- +- Drag.hotSpot.x: width / 2 +- Drag.hotSpot.y: height / 2 +- +- MouseArea { +- id: dragArea +- anchors.fill: parent +- hoverEnabled: true +- onEntered: window.hovered = true +- onExited: window.hovered = false +- acceptedButtons: Qt.LeftButton | Qt.MiddleButton +- drag.target: parent +- +- onPressed: (mouse) => { +- root.draggingFromWorkspace = windowData?.workspace.id +- window.pressed = true +- window.Drag.active = true +- window.Drag.source = window +- window.Drag.hotSpot.x = mouse.x +- window.Drag.hotSpot.y = mouse.y +- } +- +- onReleased: { +- const targetWorkspace = root.draggingTargetWorkspace +- window.pressed = false +- window.Drag.active = false +- root.draggingFromWorkspace = -1 +- root.draggingTargetWorkspace = -1 +- +- if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { +- Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace},address:${windowData?.address}`) +- Qt.callLater(() => { +- Hyprland.refreshToplevels() +- Hyprland.refreshWorkspaces() +- Qt.callLater(() => { +- window.x = window.initX +- window.y = window.initY +- }) +- }) +- } else { +- window.x = window.initX +- window.y = window.initY +- } +- } +- +- onClicked: (event) => { +- if (!windowData) return +- +- if (event.button === Qt.LeftButton) { +- root.overviewOpen = false +- Hyprland.dispatch(`focuswindow address:${windowData.address}`) +- event.accepted = true +- } else if (event.button === Qt.MiddleButton) { +- Hyprland.dispatch(`closewindow address:${windowData.address}`) +- event.accepted = true +- } +- } +- } +- } +- } +- } +- +- Item { +- id: monitorLabelSpace +- anchors.centerIn: parent +- implicitWidth: workspaceColumnLayout.implicitWidth +- implicitHeight: workspaceColumnLayout.implicitHeight +- z: root.monitorLabelZ +- +- Repeater { +- model: SettingsData.overviewRows +- delegate: Item { +- id: labelRow +- property int rowIndex: index +- y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex +- width: parent.width +- height: root.workspaceImplicitHeight +- +- Repeater { +- model: SettingsData.overviewColumns +- delegate: Item { +- id: labelItem +- property int colIndex: index +- property int workspaceIndex: labelRow.rowIndex * SettingsData.overviewColumns + colIndex +- property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1 +- property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false +- property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : "" +- +- x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex +- width: root.workspaceImplicitWidth +- height: root.workspaceImplicitHeight +- +- Rectangle { +- anchors.right: parent.right +- anchors.top: parent.top +- anchors.margins: Theme.spacingS +- width: monitorNameText.contentWidth + Theme.spacingS * 2 +- height: monitorNameText.contentHeight + Theme.spacingXS * 2 +- radius: Theme.cornerRadius +- color: Theme.surface +- visible: labelItem.workspaceExists && labelItem.workspaceMonitorName !== "" +- +- StyledText { +- id: monitorNameText +- anchors.centerIn: parent +- text: labelItem.workspaceMonitorName +- font.pixelSize: Theme.fontSizeSmall +- font.weight: Font.Medium +- color: Theme.surfaceText +- horizontalAlignment: Text.AlignHCenter +- verticalAlignment: Text.AlignVCenter +- } +- } +- } +- } +- } +- } +- } +- } +-} +diff --git a/Modules/HyprWorkspaces/OverviewWindow.qml b/Modules/HyprWorkspaces/OverviewWindow.qml +deleted file mode 100644 +index b3de986..0000000 +--- a/Modules/HyprWorkspaces/OverviewWindow.qml ++++ /dev/null +@@ -1,140 +0,0 @@ +-import QtQuick +-import QtQuick.Effects +-import QtQuick.Layouts +-import Quickshell +-import Quickshell.Wayland +-import qs.Common +- +-Item { +- id: root +- property var toplevel +- property var scale +- required property bool overviewOpen +- property var availableWorkspaceWidth +- property var availableWorkspaceHeight +- property bool restrictToWorkspace: true +- +- readonly property var windowData: toplevel?.lastIpcObject || null +- readonly property var monitorObj: toplevel?.monitor +- readonly property var monitorData: monitorObj?.lastIpcObject || null +- +- property real initX: Math.max(((windowData?.at?.[0] ?? 0) - (monitorData?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset +- property real initY: Math.max(((windowData?.at?.[1] ?? 0) - (monitorData?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset +- property real xOffset: 0 +- property real yOffset: 0 +- property int widgetMonitorId: 0 +- +- property var targetWindowWidth: (windowData?.size?.[0] ?? 100) * scale +- property var targetWindowHeight: (windowData?.size?.[1] ?? 100) * scale +- property bool hovered: false +- property bool pressed: false +- +- property var iconToWindowRatio: 0.25 +- property var iconToWindowRatioCompact: 0.45 +- property var entry: DesktopEntries.heuristicLookup(windowData?.class) +- property var iconPath: Quickshell.iconPath(entry?.icon ?? windowData?.class ?? "application-x-executable", "image-missing") +- property bool compactMode: Theme.fontSizeSmall * 4 > targetWindowHeight || Theme.fontSizeSmall * 4 > targetWindowWidth +- +- x: initX +- y: initY +- width: Math.min((windowData?.size?.[0] ?? 100) * root.scale, availableWorkspaceWidth) +- height: Math.min((windowData?.size?.[1] ?? 100) * root.scale, availableWorkspaceHeight) +- opacity: (monitorObj?.id ?? -1) == widgetMonitorId ? 1 : 0.4 +- +- Rectangle { +- id: maskRect +- width: root.width +- height: root.height +- radius: Theme.cornerRadius +- visible: false +- layer.enabled: true +- } +- +- layer.enabled: true +- layer.effect: MultiEffect { +- maskEnabled: true +- maskSource: maskRect +- maskSpreadAtMin: 1 +- maskThresholdMin: 0.5 +- } +- +- Behavior on x { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel +- } +- } +- Behavior on y { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel +- } +- } +- Behavior on width { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel +- } +- } +- Behavior on height { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel +- } +- } +- +- ScreencopyView { +- id: windowPreview +- anchors.fill: parent +- captureSource: root.overviewOpen ? root.toplevel?.wayland : null +- live: true +- +- Rectangle { +- anchors.fill: parent +- radius: Theme.cornerRadius +- color: pressed ? Theme.withAlpha(Theme.surfaceContainerHigh, 0.5) : +- hovered ? Theme.withAlpha(Theme.surfaceVariant, 0.3) : +- Theme.withAlpha(Theme.surfaceContainer, 0.1) +- border.color: Theme.withAlpha(Theme.outline, 0.3) +- border.width: 1 +- } +- +- ColumnLayout { +- anchors.verticalCenter: parent.verticalCenter +- anchors.left: parent.left +- anchors.right: parent.right +- spacing: Theme.fontSizeSmall * 0.5 +- +- Image { +- id: windowIcon +- property var iconSize: { +- return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1) +- } +- Layout.alignment: Qt.AlignHCenter +- source: root.iconPath +- width: iconSize +- height: iconSize +- sourceSize: Qt.size(iconSize, iconSize) +- +- Behavior on width { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel +- } +- } +- Behavior on height { +- NumberAnimation { +- duration: Theme.expressiveDurations.expressiveDefaultSpatial +- easing.type: Easing.BezierSpline +- easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel +- } +- } +- } +- } +- } +-} +diff --git a/Modules/Notifications/Center/NotificationCard.qml b/Modules/Notifications/Center/NotificationCard.qml +index 70edf3e..dac71d0 100644 +--- a/Modules/Notifications/Center/NotificationCard.qml ++++ b/Modules/Notifications/Center/NotificationCard.qml +@@ -537,7 +537,7 @@ Rectangle { + + StyledText { + id: clearText +- text: I18n.tr("Dismiss") ++ text: I18n.tr("Clear") + color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium +@@ -630,7 +630,7 @@ Rectangle { + + StyledText { + id: clearText +- text: I18n.tr("Dismiss") ++ text: I18n.tr("Clear") + color: clearButton.isHovered ? Theme.primary : Theme.surfaceVariantText + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium +diff --git a/Modules/Notifications/Center/NotificationHeader.qml b/Modules/Notifications/Center/NotificationHeader.qml +index 7a6ae53..d318767 100644 +--- a/Modules/Notifications/Center/NotificationHeader.qml ++++ b/Modules/Notifications/Center/NotificationHeader.qml +@@ -99,7 +99,7 @@ Item { + } + + StyledText { +- text: I18n.tr("Clear") ++ text: I18n.tr("Clear All") + font.pixelSize: Theme.fontSizeSmall + color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText + font.weight: Font.Medium +diff --git a/Modules/Notifications/Popup/NotificationPopup.qml b/Modules/Notifications/Popup/NotificationPopup.qml +index f5ff70b..3146f4a 100644 +--- a/Modules/Notifications/Popup/NotificationPopup.qml ++++ b/Modules/Notifications/Popup/NotificationPopup.qml +@@ -21,7 +21,7 @@ PanelWindow { + property bool exiting: false + property bool _isDestroying: false + property bool _finalized: false +- readonly property string clearText: I18n.tr("Dismiss") ++ readonly property string clearText: I18n.tr("Clear") + + signal entered + signal exitFinished +@@ -512,7 +512,7 @@ PanelWindow { + + anchors.fill: parent + hoverEnabled: true +- acceptedButtons: Qt.LeftButton | Qt.RightButton ++ acceptedButtons: Qt.LeftButton + propagateComposedEvents: true + z: -1 + onEntered: { +@@ -523,20 +523,9 @@ PanelWindow { + if (notificationData && notificationData.popup && notificationData.timer) + notificationData.timer.restart() + } +- onClicked: (mouse) => { +- if (!notificationData || win.exiting) +- return +- +- if (mouse.button === Qt.RightButton) { +- NotificationService.dismissNotification(notificationData) +- } else if (mouse.button === Qt.LeftButton) { +- if (notificationData.actions && notificationData.actions.length > 0) { +- notificationData.actions[0].invoke() +- NotificationService.dismissNotification(notificationData) +- } else { +- notificationData.popup = false +- } +- } ++ onClicked: { ++ if (notificationData && !win.exiting) ++ notificationData.popup = false + } + } + } +diff --git a/PLUGINS/LauncherExample/LauncherExampleLauncher.qml b/PLUGINS/LauncherExample/LauncherExampleLauncher.qml +index 39ba58d..67964d2 100644 +--- a/PLUGINS/LauncherExample/LauncherExampleLauncher.qml ++++ b/PLUGINS/LauncherExample/LauncherExampleLauncher.qml +@@ -26,35 +26,35 @@ Item { + const baseItems = [ + { + name: "Test Item 1", +- icon: "material:lightbulb", ++ icon: "lightbulb", + comment: "This is a test item that shows a toast notification", + action: "toast:Test Item 1 executed!", + categories: ["LauncherExample"] + }, + { +- name: "Test Item 2", +- icon: "material:star", ++ name: "Test Item 2", ++ icon: "star", + comment: "Another test item with different action", + action: "toast:Test Item 2 clicked!", + categories: ["LauncherExample"] + }, + { + name: "Test Item 3", +- icon: "material:favorite", ++ icon: "favorite", + comment: "Third test item for demonstration", + action: "toast:Test Item 3 activated!", + categories: ["LauncherExample"] + }, + { + name: "Example Copy Action", +- icon: "material:content_copy", ++ icon: "content_copy", + comment: "Demonstrates copying text to clipboard", + action: "copy:This text was copied by the launcher plugin!", + categories: ["LauncherExample"] + }, + { + name: "Example Script Action", +- icon: "material:terminal", ++ icon: "terminal", + comment: "Demonstrates running a simple command", + action: "script:echo 'Hello from launcher plugin!'", + categories: ["LauncherExample"] +diff --git a/PLUGINS/LauncherExample/README.md b/PLUGINS/LauncherExample/README.md +index d752d0a..ff744d4 100644 +--- a/PLUGINS/LauncherExample/README.md ++++ b/PLUGINS/LauncherExample/README.md +@@ -88,41 +88,13 @@ function executeItem(item): void + ```javascript + { + name: "Item Name", // Display name +- icon: "icon_name", // Icon (optional, see Icon Types below) ++ icon: "icon_name", // Material icon + comment: "Description", // Subtitle text + action: "type:data", // Action to execute + categories: ["PluginName"] // Category array + } + ``` + +-**Icon Types**: +- +-The `icon` field supports three formats: +- +-1. **Material Design Icons** - Use `material:` prefix: +- ```javascript +- icon: "material:lightbulb" // Material Symbols Rounded font +- ``` +- Examples: `material:star`, `material:favorite`, `material:settings` +- +-2. **Desktop Theme Icons** - Use icon name directly: +- ```javascript +- icon: "firefox" // Uses system icon theme +- ``` +- Examples: `firefox`, `chrome`, `folder`, `text-editor` +- +-3. **No Icon** - Omit the `icon` field entirely: +- ```javascript +- { +- name: "😀 Grinning Face", +- // No icon field +- comment: "Copy emoji", +- action: "copy:😀", +- categories: ["MyPlugin"] +- } +- ``` +- Perfect for emoji pickers or text-only items where the icon area should be hidden +- + **Action Format**: `type:data` where: + - `type` - Action handler (toast, copy, script, etc.) + - `data` - Action-specific data +diff --git a/PLUGINS/README.md b/PLUGINS/README.md +index 99ae3d6..c3f9e57 100644 +--- a/PLUGINS/README.md ++++ b/PLUGINS/README.md +@@ -1221,51 +1221,11 @@ Item { + Each item returned by `getItems()` must include: + + - `name` (string): Display name shown in launcher +-- `icon` (string, optional): Icon specification (see Icon Types below) ++- `icon` (string): Material Design icon name + - `comment` (string): Description/subtitle text + - `action` (string): Action identifier in `type:data` format + - `categories` (array): Array containing your plugin name + +-### Icon Types +- +-The `icon` field supports three formats: +- +-**1. Material Design Icons** - Use the `material:` prefix: +-```javascript +-{ +- name: "My Item", +- icon: "material:lightbulb", // Material Symbols Rounded font +- comment: "Uses Material Design icon", +- action: "toast:Hello!", +- categories: ["MyPlugin"] +-} +-``` +-Available icons: Any icon from Material Symbols font (e.g., `lightbulb`, `star`, `favorite`, `settings`, `terminal`, `translate`, `sentiment_satisfied`) +- +-**2. Desktop Theme Icons** - Use icon name directly: +-```javascript +-{ +- name: "Firefox", +- icon: "firefox", // Uses system icon theme +- comment: "Launches Firefox browser", +- action: "exec:firefox", +- categories: ["MyPlugin"] +-} +-``` +-Uses the user's installed icon theme. Common examples: `firefox`, `chrome`, `folder`, `text-editor` +- +-**3. No Icon** - Omit the `icon` field entirely: +-```javascript +-{ +- name: "😀 Grinning Face", +- // No icon field - emoji/unicode in name displays without icon area +- comment: "Copy emoji to clipboard", +- action: "copy:😀", +- categories: ["MyPlugin"] +-} +-``` +-When `icon` is omitted, the launcher hides the icon area and displays only the text, giving full width to the item name. Perfect for emoji pickers or text-only items. +- + ### Trigger System + + Triggers control when your plugin's items appear in the launcher: +diff --git a/README.md b/README.md +index af898d6..ee19872 100644 +--- a/README.md ++++ b/README.md +@@ -258,7 +258,7 @@ There are a lot of possible configurations that you can enable/disable in the fl + + #### Other Distributions - via manual installation + +-#### 1. Install Quickshell (Varies by Distribution) ++**1. Install Quickshell (Varies by Distribution)** + ```bash + # Arch + paru -S quickshell-git +@@ -267,61 +267,43 @@ sudo dnf copr enable avengemedia/danklinux && sudo dnf install quickshell-git + # ! TODO - document other distros + ``` + +-#### 2. Install fonts ++**2. Install fonts** + *Inter Variable* and *Fira Code* are not strictly required, but they are the default fonts of dms. + +-#### 2.1 Install Material Symbols ++**2.1 Install Material Symbols** + ```bash + sudo curl -L "https://github.com/google/material-design-icons/raw/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.ttf" -o /usr/share/fonts/MaterialSymbolsRounded.ttf + ``` +-#### 2.2 Install Inter Variable ++**2.2 Install Inter Variable** + ```bash + sudo curl -L "https://github.com/rsms/inter/raw/refs/tags/v4.1/docs/font-files/InterVariable.ttf" -o /usr/share/fonts/InterVariable.ttf + ``` + +-#### 2.3 Install Fira Code (monospace font) ++**2.3 Install Fira Code (monospace font)** + ```bash + sudo curl -L "https://github.com/tonsky/FiraCode/releases/latest/download/FiraCode-Regular.ttf" -o /usr/share/fonts/FiraCode-Regular.ttf + ``` + +-#### 2.4 Refresh font cache ++**2.4 Refresh font cache** + ```bash + fc-cache -fv + ``` + +-#### 3. Install the shell +- +-#### 3.1. Clone latest QML ++**3. Install the shell** + ++**3.1. Clone latest master** + ```bash + mkdir ~/.config/quickshell && git clone https://github.com/AvengeMedia/DankMaterialShell.git ~/.config/quickshell/dms + ``` + +-**FOR Stable Version, Checkout the latest tag** +- +-```bash +-cd ~/.config/quickshell/dms +-# You'll have to re-run this, to update to the latest version. +-git checkout $(git describe --tags --abbrev=0) +-``` +- +-#### 3.2. Install latest dms CLI +- ++**3.2. Install latest dms CLI** + ```bash + sudo sh -c "curl -L https://github.com/AvengeMedia/danklinux/releases/latest/download/dms-$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').gz | gunzip | tee /usr/local/bin/dms > /dev/null && chmod +x /usr/local/bin/dms" + ``` +-**Note:** this is the latest *stable* dms CLI. If you are using QML/master (not pinned to a tag), then you may periodically be missing features, etc. + +-If preferred, you can build the dms-cli yourself (requires GO 1.24+) ++**4. Optional Features (system monitoring, clipboard history, brightness controls, etc.)** + +-```bash +-git clone https://github.com/AvengeMedia/danklinux.git && cd danklinux +-make && sudo make install +-``` +- +-#### 4. Optional Features (system monitoring, clipboard history, brightness controls, etc.) +- +-#### 4.1 Core optional dependencies ++**4.1 Core optional dependencies** + ```bash + # Arch Linux + sudo pacman -S cava wl-clipboard cliphist brightnessctl qt6-multimedia +@@ -335,7 +317,7 @@ Note: by enabling and installing the avengemedia/dms copr above, these core depe + + *Other distros will just need to find sources for the above packages* + +-#### 4.2 - dgop manual installation ++**4.2 - dgop manual installation** + + `dgop` is available via AUR and a nix flake, other distributions can install it manually. + +@@ -411,6 +393,9 @@ binds { + Mod+C hotkey-overlay-title="Control Center" { + spawn "dms" "ipc" "call" "control-center" "toggle"; + } ++ Mod+Y hotkey-overlay-title="Browse Wallpapers" { ++ spawn "dms" "ipc" "call" "dankdash" "wallpaper"; ++ } + XF86AudioRaiseVolume allow-when-locked=true { + spawn "dms" "ipc" "call" "audio" "increment" "3"; + } +@@ -478,8 +463,8 @@ bind = SUPER, comma, exec, dms ipc call settings toggle + bind = SUPER, P, exec, dms ipc call notepad toggle + bind = SUPERALT, L, exec, dms ipc call lock lock + bind = SUPER, X, exec, dms ipc call powermenu toggle +-bind = SUPER, C, exec, dms ipc call control-center toggle +-bind = SUPER, TAB, exec, dms ipc call hypr toggleOverview ++bind = SUPER, C, exec, dms ipc call control-center toggle ++bind = SUPER, Y, exec, dms ipc call dankdash wallpaper + + # Audio controls (function keys) + bindl = , XF86AudioRaiseVolume, exec, dms ipc call audio increment 3 +diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml +index 72433d8..27851c7 100644 +--- a/Services/CompositorService.qml ++++ b/Services/CompositorService.qml +@@ -1,4 +1,5 @@ + pragma Singleton ++ + pragma ComponentBehavior: Bound + + import QtQuick +@@ -16,311 +17,137 @@ Singleton { + + readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE") + readonly property string niriSocket: Quickshell.env("NIRI_SOCKET") ++ + property bool useNiriSorting: isNiri && NiriService + +- property var sortedToplevels: sortedToplevelsCache +- property var sortedToplevelsCache: [] ++ property var sortedToplevels: { ++ if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) { ++ return [] ++ } ++ ++ if (useNiriSorting) { ++ return NiriService.sortToplevels(ToplevelManager.toplevels.values) ++ } + +- property bool _sortScheduled: false +- property bool _refreshScheduled: false +- property bool _hasRefreshedOnce: false ++ if (isHyprland) { ++ const hyprlandToplevels = Array.from(Hyprland.toplevels.values) ++ ++ const sortedHyprland = hyprlandToplevels.sort((a, b) => { ++ if (a.monitor && b.monitor) { ++ const monitorCompare = a.monitor.name.localeCompare(b.monitor.name) ++ if (monitorCompare !== 0) { ++ return monitorCompare ++ } ++ } ++ ++ if (a.workspace && b.workspace) { ++ const workspaceCompare = a.workspace.id - b.workspace.id ++ if (workspaceCompare !== 0) { ++ return workspaceCompare ++ } ++ } ++ ++ if (a.lastIpcObject && b.lastIpcObject && a.lastIpcObject.at && b.lastIpcObject.at) { ++ const aX = a.lastIpcObject.at[0] ++ const bX = b.lastIpcObject.at[0] ++ const aY = a.lastIpcObject.at[1] ++ const bY = b.lastIpcObject.at[1] ++ ++ const xCompare = aX - bX ++ if (Math.abs(xCompare) > 10) { ++ return xCompare ++ } ++ return aY - bY ++ } ++ ++ if (a.lastIpcObject && !b.lastIpcObject) { ++ return -1 ++ } ++ if (!a.lastIpcObject && b.lastIpcObject) { ++ return 1 ++ } ++ ++ if (a.title && b.title) { ++ return a.title.localeCompare(b.title) ++ } ++ ++ return 0 ++ }) ++ ++ return sortedHyprland.map(hyprToplevel => hyprToplevel.wayland).filter(wayland => wayland !== null) ++ } + +- property var _coordCache: ({}) ++ return ToplevelManager.toplevels.values ++ } + + Timer { +- id: refreshTimer +- interval: 40 ++ id: compositorInitTimer ++ interval: 100 ++ running: true + repeat: false + onTriggered: { +- try { +- Hyprland.refreshToplevels() +- } catch(e) {} +- _refreshScheduled = false +- _hasRefreshedOnce = true +- scheduleSort() ++ detectCompositor() ++ Qt.callLater(() => NiriService.generateNiriLayoutConfig()) + } + } + +- function scheduleSort() { +- if (_sortScheduled) return +- _sortScheduled = true +- Qt.callLater(function() { +- _sortScheduled = false +- sortedToplevelsCache = computeSortedToplevels() +- }) +- } +- +- function scheduleRefresh() { +- if (!isHyprland) return +- if (_refreshScheduled) return +- _refreshScheduled = true +- refreshTimer.restart() +- } +- +- Connections { +- target: ToplevelManager.toplevels +- function onValuesChanged() { root.scheduleSort() } +- } +- Connections { +- target: Hyprland.toplevels +- function onValuesChanged() { +- root._hasRefreshedOnce = false +- root.scheduleSort() ++ function filterCurrentWorkspace(toplevels, screen) { ++ if (useNiriSorting) { ++ return NiriService.filterCurrentWorkspace(toplevels, screen) + } +- } +- Connections { +- target: Hyprland.workspaces +- function onValuesChanged() { root.scheduleSort() } +- } +- Connections { +- target: Hyprland +- function onFocusedWorkspaceChanged() { root.scheduleSort() } +- } +- +- Component.onCompleted: { +- detectCompositor() +- scheduleSort() +- Qt.callLater(() => NiriService.generateNiriLayoutConfig()) +- } +- +- function computeSortedToplevels() { +- if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) +- return [] +- +- if (useNiriSorting) +- return NiriService.sortToplevels(ToplevelManager.toplevels.values) +- +- if (isHyprland) +- return sortHyprlandToplevelsSafe() +- +- return Array.from(ToplevelManager.toplevels.values) +- } +- +- function _get(o, path, fallback) { +- try { +- let v = o +- for (let i = 0; i < path.length; i++) { +- if (v === null || v === undefined) return fallback +- v = v[path[i]] +- } +- return (v === undefined || v === null) ? fallback : v +- } catch (e) { return fallback } ++ if (isHyprland) { ++ return filterHyprlandCurrentWorkspace(toplevels, screen) ++ } ++ return toplevels + } + +- function sortHyprlandToplevelsSafe() { +- if (!Hyprland.toplevels || !Hyprland.toplevels.values) return [] +- if (_refreshScheduled) return sortedToplevelsCache +- +- const items = Array.from(Hyprland.toplevels.values) +- +- function _get(o, path, fb) { +- try { +- let v = o +- for (let k of path) { if (v == null) return fb; v = v[k] } +- return (v == null) ? fb : v +- } catch(e) { return fb } ++ function filterHyprlandCurrentWorkspace(toplevels, screenName) { ++ if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels) { ++ return toplevels + } + +- let snap = [] +- let missingAnyPosition = false +- let hasNewWindow = false +- for (let i = 0; i < items.length; i++) { +- const t = items[i] +- if (!t) continue +- +- const addr = t.address || "" +- const li = t.lastIpcObject || null +- +- const monName = _get(li, ["monitor"], null) ?? _get(t, ["monitor", "name"], "") +- const monX = _get(t, ["monitor", "x"], Number.MAX_SAFE_INTEGER) +- const monY = _get(t, ["monitor", "y"], Number.MAX_SAFE_INTEGER) +- +- const wsId = _get(li, ["workspace", "id"], null) ?? _get(t, ["workspace", "id"], Number.MAX_SAFE_INTEGER) +- +- const at = _get(li, ["at"], null) +- let atX = (at !== null && at !== undefined && typeof at[0] === "number") ? at[0] : NaN +- let atY = (at !== null && at !== undefined && typeof at[1] === "number") ? at[1] : NaN ++ let currentWorkspaceId = null ++ const hyprlandToplevels = Array.from(Hyprland.toplevels.values) + +- if (!(atX === atX) || !(atY === atY)) { +- const cached = _coordCache[addr] +- if (cached) { +- atX = cached.x +- atY = cached.y +- } else { +- if (addr) hasNewWindow = true +- missingAnyPosition = true +- atX = 1e9 +- atY = 1e9 ++ for (const hyprToplevel of hyprlandToplevels) { ++ if (hyprToplevel.monitor && hyprToplevel.monitor.name === screenName && hyprToplevel.workspace) { ++ if (hyprToplevel.activated) { ++ currentWorkspaceId = hyprToplevel.workspace.id ++ break ++ } ++ if (currentWorkspaceId === null) { ++ currentWorkspaceId = hyprToplevel.workspace.id + } +- } else if (addr) { +- _coordCache[addr] = { x: atX, y: atY } + } +- +- const relX = Number.isFinite(monX) ? (atX - monX) : atX +- const relY = Number.isFinite(monY) ? (atY - monY) : atY +- +- snap.push({ +- monKey: String(monName), +- monOrderX: Number.isFinite(monX) ? monX : Number.MAX_SAFE_INTEGER, +- monOrderY: Number.isFinite(monY) ? monY : Number.MAX_SAFE_INTEGER, +- wsId: (typeof wsId === "number") ? wsId : Number.MAX_SAFE_INTEGER, +- x: relX, +- y: relY, +- title: t.title || "", +- address: addr, +- wayland: t.wayland +- }) + } + +- if (missingAnyPosition && hasNewWindow) { +- _hasRefreshedOnce = false +- scheduleRefresh() +- } +- +- const groups = new Map() +- for (const it of snap) { +- const key = it.monKey + "::" + it.wsId +- if (!groups.has(key)) groups.set(key, []) +- groups.get(key).push(it) +- } +- +- let groupList = [] +- for (const [key, arr] of groups) { +- const repr = arr[0] +- groupList.push({ +- key, +- monKey: repr.monKey, +- monOrderX: repr.monOrderX, +- monOrderY: repr.monOrderY, +- wsId: repr.wsId, +- items: arr +- }) +- } +- +- groupList.sort((a, b) => { +- if (a.monOrderX !== b.monOrderX) return a.monOrderX - b.monOrderX +- if (a.monOrderY !== b.monOrderY) return a.monOrderY - b.monOrderY +- if (a.monKey !== b.monKey) return a.monKey.localeCompare(b.monKey) +- if (a.wsId !== b.wsId) return a.wsId - b.wsId +- return 0 +- }) +- +- const COLUMN_THRESHOLD = 48 +- const JITTER_Y = 6 +- +- let ordered = [] +- for (const g of groupList) { +- const arr = g.items +- +- const xs = arr.map(it => it.x).filter(x => Number.isFinite(x)).sort((a, b) => a - b) +- let colCenters = [] +- if (xs.length > 0) { +- for (const x of xs) { +- if (colCenters.length === 0) { +- colCenters.push(x) +- } else { +- const last = colCenters[colCenters.length - 1] +- if (x - last >= COLUMN_THRESHOLD) { +- colCenters.push(x) +- } ++ if (currentWorkspaceId === null && Hyprland.workspaces) { ++ const workspaces = Array.from(Hyprland.workspaces.values) ++ for (const workspace of workspaces) { ++ if (workspace.monitor && workspace.monitor === screenName) { ++ if (Hyprland.focusedWorkspace && workspace.id === Hyprland.focusedWorkspace.id) { ++ currentWorkspaceId = workspace.id ++ break + } +- } +- } else { +- colCenters = [0] +- } +- +- for (const it of arr) { +- let bestCol = 0 +- let bestDist = Number.POSITIVE_INFINITY +- for (let ci = 0; ci < colCenters.length; ci++) { +- const d = Math.abs(it.x - colCenters[ci]) +- if (d < bestDist) { +- bestDist = d +- bestCol = ci ++ if (currentWorkspaceId === null) { ++ currentWorkspaceId = workspace.id + } + } +- it._col = bestCol + } +- +- arr.sort((a, b) => { +- if (a._col !== b._col) return a._col - b._col +- +- const dy = a.y - b.y +- if (Math.abs(dy) > JITTER_Y) return dy +- +- if (a.title !== b.title) return a.title.localeCompare(b.title) +- if (a.address !== b.address) return a.address.localeCompare(b.address) +- return 0 +- }) +- +- ordered.push.apply(ordered, arr) + } + +- return ordered.map(x => x.wayland).filter(w => w !== null && w !== undefined) +- } +- +- function filterCurrentWorkspace(toplevels, screen) { +- if (useNiriSorting) return NiriService.filterCurrentWorkspace(toplevels, screen) +- if (isHyprland) return filterHyprlandCurrentWorkspaceSafe(toplevels, screen) +- return toplevels +- } +- +- function filterHyprlandCurrentWorkspaceSafe(toplevels, screenName) { +- if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels) return toplevels +- +- let currentWorkspaceId = null +- try { +- const hy = Array.from(Hyprland.toplevels.values) +- for (const t of hy) { +- const mon = _get(t, ["monitor", "name"], "") +- const wsId = _get(t, ["workspace", "id"], null) +- const active = !!_get(t, ["activated"], false) +- if (mon === screenName && wsId !== null) { +- if (active) { currentWorkspaceId = wsId; break } +- if (currentWorkspaceId === null) currentWorkspaceId = wsId +- } +- } +- +- if (currentWorkspaceId === null && Hyprland.workspaces) { +- const wss = Array.from(Hyprland.workspaces.values) +- const focusedId = _get(Hyprland, ["focusedWorkspace", "id"], null) +- for (const ws of wss) { +- const monName = _get(ws, ["monitor"], "") +- const wsId = _get(ws, ["id"], null) +- if (monName === screenName && wsId !== null) { +- if (focusedId !== null && wsId === focusedId) { currentWorkspaceId = wsId; break } +- if (currentWorkspaceId === null) currentWorkspaceId = wsId +- } +- } +- } +- } catch (e) { +- console.warn("CompositorService: workspace snapshot failed:", e) ++ if (currentWorkspaceId === null) { ++ return toplevels + } + +- if (currentWorkspaceId === null) return toplevels +- +- // Map wayland → wsId snapshot +- let map = new Map() +- try { +- const hy = Array.from(Hyprland.toplevels.values) +- for (const t of hy) { +- const wsId = _get(t, ["workspace", "id"], null) +- if (t && t.wayland && wsId !== null) map.set(t.wayland, wsId) +- } +- } catch (e) {} +- +- return toplevels.filter(w => map.get(w) === currentWorkspaceId) +- } +- +- Timer { +- id: compositorInitTimer +- interval: 100 +- running: true +- repeat: false +- onTriggered: { +- detectCompositor() +- Qt.callLater(() => NiriService.generateNiriLayoutConfig()) +- } ++ return toplevels.filter(toplevel => { ++ for (const hyprToplevel of hyprlandToplevels) { ++ if (hyprToplevel.wayland === toplevel) { ++ return hyprToplevel.workspace && hyprToplevel.workspace.id === currentWorkspaceId ++ } ++ } ++ return false ++ }) + } + + function detectCompositor() { +@@ -329,24 +156,21 @@ Singleton { + isNiri = false + compositor = "hyprland" + console.log("CompositorService: Detected Hyprland") +- try { +- Hyprland.refreshToplevels() +- } catch(e) {} + return + } + + if (niriSocket && niriSocket.length > 0) { +- Proc.runCommand("niriSocketCheck", ["test", "-S", niriSocket], (output, exitCode) => { ++ Proc.runCommand("niriSocketCheck", ["test", "-S", root.niriSocket], (output, exitCode) => { + if (exitCode === 0) { +- isNiri = true +- isHyprland = false +- compositor = "niri" +- console.log("CompositorService: Detected Niri with socket:", niriSocket) ++ root.isNiri = true ++ root.isHyprland = false ++ root.compositor = "niri" ++ console.log("CompositorService: Detected Niri with socket:", root.niriSocket) + NiriService.generateNiriBinds() + } else { +- isHyprland = false +- isNiri = true +- compositor = "niri" ++ root.isHyprland = false ++ root.isNiri = true ++ root.compositor = "niri" + console.warn("CompositorService: Niri socket check failed, defaulting to Niri anyway") + } + }, 0) +@@ -359,14 +183,22 @@ Singleton { + } + + function powerOffMonitors() { +- if (isNiri) return NiriService.powerOffMonitors() +- if (isHyprland) return Hyprland.dispatch("dpms off") ++ if (isNiri) { ++ return NiriService.powerOffMonitors() ++ } ++ if (isHyprland) { ++ return Hyprland.dispatch("dpms off") ++ } + console.warn("CompositorService: Cannot power off monitors, unknown compositor") + } + + function powerOnMonitors() { +- if (isNiri) return NiriService.powerOnMonitors() +- if (isHyprland) return Hyprland.dispatch("dpms on") ++ if (isNiri) { ++ return NiriService.powerOnMonitors() ++ } ++ if (isHyprland) { ++ return Hyprland.dispatch("dpms on") ++ } + console.warn("CompositorService: Cannot power on monitors, unknown compositor") + } +-} ++} +\ No newline at end of file +diff --git a/Services/DMSService.qml b/Services/DMSService.qml +index 6e80544..559f883 100644 +--- a/Services/DMSService.qml ++++ b/Services/DMSService.qml +@@ -41,7 +41,6 @@ Singleton { + signal loginctlStateUpdate(var data) + signal loginctlEvent(var event) + signal capabilitiesReceived() +- signal credentialsRequest(var data) + + Component.onCompleted: { + if (socketPath && socketPath.length > 0) { +@@ -262,8 +261,6 @@ Singleton { + capabilitiesReceived() + } else if (service === "network") { + networkStateUpdate(data) +- } else if (service === "network.credentials") { +- credentialsRequest(data) + } else if (service === "loginctl") { + if (data.event) { + loginctlEvent(data) +diff --git a/Services/NetworkManagerService.qml b/Services/NetworkManagerService.qml +index 6589a7d..6cb6d99 100644 +--- a/Services/NetworkManagerService.qml ++++ b/Services/NetworkManagerService.qml +@@ -79,21 +79,8 @@ Singleton { + property int refCount: 0 + property bool stateInitialized: false + +- property string credentialsToken: "" +- property string credentialsSSID: "" +- property string credentialsSetting: "" +- property var credentialsFields: [] +- property var credentialsHints: [] +- property string credentialsReason: "" +- property bool credentialsRequested: false +- +- property string pendingConnectionSSID: "" +- property var pendingConnectionStartTime: 0 +- property bool wasConnecting: false +- + signal networksUpdated + signal connectionChanged +- signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason) + + readonly property string socketPath: Quickshell.env("DMS_SOCKET") + +@@ -133,10 +120,6 @@ Singleton { + function onCapabilitiesChanged() { + checkDMSCapabilities() + } +- +- function onCredentialsRequest(data) { +- handleCredentialsRequest(data) +- } + } + + function checkDMSCapabilities() { +@@ -160,18 +143,6 @@ Singleton { + } + } + +- function handleCredentialsRequest(data) { +- credentialsToken = data.token || "" +- credentialsSSID = data.ssid || "" +- credentialsSetting = data.setting || "802-11-wireless-security" +- credentialsFields = data.fields || ["psk"] +- credentialsHints = data.hints || [] +- credentialsReason = data.reason || "Credentials required" +- credentialsRequested = true +- +- credentialsNeeded(credentialsToken, credentialsSSID, credentialsSetting, credentialsFields, credentialsHints, credentialsReason) +- } +- + function addRef() { + refCount++ + if (refCount === 1 && networkAvailable) { +@@ -206,9 +177,6 @@ Singleton { + } + + function updateState(state) { +- const previousConnecting = isConnecting +- const previousConnectingSSID = connectingSSID +- + networkStatus = state.networkStatus || "disconnected" + primaryConnection = state.primaryConnection || "" + +@@ -257,45 +225,6 @@ Singleton { + connectionError = state.lastError || "" + lastConnectionError = state.lastError || "" + +- if (pendingConnectionSSID) { +- if (wifiConnected && currentWifiSSID === pendingConnectionSSID && wifiIP) { +- if (DMSService.verboseLogs) { +- const elapsed = Date.now() - pendingConnectionStartTime +- console.log("NetworkManagerService: Successfully connected to", pendingConnectionSSID, "in", elapsed, "ms") +- } +- ToastService.showInfo(`Connected to ${pendingConnectionSSID}`) +- +- if (userPreference === "wifi" || userPreference === "auto") { +- setConnectionPriority("wifi") +- } +- +- pendingConnectionSSID = "" +- connectionStatus = "connected" +- } else if (previousConnecting && !isConnecting && !wifiConnected) { +- const elapsed = Date.now() - pendingConnectionStartTime +- +- if (elapsed < 5000) { +- if (DMSService.verboseLogs) { +- console.log("NetworkManagerService: Quick connection failure, likely authentication error") +- } +- connectionStatus = "invalid_password" +- } else { +- if (DMSService.verboseLogs) { +- console.log("NetworkManagerService: Connection failed for", pendingConnectionSSID) +- } +- if (connectionError === "connection-failed") { +- ToastService.showError(I18n.tr("Connection failed. Check password and try again.")) +- } else if (connectionError) { +- ToastService.showError(I18n.tr("Failed to connect to ") + pendingConnectionSSID) +- } +- connectionStatus = "failed" +- pendingConnectionSSID = "" +- } +- } +- } +- +- wasConnecting = isConnecting +- + connectionChanged() + } + +@@ -313,11 +242,11 @@ Singleton { + connectionError = response.error + lastConnectionError = response.error + connectionStatus = "failed" +- ToastService.showError(I18n.tr("Failed to activate configuration")) ++ ToastService.showError(`Failed to activate configuration`) + } else { + connectionError = "" + connectionStatus = "connected" +- ToastService.showInfo(I18n.tr("Configuration activated")) ++ ToastService.showInfo(`Configuration activated`) + } + + isConnecting = false +@@ -351,47 +280,42 @@ Singleton { + function connectToWifi(ssid, password = "", username = "", anonymousIdentity = "", domainSuffixMatch = "") { + if (!networkAvailable || isConnecting) return + +- pendingConnectionSSID = ssid +- pendingConnectionStartTime = Date.now() ++ connectingSSID = ssid + connectionError = "" + connectionStatus = "connecting" +- credentialsRequested = false + + const params = { ssid: ssid } +- +- if (DMSService.apiVersion >= 7) { +- if (password || username) { +- params.password = password +- if (username) params.username = username +- if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity +- if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch +- params.interactive = false +- } else { +- params.interactive = true +- } +- } else { +- if (password) params.password = password +- if (username) params.username = username +- if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity +- if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch +- } ++ if (password) params.password = password ++ if (username) params.username = username ++ if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity ++ if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch + + DMSService.sendRequest("network.wifi.connect", params, response => { + if (response.error) { +- if (DMSService.verboseLogs) { +- console.log("NetworkManagerService: Connection request failed:", response.error) +- } +- + connectionError = response.error + lastConnectionError = response.error +- pendingConnectionSSID = "" +- connectionStatus = "failed" +- ToastService.showError(I18n.tr("Failed to start connection to ") + ssid) ++ connectionStatus = response.error.includes("password") || response.error.includes("authentication") ++ ? "invalid_password" ++ : "failed" ++ ++ if (connectionStatus === "invalid_password") { ++ passwordDialogShouldReopen = true ++ ToastService.showError(`Invalid password for ${ssid}`) ++ } else { ++ ToastService.showError(`Failed to connect to ${ssid}`) ++ } + } else { +- if (DMSService.verboseLogs) { +- console.log("NetworkManagerService: Connection request sent for", ssid) ++ connectionError = "" ++ connectionStatus = "connected" ++ ToastService.showInfo(`Connected to ${ssid}`) ++ ++ if (userPreference === "wifi" || userPreference === "auto") { ++ setConnectionPriority("wifi") + } + } ++ ++ isConnecting = false ++ connectingSSID = "" + }) + } + +@@ -400,60 +324,15 @@ Singleton { + + DMSService.sendRequest("network.wifi.disconnect", null, response => { + if (response.error) { +- ToastService.showError(I18n.tr("Failed to disconnect WiFi")) ++ ToastService.showError("Failed to disconnect WiFi") + } else { +- ToastService.showInfo(I18n.tr("Disconnected from WiFi")) ++ ToastService.showInfo("Disconnected from WiFi") + currentWifiSSID = "" + connectionStatus = "" + } + }) + } + +- function submitCredentials(token, secrets, save) { +- if (!networkAvailable || DMSService.apiVersion < 7) return +- +- const params = { +- token: token, +- secrets: secrets, +- save: save || false +- } +- +- if (DMSService.verboseLogs) { +- console.log("NetworkManagerService: Submitting credentials for token", token) +- } +- +- credentialsRequested = false +- +- DMSService.sendRequest("network.credentials.submit", params, response => { +- if (response.error) { +- console.warn("NetworkManagerService: Failed to submit credentials:", response.error) +- } +- }) +- } +- +- function cancelCredentials(token) { +- if (!networkAvailable || DMSService.apiVersion < 7) return +- +- const params = { +- token: token, +- cancel: true +- } +- +- if (DMSService.verboseLogs) { +- console.log("NetworkManagerService: Cancelling credentials for token", token) +- } +- +- credentialsRequested = false +- pendingConnectionSSID = "" +- connectionStatus = "cancelled" +- +- DMSService.sendRequest("network.credentials.submit", params, response => { +- if (response.error) { +- console.warn("NetworkManagerService: Failed to cancel credentials:", response.error) +- } +- }) +- } +- + function forgetWifiNetwork(ssid) { + if (!networkAvailable) return + +@@ -462,7 +341,7 @@ Singleton { + if (response.error) { + console.warn("Failed to forget network:", response.error) + } else { +- ToastService.showInfo(I18n.tr("Forgot network ") + ssid) ++ ToastService.showInfo(`Forgot network ${ssid}`) + + savedConnections = savedConnections.filter(s => s.ssid !== ssid) + savedWifiNetworks = savedWifiNetworks.filter(s => s.ssid !== ssid) +@@ -495,7 +374,7 @@ Singleton { + console.warn("Failed to toggle WiFi:", response.error) + } else if (response.result) { + wifiEnabled = response.result.enabled +- ToastService.showInfo(wifiEnabled ? I18n.tr("WiFi enabled") : I18n.tr("WiFi disabled")) ++ ToastService.showInfo(wifiEnabled ? "WiFi enabled" : "WiFi disabled") + } + }) + } +@@ -505,9 +384,9 @@ Singleton { + + DMSService.sendRequest("network.wifi.enable", null, response => { + if (response.error) { +- ToastService.showError(I18n.tr("Failed to enable WiFi")) ++ ToastService.showError("Failed to enable WiFi") + } else { +- ToastService.showInfo(I18n.tr("WiFi enabled")) ++ ToastService.showInfo("WiFi enabled") + } + }) + } +diff --git a/Services/NetworkService.qml b/Services/NetworkService.qml +index c8b8c3d..35fb492 100644 +--- a/Services/NetworkService.qml ++++ b/Services/NetworkService.qml +@@ -69,17 +69,8 @@ Singleton { + + property bool subscriptionConnected: activeService?.subscriptionConnected ?? false + +- property string credentialsToken: activeService?.credentialsToken ?? "" +- property string credentialsSSID: activeService?.credentialsSSID ?? "" +- property string credentialsSetting: activeService?.credentialsSetting ?? "" +- property var credentialsFields: activeService?.credentialsFields ?? [] +- property var credentialsHints: activeService?.credentialsHints ?? [] +- property string credentialsReason: activeService?.credentialsReason ?? "" +- property bool credentialsRequested: activeService?.credentialsRequested ?? false +- + signal networksUpdated + signal connectionChanged +- signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason) + + property bool usingLegacy: false + property var activeService: null +@@ -131,9 +122,6 @@ Singleton { + if (activeService.connectionChanged) { + activeService.connectionChanged.connect(root.connectionChanged) + } +- if (activeService.credentialsNeeded) { +- activeService.credentialsNeeded.connect(root.credentialsNeeded) +- } + } + } + +@@ -270,16 +258,4 @@ Singleton { + activeService.connectToSpecificWiredConfig(uuid) + } + } +- +- function submitCredentials(token, secrets, save) { +- if (activeService && activeService.submitCredentials) { +- activeService.submitCredentials(token, secrets, save) +- } +- } +- +- function cancelCredentials(token) { +- if (activeService && activeService.cancelCredentials) { +- activeService.cancelCredentials(token) +- } +- } + } +diff --git a/Services/niri-binds.kdl b/Services/niri-binds.kdl +index 7f91d40..d7dbe68 100644 +--- a/Services/niri-binds.kdl ++++ b/Services/niri-binds.kdl +@@ -19,6 +19,10 @@ binds { + spawn "dms" "ipc" "call" "notifications" "toggle"; + } + ++ Mod+Y hotkey-overlay-title="Browse Wallpapers" { ++ spawn "dms" "ipc" "call" "dankdash" "wallpaper"; ++ } ++ + Mod+Shift+N hotkey-overlay-title="Notepad" { + spawn "dms" "ipc" "call" "notepad" "toggle"; + } +diff --git a/Widgets/DankPopout.qml b/Widgets/DankPopout.qml +index 5a45fee..b11506d 100644 +--- a/Widgets/DankPopout.qml ++++ b/Widgets/DankPopout.qml +@@ -25,6 +25,7 @@ PanelWindow { + property list animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial + property list animationExitCurve: Theme.expressiveCurves.emphasized + property bool shouldBeVisible: false ++ property int keyboardFocusMode: WlrKeyboardFocus.OnDemand + + signal opened + signal popoutClosed +@@ -63,7 +64,7 @@ PanelWindow { + color: "transparent" + WlrLayershell.layer: WlrLayershell.Top + WlrLayershell.exclusiveZone: -1 +- WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None ++ WlrLayershell.keyboardFocus: shouldBeVisible ? keyboardFocusMode : WlrKeyboardFocus.None + + anchors { + top: true +diff --git a/Widgets/DankTabBar.qml b/Widgets/DankTabBar.qml +index b065a31..bfca6e1 100644 +--- a/Widgets/DankTabBar.qml ++++ b/Widgets/DankTabBar.qml +@@ -2,7 +2,7 @@ import QtQuick + import qs.Common + import qs.Widgets + +-Item { ++FocusScope { + id: tabBar + + property alias model: tabRepeater.model +@@ -11,12 +11,101 @@ Item { + property int tabHeight: 56 + property bool showIcons: true + property bool equalWidthTabs: true ++ property bool enableArrowNavigation: true ++ property Item nextFocusTarget: null ++ property Item previousFocusTarget: null + + signal tabClicked(int index) + signal actionTriggered(int index) + ++ focus: false ++ activeFocusOnTab: true + height: tabHeight + ++ KeyNavigation.tab: nextFocusTarget ++ KeyNavigation.down: nextFocusTarget ++ KeyNavigation.backtab: previousFocusTarget ++ KeyNavigation.up: previousFocusTarget ++ ++ Keys.onPressed: (event) => { ++ if (!tabBar.activeFocus || tabRepeater.count === 0) ++ return ++ ++ function findSelectableIndex(startIndex, step) { ++ let idx = startIndex ++ for (let i = 0; i < tabRepeater.count; i++) { ++ idx = (idx + step + tabRepeater.count) % tabRepeater.count ++ const item = tabRepeater.itemAt(idx) ++ if (item && !item.isAction) ++ return idx ++ } ++ return -1 ++ } ++ ++ const goToIndex = (nextIndex) => { ++ if (nextIndex >= 0 && nextIndex !== tabBar.currentIndex) { ++ tabBar.currentIndex = nextIndex ++ tabBar.tabClicked(nextIndex) ++ } ++ } ++ ++ const resolveTarget = (item) => { ++ if (!item) ++ return null ++ ++ if (item.focusTarget) ++ return resolveTarget(item.focusTarget) ++ ++ return item ++ } ++ ++ const focusItem = (item) => { ++ const target = resolveTarget(item) ++ if (!target) ++ return false ++ ++ if (target.requestFocus) { ++ Qt.callLater(() => target.requestFocus()) ++ return true ++ } ++ ++ if (target.forceActiveFocus) { ++ Qt.callLater(() => target.forceActiveFocus()) ++ return true ++ } ++ ++ return false ++ } ++ ++ if (event.key === Qt.Key_Right && tabBar.enableArrowNavigation) { ++ const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : -1 ++ const nextIndex = findSelectableIndex(baseIndex, 1) ++ if (nextIndex >= 0) { ++ goToIndex(nextIndex) ++ event.accepted = true ++ } ++ } else if (event.key === Qt.Key_Left && tabBar.enableArrowNavigation) { ++ const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : 0 ++ const nextIndex = findSelectableIndex(baseIndex, -1) ++ if (nextIndex >= 0) { ++ goToIndex(nextIndex) ++ event.accepted = true ++ } ++ } else if (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) { ++ if (focusItem(tabBar.previousFocusTarget)) { ++ event.accepted = true ++ } ++ } else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down) { ++ if (focusItem(tabBar.nextFocusTarget)) { ++ event.accepted = true ++ } ++ } else if (event.key === Qt.Key_Up) { ++ if (focusItem(tabBar.previousFocusTarget)) { ++ event.accepted = true ++ } ++ } ++ } ++ + Row { + id: tabRow + anchors.fill: parent +@@ -77,7 +166,6 @@ Item { + if (tabItem.isAction) { + tabBar.actionTriggered(index) + } else { +- tabBar.currentIndex = index + tabBar.tabClicked(index) + } + } +diff --git a/docs/IPC.md b/docs/IPC.md +index 10d7f99..b902387 100644 +--- a/docs/IPC.md ++++ b/docs/IPC.md +@@ -501,6 +501,13 @@ Dashboard popup control with tab selection for overview, media, and weather info + - Parameters: `tab` - Optional tab to open when showing: "" (default), "overview", "media", or "weather" + - Returns: Success/failure message + ++### Target: `dankdash` ++DankDash wallpaper browser control. ++ ++**Functions:** ++- `wallpaper` - Toggle DankDash popup on focused screen with wallpaper tab selected ++ - Returns: Success/failure message ++ + ### Target: `file` + File browser controls for selecting wallpapers and profile images. + +@@ -512,7 +519,7 @@ File browser controls for selecting wallpapers and profile images. + - Both browsers support common image formats (jpg, jpeg, png, bmp, gif, webp) + + ### Target: `hypr` +-Hyprland-specific controls including keybinds cheatsheet and workspace overview (Hyprland only). ++Hyprland keybinds cheatsheet modal control (Hyprland only). + + **Functions:** + - `openBinds` - Show Hyprland keybinds cheatsheet modal +@@ -524,31 +531,13 @@ Hyprland-specific controls including keybinds cheatsheet and workspace overview + - `toggleBinds` - Toggle Hyprland keybinds cheatsheet modal visibility + - Returns: Success/failure message + - Note: Returns "HYPR_NOT_AVAILABLE" if not running Hyprland +-- `openOverview` - Show Hyprland workspace overview +- - Returns: "OVERVIEW_OPEN_SUCCESS" or "HYPR_NOT_AVAILABLE" +- - Displays all workspaces across all monitors with live window previews +- - Allows drag-and-drop window movement between workspaces and monitors +-- `closeOverview` - Hide Hyprland workspace overview +- - Returns: "OVERVIEW_CLOSE_SUCCESS" or "HYPR_NOT_AVAILABLE" +-- `toggleOverview` - Toggle Hyprland workspace overview visibility +- - Returns: "OVERVIEW_OPEN_SUCCESS", "OVERVIEW_CLOSE_SUCCESS", or "HYPR_NOT_AVAILABLE" +- +-**Keybinds Cheatsheet Description:** ++ ++**Description:** + Displays an auto-categorized cheatsheet of all Hyprland keybinds parsed from `~/.config/hypr`. Keybinds are organized into three columns: + - **Window / Monitor** - Window and monitor management keybinds (sorted by dispatcher) + - **Workspace** - Workspace switching and management (sorted by dispatcher) + - **Execute** - Application launchers and commands (sorted by keybind) + +-**Workspace Overview Description:** +-Displays a live overview of all workspaces across all monitors with window previews: +-- **Multi-monitor support** - Shows workspaces from all connected monitors with monitor name labels +-- **Live window previews** - Real-time screen capture of all windows on each workspace +-- **Drag-and-drop** - Move windows between workspaces and monitors by dragging +-- **Keyboard navigation** - Use Left/Right arrow keys to switch between workspaces on current monitor +-- **Visual indicators** - Active workspace highlighted when it contains windows +-- **Click to switch** - Click any workspace to switch to it +-- **Click outside or press Escape** - Close the overview +- + ### Modal Examples + ```bash + # Open application launcher +@@ -581,6 +570,9 @@ dms ipc call dash open overview + dms ipc call dash toggle media + dms ipc call dash open weather + ++# Open wallpaper browser ++dms ipc call dankdash wallpaper ++ + # Open file browsers + dms ipc call file browse wallpaper + dms ipc call file browse profile +@@ -588,11 +580,6 @@ dms ipc call file browse profile + # Show Hyprland keybinds cheatsheet (Hyprland only) + dms ipc call hypr toggleBinds + dms ipc call hypr openBinds +- +-# Show Hyprland workspace overview (Hyprland only) +-dms ipc call hypr toggleOverview +-dms ipc call hypr openOverview +-dms ipc call hypr closeOverview + ``` + + ## Common Usage Patterns +@@ -620,7 +607,6 @@ bind = SUPER, V, exec, qs -c dms ipc call clipboard toggle + bind = SUPER, P, exec, qs -c dms ipc call notepad toggle + bind = SUPER, X, exec, qs -c dms ipc call powermenu toggle + bind = SUPER, slash, exec, qs -c dms ipc call hypr toggleBinds +-bind = SUPER, Tab, exec, qs -c dms ipc call hypr toggleOverview + bind = , XF86AudioRaiseVolume, exec, qs -c dms ipc call audio increment 3 + bind = , XF86MonBrightnessUp, exec, qs -c dms ipc call brightness increment 5 "" + ``` +diff --git a/nix/niri.nix b/nix/niri.nix +index b6ba132..acb9140 100644 +--- a/nix/niri.nix ++++ b/nix/niri.nix +@@ -59,11 +59,6 @@ in { + allow-when-locked = true; + action = dms-ipc "audio" "micmute"; + }; +- "Mod+Alt+N" = { +- allow-when-locked = true; +- action = dms-ipc "night" "toggle"; +- hotkey-overlay.title = "Toggle Night Mode"; +- }; + } + // lib.attrsets.optionalAttrs cfg.enableSystemMonitoring { + "Mod+M" = { +@@ -86,6 +81,13 @@ in { + allow-when-locked = true; + action = dms-ipc "brightness" "decrement" "5" ""; + }; ++ } ++ // lib.attrsets.optionalAttrs cfg.enableNightMode { ++ "Mod+Alt+N" = { ++ allow-when-locked = true; ++ action = dms-ipc "night" "toggle"; ++ hotkey-overlay.title = "Toggle Night Mode"; ++ }; + }; + }) + +diff --git a/scripts/matugen-worker.sh b/scripts/matugen-worker.sh +index 3b0e5d7..39d44f4 100755 +--- a/scripts/matugen-worker.sh ++++ b/scripts/matugen-worker.sh +@@ -1,15 +1,14 @@ + #!/usr/bin/env bash + set -euo pipefail + +-if [ $# -lt 5 ]; then +- echo "Usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL --run" >&2 ++if [ $# -lt 4 ]; then ++ echo "Usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR --run" >&2 + exit 1 + fi + + STATE_DIR="$1" + SHELL_DIR="$2" + CONFIG_DIR="$3" +-SYNC_MODE_WITH_PORTAL="$4" + + if [ ! -d "$STATE_DIR" ]; then + echo "Error: STATE_DIR '$STATE_DIR' does not exist" >&2 +@@ -26,10 +25,10 @@ if [ ! -d "$CONFIG_DIR" ]; then + exit 1 + fi + +-shift 4 ++shift 3 # Remove STATE_DIR, SHELL_DIR, and CONFIG_DIR from arguments + + if [[ "${1:-}" != "--run" ]]; then +- echo "usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL --run" >&2 ++ echo "usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR --run" >&2 + exit 1 + fi + +@@ -63,27 +62,6 @@ key_of() { + echo "${kind}|${value}|${mode}|${icon}|${matugen_type}|${surface_base}|${run_user_templates}" | sha256sum | cut -d' ' -f1 + } + +-set_system_color_scheme() { +- local mode="$1" +- +- if [[ "$SYNC_MODE_WITH_PORTAL" != "true" ]]; then +- return 0 +- fi +- +- local target_scheme +- if [[ "$mode" == "light" ]]; then +- target_scheme="default" +- else +- target_scheme="prefer-dark" +- fi +- +- if command -v gsettings >/dev/null 2>&1; then +- gsettings set org.gnome.desktop.interface color-scheme "$target_scheme" >/dev/null 2>&1 || true +- elif command -v dconf >/dev/null 2>&1; then +- dconf write /org/gnome/desktop/interface/color-scheme "'$target_scheme'" >/dev/null 2>&1 || true +- fi +-} +- + build_once() { + local json="$1" + local kind value mode icon matugen_type surface_base run_user_templates +@@ -259,7 +237,7 @@ EOF + rm -f "$TMP_CONTENT_CFG" + popd >/dev/null + +- echo "$JSON" | grep -q '"primary"' || { echo "matugen JSON missing primary" >&2; set_system_color_scheme "$mode"; return 2; } ++ echo "$JSON" | grep -q '"primary"' || { echo "matugen JSON missing primary" >&2; return 2; } + printf "%s" "$JSON" > "$LAST_JSON" + + GTK_CSS="$CONFIG_DIR/gtk-3.0/gtk.css" +@@ -311,8 +289,6 @@ EOF + mv "$TMP" "$CONFIG_DIR/kitty/dank-theme.conf" + fi + fi +- +- set_system_color_scheme "$mode" + } + + if command -v pywalfox >/dev/null 2>&1 && [[ -f "$HOME/.cache/wal/colors.json" ]]; then +diff --git a/translations/en.json b/translations/en.json +index 522f8f2..1e5d746 100644 +--- a/translations/en.json ++++ b/translations/en.json +@@ -62,7 +62,7 @@ + { + "term": "About", + "context": "About", +- "reference": "Modules/Settings/AboutTab.qml:251, Modals/Settings/SettingsSidebar.qml:44", ++ "reference": "Modals/Settings/SettingsSidebar.qml:44, Modules/Settings/AboutTab.qml:251", + "comment": "" + }, + { +@@ -116,7 +116,7 @@ + { + "term": "All", + "context": "All", +- "reference": "Services/AppSearchService.qml:217, Services/AppSearchService.qml:233, Modules/AppDrawer/AppDrawerPopout.qml:45, Modules/AppDrawer/CategorySelector.qml:11, Modules/AppDrawer/AppLauncher.qml:16, Modules/AppDrawer/AppLauncher.qml:27, Modules/AppDrawer/AppLauncher.qml:28, Modules/AppDrawer/AppLauncher.qml:45, Modules/AppDrawer/AppLauncher.qml:46, Modules/AppDrawer/AppLauncher.qml:80, Modals/Spotlight/SpotlightModal.qml:59", ++ "reference": "Services/AppSearchService.qml:217, Services/AppSearchService.qml:233, Modals/Spotlight/SpotlightModal.qml:59, Modules/AppDrawer/CategorySelector.qml:11, Modules/AppDrawer/AppLauncher.qml:12, Modules/AppDrawer/AppLauncher.qml:23, Modules/AppDrawer/AppLauncher.qml:24, Modules/AppDrawer/AppLauncher.qml:41, Modules/AppDrawer/AppLauncher.qml:42, Modules/AppDrawer/AppLauncher.qml:76, Modules/AppDrawer/AppDrawerPopout.qml:45", + "comment": "" + }, + { +@@ -128,7 +128,7 @@ + { + "term": "All displays", + "context": "All displays", +- "reference": "Modules/Settings/DisplaysTab.qml:670", ++ "reference": "Modules/Settings/DisplaysTab.qml:666", + "comment": "" + }, + { +@@ -158,7 +158,7 @@ + { + "term": "Anonymous Identity (optional)", + "context": "Anonymous Identity (optional)", +- "reference": "Modals/WifiPasswordModal.qml:365", ++ "reference": "Modals/WifiPasswordModal.qml:282", + "comment": "" + }, + { +@@ -299,6 +299,12 @@ + "reference": "Modules/Settings/DockTab.qml:128", + "comment": "" + }, ++ { ++ "term": "Auto-location", ++ "context": "Auto-location", ++ "reference": "Modules/Settings/DisplaysTab.qml:364", ++ "comment": "" ++ }, + { + "term": "Auto-saving...", + "context": "Auto-saving...", +@@ -308,7 +314,7 @@ + { + "term": "Automatic Control", + "context": "Automatic Control", +- "reference": "Modules/Settings/DisplaysTab.qml:164", ++ "reference": "Modules/Settings/DisplaysTab.qml:162", + "comment": "" + }, + { +@@ -329,12 +335,6 @@ + "reference": "Modules/Settings/PersonalizationTab.qml:647", + "comment": "" + }, +- { +- "term": "Automatically detect location based on IP address", +- "context": "Automatically detect location based on IP address", +- "reference": "Modules/Settings/DisplaysTab.qml:369", +- "comment": "" +- }, + { + "term": "Automatically determine your location using your IP address", + "context": "Automatically determine your location using your IP address", +@@ -380,7 +380,7 @@ + { + "term": "Available Screens (", + "context": "Available Screens (", +- "reference": "Modules/Settings/DisplaysTab.qml:513", ++ "reference": "Modules/Settings/DisplaysTab.qml:509", + "comment": "" + }, + { +@@ -524,7 +524,7 @@ + { + "term": "Cancel", + "context": "Cancel", +- "reference": "Modals/DankColorPickerModal.qml:510, Modals/WifiPasswordModal.qml:468, Modules/Settings/PluginsTab.qml:1220, Modals/FileBrowser/FileBrowserModal.qml:952", ++ "reference": "Modals/DankColorPickerModal.qml:510, Modals/WifiPasswordModal.qml:385, Modals/FileBrowser/FileBrowserModal.qml:952, Modules/Settings/PluginsTab.qml:1220", + "comment": "" + }, + { +@@ -578,13 +578,13 @@ + { + "term": "Clear", + "context": "Clear", +- "reference": "Modules/Notifications/Center/NotificationHeader.qml:102", ++ "reference": "Modules/Notifications/Popup/NotificationPopup.qml:24, Modules/Notifications/Center/NotificationCard.qml:540, Modules/Notifications/Center/NotificationCard.qml:633", + "comment": "" + }, + { + "term": "Clear All", + "context": "Clear All", +- "reference": "Modals/Clipboard/ClipboardHistoryModal.qml:157", ++ "reference": "Modals/Clipboard/ClipboardHistoryModal.qml:157, Modules/Notifications/Center/NotificationHeader.qml:102", + "comment": "" + }, + { +@@ -620,7 +620,7 @@ + { + "term": "Close", + "context": "Close", +- "reference": "Modals/NetworkInfoModal.qml:129, Modals/NetworkWiredInfoModal.qml:129, Modules/SystemUpdatePopout.qml:333, Modules/DankBar/Widgets/RunningApps.qml:717", ++ "reference": "Modules/SystemUpdatePopout.qml:333, Modals/NetworkWiredInfoModal.qml:129, Modals/NetworkInfoModal.qml:129, Modules/DankBar/Widgets/RunningApps.qml:711", + "comment": "" + }, + { +@@ -638,7 +638,7 @@ + { + "term": "Color temperature for night mode", + "context": "Color temperature for night mode", +- "reference": "Modules/Settings/DisplaysTab.qml:145", ++ "reference": "Modules/Settings/DisplaysTab.qml:143", + "comment": "" + }, + { +@@ -701,12 +701,6 @@ + "reference": "Modules/Settings/AboutTab.qml:361", + "comment": "" + }, +- { +- "term": "Configuration activated", +- "context": "Configuration activated", +- "reference": "Services/NetworkManagerService.qml:320", +- "comment": "" +- }, + { + "term": "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.", + "context": "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.", +@@ -716,31 +710,25 @@ + { + "term": "Configure which displays show shell components", + "context": "Configure which displays show shell components", +- "reference": "Modules/Settings/DisplaysTab.qml:497", ++ "reference": "Modules/Settings/DisplaysTab.qml:493", + "comment": "" + }, + { + "term": "Connect", + "context": "Connect", +- "reference": "Modals/WifiPasswordModal.qml:505", ++ "reference": "Modals/WifiPasswordModal.qml:419", + "comment": "" + }, + { + "term": "Connect to Wi-Fi", + "context": "Connect to Wi-Fi", +- "reference": "Modals/WifiPasswordModal.qml:166", ++ "reference": "Modals/WifiPasswordModal.qml:114", + "comment": "" + }, + { + "term": "Connected Displays", + "context": "Connected Displays", +- "reference": "Modules/Settings/DisplaysTab.qml:490", +- "comment": "" +- }, +- { +- "term": "Connection failed. Check password and try again.", +- "context": "Connection failed. Check password and try again.", +- "reference": "Services/NetworkManagerService.qml:287", ++ "reference": "Modules/Settings/DisplaysTab.qml:486", + "comment": "" + }, + { +@@ -884,7 +872,7 @@ + { + "term": "DMS out of date", + "context": "DMS out of date", +- "reference": "Services/DMSService.qml:235", ++ "reference": "Services/DMSService.qml:234", + "comment": "" + }, + { +@@ -983,12 +971,6 @@ + "reference": "Modules/ControlCenter/BuiltinPlugins/VpnWidget.qml:90, Modules/DankBar/Popouts/VpnPopout.qml:213", + "comment": "" + }, +- { +- "term": "Disconnected from WiFi", +- "context": "Disconnected from WiFi", +- "reference": "Services/NetworkManagerService.qml:405", +- "comment": "" +- }, + { + "term": "Disk", + "context": "Disk", +@@ -1001,12 +983,6 @@ + "reference": "Modules/Settings/DankBarTab.qml:87", + "comment": "" + }, +- { +- "term": "Dismiss", +- "context": "Dismiss", +- "reference": "Modules/Notifications/Popup/NotificationPopup.qml:24, Modules/Notifications/Center/NotificationCard.qml:540, Modules/Notifications/Center/NotificationCard.qml:633", +- "comment": "" +- }, + { + "term": "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen", + "context": "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen", +@@ -1058,7 +1034,7 @@ + { + "term": "Do Not Disturb", + "context": "Do Not Disturb", +- "reference": "Modules/Notifications/Center/NotificationSettings.qml:131, Modules/Notifications/Center/NotificationHeader.qml:41", ++ "reference": "Modules/Notifications/Center/NotificationHeader.qml:41, Modules/Notifications/Center/NotificationSettings.qml:131", + "comment": "" + }, + { +@@ -1082,7 +1058,7 @@ + { + "term": "Domain (optional)", + "context": "Domain (optional)", +- "reference": "Modals/WifiPasswordModal.qml:397", ++ "reference": "Modals/WifiPasswordModal.qml:314", + "comment": "" + }, + { +@@ -1160,13 +1136,13 @@ + { + "term": "End", + "context": "End", +- "reference": "Modules/Settings/DisplaysTab.qml:318", ++ "reference": "Modules/Settings/DisplaysTab.qml:315", + "comment": "" + }, + { + "term": "Enter credentials for ", + "context": "Enter credentials for ", +- "reference": "Modals/WifiPasswordModal.qml:178", ++ "reference": "Modals/WifiPasswordModal.qml:121", + "comment": "" + }, + { +@@ -1190,7 +1166,7 @@ + { + "term": "Enter password for ", + "context": "Enter password for ", +- "reference": "Modals/WifiPasswordModal.qml:178", ++ "reference": "Modals/WifiPasswordModal.qml:121", + "comment": "" + }, + { +@@ -1211,30 +1187,6 @@ + "reference": "Modals/FileBrowser/FileInfo.qml:200", + "comment": "" + }, +- { +- "term": "Failed to activate configuration", +- "context": "Failed to activate configuration", +- "reference": "Services/NetworkManagerService.qml:316", +- "comment": "" +- }, +- { +- "term": "Failed to connect to ", +- "context": "Failed to connect to ", +- "reference": "Services/NetworkManagerService.qml:289", +- "comment": "" +- }, +- { +- "term": "Failed to disconnect WiFi", +- "context": "Failed to disconnect WiFi", +- "reference": "Services/NetworkManagerService.qml:403", +- "comment": "" +- }, +- { +- "term": "Failed to enable WiFi", +- "context": "Failed to enable WiFi", +- "reference": "Services/NetworkManagerService.qml:508", +- "comment": "" +- }, + { + "term": "Failed to set profile image", + "context": "Failed to set profile image", +@@ -1247,12 +1199,6 @@ + "reference": "Services/PortalService.qml:159", + "comment": "" + }, +- { +- "term": "Failed to start connection to ", +- "context": "Failed to start connection to ", +- "reference": "Services/NetworkManagerService.qml:389", +- "comment": "" +- }, + { + "term": "Feels Like", + "context": "Feels Like", +@@ -1292,7 +1238,7 @@ + { + "term": "Font Family", + "context": "Font Family", +- "reference": "Modules/Settings/ThemeColorsTab.qml:944, Modules/Notepad/NotepadSettings.qml:220", ++ "reference": "Modules/Notepad/NotepadSettings.qml:220, Modules/Settings/ThemeColorsTab.qml:944", + "comment": "" + }, + { +@@ -1334,13 +1280,7 @@ + { + "term": "Forget Network", + "context": "Forget Network", +- "reference": "Modules/ControlCenter/Details/NetworkDetail.qml:606", +- "comment": "" +- }, +- { +- "term": "Forgot network ", +- "context": "Forgot network ", +- "reference": "Services/NetworkManagerService.qml:465", ++ "reference": "Modules/ControlCenter/Details/NetworkDetail.qml:598", + "comment": "" + }, + { +@@ -1392,9 +1332,9 @@ + "comment": "" + }, + { +- "term": "Gamma control not available. Requires DMS API v6+.", +- "context": "Gamma control not available. Requires DMS API v6+.", +- "reference": "Modules/Settings/DisplaysTab.qml:119", ++ "term": "Geoclue service not running - cannot auto-detect location", ++ "context": "Geoclue service not running - cannot auto-detect location", ++ "reference": "Modules/Settings/DisplaysTab.qml:365", + "comment": "" + }, + { +@@ -1484,7 +1424,7 @@ + { + "term": "Hour", + "context": "Hour", +- "reference": "Modules/Settings/DisplaysTab.qml:255", ++ "reference": "Modules/Settings/DisplaysTab.qml:252", + "comment": "" + }, + { +@@ -1547,12 +1487,6 @@ + "reference": "Modules/Settings/PersonalizationTab.qml:923", + "comment": "" + }, +- { +- "term": "Incorrect password", +- "context": "Incorrect password", +- "reference": "Modals/WifiPasswordModal.qml:189", +- "comment": "" +- }, + { + "term": "Individual Batteries", + "context": "Individual Batteries", +@@ -1640,13 +1574,13 @@ + { + "term": "Latitude", + "context": "Latitude", +- "reference": "Modules/Settings/DisplaysTab.qml:402, Modules/Settings/TimeWeatherTab.qml:665", ++ "reference": "Modules/Settings/DisplaysTab.qml:398, Modules/Settings/TimeWeatherTab.qml:665", + "comment": "" + }, + { + "term": "Launch", + "context": "Launch", +- "reference": "Modules/AppDrawer/AppDrawerPopout.qml:923, Modals/Spotlight/SpotlightContextMenu.qml:251", ++ "reference": "Modals/Spotlight/SpotlightContextMenu.qml:251, Modules/AppDrawer/AppDrawerPopout.qml:895", + "comment": "" + }, + { +@@ -1658,7 +1592,7 @@ + { + "term": "Launch on dGPU", + "context": "Launch on dGPU", +- "reference": "Modules/AppDrawer/AppDrawerPopout.qml:983, Modules/Dock/DockContextMenu.qml:417, Modals/Spotlight/SpotlightContextMenu.qml:312", ++ "reference": "Modals/Spotlight/SpotlightContextMenu.qml:312, Modules/AppDrawer/AppDrawerPopout.qml:955, Modules/Dock/DockContextMenu.qml:417", + "comment": "" + }, + { +@@ -1748,7 +1682,7 @@ + { + "term": "Longitude", + "context": "Longitude", +- "reference": "Modules/Settings/DisplaysTab.qml:425, Modules/Settings/TimeWeatherTab.qml:716", ++ "reference": "Modules/Settings/DisplaysTab.qml:421, Modules/Settings/TimeWeatherTab.qml:716", + "comment": "" + }, + { +@@ -1766,7 +1700,7 @@ + { + "term": "Manual Coordinates", + "context": "Manual Coordinates", +- "reference": "Modules/Settings/DisplaysTab.qml:390", ++ "reference": "Modules/Settings/DisplaysTab.qml:386", + "comment": "" + }, + { +@@ -1790,7 +1724,7 @@ + { + "term": "Matugen Palette", + "context": "Matugen Palette", +- "reference": "Modules/Settings/PersonalizationTab.qml:1276, Modules/Settings/ThemeColorsTab.qml:629", ++ "reference": "Modules/Settings/ThemeColorsTab.qml:629, Modules/Settings/PersonalizationTab.qml:1276", + "comment": "" + }, + { +@@ -1856,7 +1790,7 @@ + { + "term": "Minute", + "context": "Minute", +- "reference": "Modules/Settings/DisplaysTab.qml:263", ++ "reference": "Modules/Settings/DisplaysTab.qml:260", + "comment": "" + }, + { +@@ -1928,13 +1862,13 @@ + { + "term": "Network Info", + "context": "Network Info", +- "reference": "Modules/ControlCenter/Details/NetworkDetail.qml:340, Modules/ControlCenter/Details/NetworkDetail.qml:583", ++ "reference": "Modules/ControlCenter/Details/NetworkDetail.qml:340, Modules/ControlCenter/Details/NetworkDetail.qml:575", + "comment": "" + }, + { + "term": "Network Information", + "context": "Network Information", +- "reference": "Modals/NetworkInfoModal.qml:59, Modals/NetworkWiredInfoModal.qml:59", ++ "reference": "Modals/NetworkWiredInfoModal.qml:59, Modals/NetworkInfoModal.qml:59", + "comment": "" + }, + { +@@ -2054,7 +1988,7 @@ + { + "term": "Notepad", + "context": "Notepad", +- "reference": "DMSShell.qml:413, Modules/Settings/DankBarTab.qml:175", ++ "reference": "DMSShell.qml:407, Modules/Settings/DankBarTab.qml:175", + "comment": "" + }, + { +@@ -2090,7 +2024,7 @@ + { + "term": "Notification Popups", + "context": "Notification Popups", +- "reference": "Modules/Settings/WidgetTweaksTab.qml:585, Modules/Settings/DisplaysTab.qml:24", ++ "reference": "Modules/Settings/DisplaysTab.qml:24, Modules/Settings/WidgetTweaksTab.qml:585", + "comment": "" + }, + { +@@ -2144,7 +2078,7 @@ + { + "term": "Only adjust gamma based on time or location rules.", + "context": "Only adjust gamma based on time or location rules.", +- "reference": "Modules/Settings/DisplaysTab.qml:165", ++ "reference": "Modules/Settings/DisplaysTab.qml:163", + "comment": "" + }, + { +@@ -2192,7 +2126,7 @@ + { + "term": "Password", + "context": "Password", +- "reference": "Modals/WifiPasswordModal.qml:274", ++ "reference": "Modals/WifiPasswordModal.qml:203", + "comment": "" + }, + { +@@ -2234,7 +2168,7 @@ + { + "term": "Pin to Dock", + "context": "Pin to Dock", +- "reference": "Modules/AppDrawer/AppDrawerPopout.qml:786, Modules/Dock/DockContextMenu.qml:370, Modals/Spotlight/SpotlightContextMenu.qml:110, Modals/Spotlight/SpotlightContextMenu.qml:113", ++ "reference": "Modals/Spotlight/SpotlightContextMenu.qml:110, Modals/Spotlight/SpotlightContextMenu.qml:113, Modules/AppDrawer/AppDrawerPopout.qml:758, Modules/Dock/DockContextMenu.qml:370", + "comment": "" + }, + { +@@ -2510,7 +2444,7 @@ + { + "term": "Save", + "context": "Save", +- "reference": "Modules/Notepad/Notepad.qml:480, Modules/Notepad/NotepadTextEditor.qml:511, Modals/FileBrowser/FileBrowserModal.qml:818", ++ "reference": "Modals/FileBrowser/FileBrowserModal.qml:818, Modules/Notepad/NotepadTextEditor.qml:511, Modules/Notepad/Notepad.qml:480", + "comment": "" + }, + { +@@ -2636,7 +2570,7 @@ + { + "term": "Select the palette algorithm used for wallpaper-based colors", + "context": "Select the palette algorithm used for wallpaper-based colors", +- "reference": "Modules/Settings/PersonalizationTab.qml:1277, Modules/Settings/ThemeColorsTab.qml:630", ++ "reference": "Modules/Settings/ThemeColorsTab.qml:630, Modules/Settings/PersonalizationTab.qml:1277", + "comment": "" + }, + { +@@ -2672,7 +2606,7 @@ + { + "term": "Settings", + "context": "Settings", +- "reference": "Services/AppSearchService.qml:176, Modules/DankDash/DankDashPopout.qml:142, Modals/Settings/SettingsModal.qml:165", ++ "reference": "Services/AppSearchService.qml:176, Modals/Settings/SettingsModal.qml:165, Modules/DankDash/DankDashPopout.qml:142", + "comment": "" + }, + { +@@ -2720,13 +2654,13 @@ + { + "term": "Show on all connected displays", + "context": "Show on all connected displays", +- "reference": "Modules/Settings/DisplaysTab.qml:671", ++ "reference": "Modules/Settings/DisplaysTab.qml:667", + "comment": "" + }, + { + "term": "Show on screens:", + "context": "Show on screens:", +- "reference": "Modules/Settings/DisplaysTab.qml:655", ++ "reference": "Modules/Settings/DisplaysTab.qml:651", + "comment": "" + }, + { +@@ -2744,7 +2678,7 @@ + { + "term": "Show password", + "context": "Show password", +- "reference": "Modals/WifiPasswordModal.qml:440", ++ "reference": "Modals/WifiPasswordModal.qml:357", + "comment": "" + }, + { +@@ -2834,7 +2768,7 @@ + { + "term": "Start", + "context": "Start", +- "reference": "Modules/Settings/DisplaysTab.qml:275", ++ "reference": "Modules/Settings/DisplaysTab.qml:272", + "comment": "" + }, + { +@@ -2882,7 +2816,7 @@ + { + "term": "Switch User", + "context": "Switch User", +- "reference": "Modules/Greetd/GreeterContent.qml:549", ++ "reference": "Modules/Greetd/GreeterContent.qml:567", + "comment": "" + }, + { +@@ -2996,7 +2930,7 @@ + { + "term": "Temperature", + "context": "Temperature", +- "reference": "Modules/Settings/DisplaysTab.qml:144", ++ "reference": "Modules/Settings/DisplaysTab.qml:142", + "comment": "" + }, + { +@@ -3074,7 +3008,7 @@ + { + "term": "To update, run the following command:", + "context": "To update, run the following command:", +- "reference": "Services/DMSService.qml:236", ++ "reference": "Services/DMSService.qml:235", + "comment": "" + }, + { +@@ -3134,7 +3068,7 @@ + { + "term": "Unpin from Dock", + "context": "Unpin from Dock", +- "reference": "Modules/AppDrawer/AppDrawerPopout.qml:786, Modules/Dock/DockContextMenu.qml:370, Modals/Spotlight/SpotlightContextMenu.qml:113", ++ "reference": "Modals/Spotlight/SpotlightContextMenu.qml:113, Modules/AppDrawer/AppDrawerPopout.qml:758, Modules/Dock/DockContextMenu.qml:370", + "comment": "" + }, + { +@@ -3191,12 +3125,6 @@ + "reference": "Modules/Settings/TimeWeatherTab.qml:545", + "comment": "" + }, +- { +- "term": "Use IP Location", +- "context": "Use IP Location", +- "reference": "Modules/Settings/DisplaysTab.qml:368", +- "comment": "" +- }, + { + "term": "Use Monospace Font", + "context": "Use Monospace Font", +@@ -3215,6 +3143,12 @@ + "reference": "Modules/Settings/WidgetTweaksTab.qml:194", + "comment": "" + }, ++ { ++ "term": "Use automatic location detection (geoclue2)", ++ "context": "Use automatic location detection (geoclue2)", ++ "reference": "Modules/Settings/DisplaysTab.qml:365", ++ "comment": "" ++ }, + { + "term": "Use custom command for update your system", + "context": "Use custom command for update your system", +@@ -3254,13 +3188,13 @@ + { + "term": "Username", + "context": "Username", +- "reference": "Modals/WifiPasswordModal.qml:237", ++ "reference": "Modals/WifiPasswordModal.qml:166", + "comment": "" + }, + { + "term": "Uses sunrise/sunset times to automatically adjust night mode based on your location.", + "context": "Uses sunrise/sunset times to automatically adjust night mode based on your location.", +- "reference": "Modules/Settings/DisplaysTab.qml:446", ++ "reference": "Modules/Settings/DisplaysTab.qml:442", + "comment": "" + }, + { +@@ -3353,18 +3287,6 @@ + "reference": "Modules/Settings/LauncherTab.qml:517", + "comment": "" + }, +- { +- "term": "WiFi disabled", +- "context": "WiFi disabled", +- "reference": "Services/NetworkManagerService.qml:498", +- "comment": "" +- }, +- { +- "term": "WiFi enabled", +- "context": "WiFi enabled", +- "reference": "Services/NetworkManagerService.qml:498, Services/NetworkManagerService.qml:510", +- "comment": "" +- }, + { + "term": "WiFi is off", + "context": "WiFi is off", +diff --git a/translations/poexports/ja.json b/translations/poexports/ja.json +index d173441..01d02d7 100644 +--- a/translations/poexports/ja.json ++++ b/translations/poexports/ja.json +@@ -167,9 +167,6 @@ + "Automatically cycle through wallpapers in the same folder": { + "Automatically cycle through wallpapers in the same folder": "同じフォルダ内の壁紙を自動的に切り替える" + }, +- "Automatically detect location based on IP address": { +- "Automatically detect location based on IP address": "IPアドレスに基づいて位置を自動的に検出" +- }, + "Automatically determine your location using your IP address": { + "Automatically determine your location using your IP address": "IPアドレスを使用して現在地を自動的に特定します" + }, +@@ -353,9 +350,6 @@ + "Compositor:": { + "Compositor:": "コンポジター:" + }, +- "Configuration activated": { +- "Configuration activated": "" +- }, + "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": { + "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": "名前付きワークスペースのアイコンを設定します。両方が有効になっている場合、アイコンが数字よりも優先されます。" + }, +@@ -371,9 +365,6 @@ + "Connected Displays": { + "Connected Displays": "接続されたディスプレイ" + }, +- "Connection failed. Check password and try again.": { +- "Connection failed. Check password and try again.": "" +- }, + "Contrast": { + "Contrast": "コントラスト" + }, +@@ -494,18 +485,12 @@ + "Disconnect": { + "Disconnect": "切断" + }, +- "Disconnected from WiFi": { +- "Disconnected from WiFi": "" +- }, + "Disk": { + "Disk": "ディスク" + }, + "Disk Usage": { + "Disk Usage": "ディスク使用率" + }, +- "Dismiss": { +- "Dismiss": "" +- }, + "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": { + "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "画面の上、下、左、右の端に配置できる、ピン留めされた実行中のアプリケーションを含むドックを表示します" + }, +@@ -608,27 +593,6 @@ + "F1/I: Toggle • F10: Help": { + "F1/I: Toggle • F10: Help": "F1/I: 切り替え • F10: ヘルプ" + }, +- "Failed to activate configuration": { +- "Failed to activate configuration": "" +- }, +- "Failed to connect to ": { +- "Failed to connect to ": "" +- }, +- "Failed to disconnect WiFi": { +- "Failed to disconnect WiFi": "" +- }, +- "Failed to enable WiFi": { +- "Failed to enable WiFi": "" +- }, +- "Failed to set profile image": { +- "Failed to set profile image": "プロフィール画像の設定に失敗しました" +- }, +- "Failed to set profile image: ": { +- "Failed to set profile image: ": "プロフィール画像の設定に失敗しました: " +- }, +- "Failed to start connection to ": { +- "Failed to start connection to ": "" +- }, + "Feels Like": { + "Feels Like": "どうやら" + }, +@@ -671,9 +635,6 @@ + "Forget Network": { + "Forget Network": "ネットワークを忘れる" + }, +- "Forgot network ": { +- "Forgot network ": "" +- }, + "Format Legend": { + "Format Legend": "フォーマット凡例" + }, +@@ -698,9 +659,6 @@ + "Gamma Control": { + "Gamma Control": "ガンマ設定" + }, +- "Gamma control not available. Requires DMS API v6+.": { +- "Gamma control not available. Requires DMS API v6+.": "ガンマ制御は使用できません。DMS API v6+ が必要です。" +- }, + "Geoclue service not running - cannot auto-detect location": { + "Geoclue service not running - cannot auto-detect location": "ジオクルー サービスが実行されていない - 位置を自動検出できません" + }, +@@ -779,9 +737,6 @@ + "Include Transitions": { + "Include Transitions": "トランジションを含める" + }, +- "Incorrect password": { +- "Incorrect password": "" +- }, + "Individual Batteries": { + "Individual Batteries": "バッテリーごと" + }, +@@ -1115,9 +1070,6 @@ + "Percentage": { + "Percentage": "百分率" + }, +- "Permission denied to set profile image.": { +- "Permission denied to set profile image.": "プロフィール画像の設定権限が拒否されました。" +- }, + "Personalization": { + "Personalization": "パーソナライゼーション" + }, +@@ -1196,12 +1148,6 @@ + "Process": { + "Process": "プロセス" + }, +- "Profile Image Error": { +- "Profile Image Error": "プロフィール画像エラー" +- }, +- "Profile image is too large. Please use a smaller image.": { +- "Profile image is too large. Please use a smaller image.": "プロフィール画像が大きすぎます。より小さい画像を使用してください。" +- }, + "QML, JavaScript, Go": { + "QML, JavaScript, Go": "QML, JavaScript, Go" + }, +@@ -1328,9 +1274,6 @@ + "Select which transitions to include in randomization": { + "Select which transitions to include in randomization": "含めたいトランジションをランダム化に選択" + }, +- "Selected image file not found.": { +- "Selected image file not found.": "選択した画像ファイルが見つかりませんでした。" +- }, + "Separator": { + "Separator": "区切り" + }, +@@ -1601,9 +1544,6 @@ + "Use Fahrenheit instead of Celsius for temperature": { + "Use Fahrenheit instead of Celsius for temperature": "温度は摂氏ではなく華氏を使用" + }, +- "Use IP Location": { +- "Use IP Location": "IP ロケーションの使用" +- }, + "Use Monospace Font": { + "Use Monospace Font": "等幅フォントを使用" + }, +@@ -1685,12 +1625,6 @@ + "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": { + "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "有効にすると、アプリはアルファベット順に並べ替えられます。無効にすると、アプリは使用頻度で並べ替えられます。" + }, +- "WiFi disabled": { +- "WiFi disabled": "" +- }, +- "WiFi enabled": { +- "WiFi enabled": "" +- }, + "WiFi is off": { + "WiFi is off": "Wi-Fiはオフ中" + }, +diff --git a/translations/poexports/pt.json b/translations/poexports/pt.json +index 0e458cb..4348e99 100644 +--- a/translations/poexports/pt.json ++++ b/translations/poexports/pt.json +@@ -167,9 +167,6 @@ + "Automatically cycle through wallpapers in the same folder": { + "Automatically cycle through wallpapers in the same folder": "Circular automaticamente dentre os papéis de parede na mesma pasta" + }, +- "Automatically detect location based on IP address": { +- "Automatically detect location based on IP address": "" +- }, + "Automatically determine your location using your IP address": { + "Automatically determine your location using your IP address": "Detectar automaticamente a sua localização usando o seu endereço de IP" + }, +@@ -353,9 +350,6 @@ + "Compositor:": { + "Compositor:": "Compositor:" + }, +- "Configuration activated": { +- "Configuration activated": "" +- }, + "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": { + "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": "Configurações de ícones para área de trabalho nomeadas. Ícones tem prioridade sobre números, isso quando ambos estão habilitados. " + }, +@@ -371,9 +365,6 @@ + "Connected Displays": { + "Connected Displays": "Telas Conectadas" + }, +- "Connection failed. Check password and try again.": { +- "Connection failed. Check password and try again.": "" +- }, + "Contrast": { + "Contrast": "Contraste" + }, +@@ -494,18 +485,12 @@ + "Disconnect": { + "Disconnect": "Desconectar" + }, +- "Disconnected from WiFi": { +- "Disconnected from WiFi": "" +- }, + "Disk": { + "Disk": "Disco" + }, + "Disk Usage": { + "Disk Usage": "Uso de Disco" + }, +- "Dismiss": { +- "Dismiss": "" +- }, + "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": { + "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "Exibir um dock com aplicativos que estão sendo utilizados, e que pode ser posicionada no superior, inferior, esquerda ou direita dos cantos da tela" + }, +@@ -608,27 +593,6 @@ + "F1/I: Toggle • F10: Help": { + "F1/I: Toggle • F10: Help": "F1/I: Ativar • F10: Ajuda" + }, +- "Failed to activate configuration": { +- "Failed to activate configuration": "" +- }, +- "Failed to connect to ": { +- "Failed to connect to ": "" +- }, +- "Failed to disconnect WiFi": { +- "Failed to disconnect WiFi": "" +- }, +- "Failed to enable WiFi": { +- "Failed to enable WiFi": "" +- }, +- "Failed to set profile image": { +- "Failed to set profile image": "" +- }, +- "Failed to set profile image: ": { +- "Failed to set profile image: ": "" +- }, +- "Failed to start connection to ": { +- "Failed to start connection to ": "" +- }, + "Feels Like": { + "Feels Like": "Sensação Térmica" + }, +@@ -671,9 +635,6 @@ + "Forget Network": { + "Forget Network": "Esquecer Internet" + }, +- "Forgot network ": { +- "Forgot network ": "" +- }, + "Format Legend": { + "Format Legend": "Formatar Legenda" + }, +@@ -698,9 +659,6 @@ + "Gamma Control": { + "Gamma Control": "Controle de Gama" + }, +- "Gamma control not available. Requires DMS API v6+.": { +- "Gamma control not available. Requires DMS API v6+.": "" +- }, + "Geoclue service not running - cannot auto-detect location": { + "Geoclue service not running - cannot auto-detect location": "" + }, +@@ -779,9 +737,6 @@ + "Include Transitions": { + "Include Transitions": "Incluir Transições" + }, +- "Incorrect password": { +- "Incorrect password": "" +- }, + "Individual Batteries": { + "Individual Batteries": "Baterias Individuais" + }, +@@ -1115,9 +1070,6 @@ + "Percentage": { + "Percentage": "Porcetagem" + }, +- "Permission denied to set profile image.": { +- "Permission denied to set profile image.": "" +- }, + "Personalization": { + "Personalization": "Personalização" + }, +@@ -1196,12 +1148,6 @@ + "Process": { + "Process": "Processo" + }, +- "Profile Image Error": { +- "Profile Image Error": "" +- }, +- "Profile image is too large. Please use a smaller image.": { +- "Profile image is too large. Please use a smaller image.": "" +- }, + "QML, JavaScript, Go": { + "QML, JavaScript, Go": "QML, JavaScript, Go" + }, +@@ -1328,9 +1274,6 @@ + "Select which transitions to include in randomization": { + "Select which transitions to include in randomization": "Selecionar quais transições incluir na randomização" + }, +- "Selected image file not found.": { +- "Selected image file not found.": "" +- }, + "Separator": { + "Separator": "Separador" + }, +@@ -1601,9 +1544,6 @@ + "Use Fahrenheit instead of Celsius for temperature": { + "Use Fahrenheit instead of Celsius for temperature": "Usar Fahrenheit em vez de Celsius para temperatura" + }, +- "Use IP Location": { +- "Use IP Location": "" +- }, + "Use Monospace Font": { + "Use Monospace Font": "Usar Fonte Monoespaçada" + }, +@@ -1685,12 +1625,6 @@ + "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": { + "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "Quando ativado, apps são ordenados alfabeticamente. Quando desativado, apps são ordenados por frequência de uso" + }, +- "WiFi disabled": { +- "WiFi disabled": "" +- }, +- "WiFi enabled": { +- "WiFi enabled": "" +- }, + "WiFi is off": { + "WiFi is off": "WiFi desligado" + }, +diff --git a/translations/poexports/zh_CN.json b/translations/poexports/zh_CN.json +index 663fd2b..c8ab15a 100644 +--- a/translations/poexports/zh_CN.json ++++ b/translations/poexports/zh_CN.json +@@ -167,9 +167,6 @@ + "Automatically cycle through wallpapers in the same folder": { + "Automatically cycle through wallpapers in the same folder": "自动轮换文件夹中的壁纸" + }, +- "Automatically detect location based on IP address": { +- "Automatically detect location based on IP address": "根据 IP 地址自动检测位置" +- }, + "Automatically determine your location using your IP address": { + "Automatically determine your location using your IP address": "通过 IP 地址自动检测您的位置" + }, +@@ -353,9 +350,6 @@ + "Compositor:": { + "Compositor:": "合成器:" + }, +- "Configuration activated": { +- "Configuration activated": "" +- }, + "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": { + "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.": "为已命名工作区配置图标。当数字和图标同时启用时,图标优先生效。" + }, +@@ -371,9 +365,6 @@ + "Connected Displays": { + "Connected Displays": "已连接显示器" + }, +- "Connection failed. Check password and try again.": { +- "Connection failed. Check password and try again.": "" +- }, + "Contrast": { + "Contrast": "对比度" + }, +@@ -494,18 +485,12 @@ + "Disconnect": { + "Disconnect": "断开连接" + }, +- "Disconnected from WiFi": { +- "Disconnected from WiFi": "" +- }, + "Disk": { + "Disk": "磁盘" + }, + "Disk Usage": { + "Disk Usage": "磁盘占用" + }, +- "Dismiss": { +- "Dismiss": "" +- }, + "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": { + "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "显示一个包含固定和运行中应用的程序坞,可放置在屏幕四边任意位置" + }, +@@ -608,27 +593,6 @@ + "F1/I: Toggle • F10: Help": { + "F1/I: Toggle • F10: Help": "F1/I: 切换 • F10: 帮助" + }, +- "Failed to activate configuration": { +- "Failed to activate configuration": "" +- }, +- "Failed to connect to ": { +- "Failed to connect to ": "" +- }, +- "Failed to disconnect WiFi": { +- "Failed to disconnect WiFi": "" +- }, +- "Failed to enable WiFi": { +- "Failed to enable WiFi": "" +- }, +- "Failed to set profile image": { +- "Failed to set profile image": "无法设置个人资料图片" +- }, +- "Failed to set profile image: ": { +- "Failed to set profile image: ": "无法设置个人资料图片:" +- }, +- "Failed to start connection to ": { +- "Failed to start connection to ": "" +- }, + "Feels Like": { + "Feels Like": "体感温度" + }, +@@ -671,9 +635,6 @@ + "Forget Network": { + "Forget Network": "取消保存" + }, +- "Forgot network ": { +- "Forgot network ": "" +- }, + "Format Legend": { + "Format Legend": "参考格式" + }, +@@ -698,9 +659,6 @@ + "Gamma Control": { + "Gamma Control": "伽马控制" + }, +- "Gamma control not available. Requires DMS API v6+.": { +- "Gamma control not available. Requires DMS API v6+.": "伽玛控制不可用。需要 DMS API v6+。" +- }, + "Geoclue service not running - cannot auto-detect location": { + "Geoclue service not running - cannot auto-detect location": "Geoclue 服务未运行 - 无法自动检测位置" + }, +@@ -779,9 +737,6 @@ + "Include Transitions": { + "Include Transitions": "包含过渡效果" + }, +- "Incorrect password": { +- "Incorrect password": "" +- }, + "Individual Batteries": { + "Individual Batteries": "分别显示电池" + }, +@@ -1115,9 +1070,6 @@ + "Percentage": { + "Percentage": "占用率" + }, +- "Permission denied to set profile image.": { +- "Permission denied to set profile image.": "因权限问题,无法设置个人资料图片。" +- }, + "Personalization": { + "Personalization": "个性化" + }, +@@ -1196,12 +1148,6 @@ + "Process": { + "Process": "进程" + }, +- "Profile Image Error": { +- "Profile Image Error": "个人资料图片错误" +- }, +- "Profile image is too large. Please use a smaller image.": { +- "Profile image is too large. Please use a smaller image.": "个人资料图片太大。请选择更小的图片。" +- }, + "QML, JavaScript, Go": { + "QML, JavaScript, Go": "QML, JavaScript, Go" + }, +@@ -1328,9 +1274,6 @@ + "Select which transitions to include in randomization": { + "Select which transitions to include in randomization": "选择在随机轮换中使用的过渡效果" + }, +- "Selected image file not found.": { +- "Selected image file not found.": "找不到选定的图片文件。" +- }, + "Separator": { + "Separator": "分隔符" + }, +@@ -1601,9 +1544,6 @@ + "Use Fahrenheit instead of Celsius for temperature": { + "Use Fahrenheit instead of Celsius for temperature": "使用华氏度显示温度" + }, +- "Use IP Location": { +- "Use IP Location": "使用IP所在位置" +- }, + "Use Monospace Font": { + "Use Monospace Font": "使用等宽字体" + }, +@@ -1685,12 +1625,6 @@ + "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": { + "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "启用后,应用按字母顺序排序;禁用则按使用频率排序" + }, +- "WiFi disabled": { +- "WiFi disabled": "" +- }, +- "WiFi enabled": { +- "WiFi enabled": "" +- }, + "WiFi is off": { + "WiFi is off": "Wi-Fi 已关闭" + }, +diff --git a/translations/template.json b/translations/template.json +index ed21318..a82e849 100644 +--- a/translations/template.json ++++ b/translations/template.json +@@ -350,42 +350,42 @@ + "comment": "" + }, + { +- "term": "Auto-saving...", ++ "term": "Auto-location", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { +- "term": "Automatic Control", ++ "term": "Auto-saving...", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { +- "term": "Automatic Cycling", ++ "term": "Automatic Control", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { +- "term": "Automatically calculate popup distance from bar edge.", ++ "term": "Automatic Cycling", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { +- "term": "Automatically cycle through wallpapers in the same folder", ++ "term": "Automatically calculate popup distance from bar edge.", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { +- "term": "Automatically detect location based on IP address", ++ "term": "Automatically cycle through wallpapers in the same folder", + "translation": "", + "context": "", + "reference": "", +@@ -818,13 +818,6 @@ + "reference": "", + "comment": "" + }, +- { +- "term": "Configuration activated", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, + { + "term": "Configure icons for named workspaces. Icons take priority over numbers when both are enabled.", + "translation": "", +@@ -860,13 +853,6 @@ + "reference": "", + "comment": "" + }, +- { +- "term": "Connection failed. Check password and try again.", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, + { + "term": "Contrast", + "translation": "", +@@ -1147,13 +1133,6 @@ + "reference": "", + "comment": "" + }, +- { +- "term": "Disconnected from WiFi", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, + { + "term": "Disk", + "translation": "", +@@ -1168,13 +1147,6 @@ + "reference": "", + "comment": "" + }, +- { +- "term": "Dismiss", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, + { + "term": "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen", + "translation": "", +@@ -1413,34 +1385,6 @@ + "reference": "", + "comment": "" + }, +- { +- "term": "Failed to activate configuration", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, +- { +- "term": "Failed to connect to ", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, +- { +- "term": "Failed to disconnect WiFi", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, +- { +- "term": "Failed to enable WiFi", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, + { + "term": "Failed to set profile image", + "translation": "", +@@ -1455,13 +1399,6 @@ + "reference": "", + "comment": "" + }, +- { +- "term": "Failed to start connection to ", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, + { + "term": "Feels Like", + "translation": "", +@@ -1560,13 +1497,6 @@ + "reference": "", + "comment": "" + }, +- { +- "term": "Forgot network ", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, + { + "term": "Format Legend", + "translation": "", +@@ -1624,7 +1554,7 @@ + "comment": "" + }, + { +- "term": "Gamma control not available. Requires DMS API v6+.", ++ "term": "Geoclue service not running - cannot auto-detect location", + "translation": "", + "context": "", + "reference": "", +@@ -1805,13 +1735,6 @@ + "reference": "", + "comment": "" + }, +- { +- "term": "Incorrect password", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, + { + "term": "Individual Batteries", + "translation": "", +@@ -3724,28 +3647,28 @@ + "comment": "" + }, + { +- "term": "Use IP Location", ++ "term": "Use Monospace Font", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { +- "term": "Use Monospace Font", ++ "term": "Use System Theme", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { +- "term": "Use System Theme", ++ "term": "Use animated wave progress bars for media playback", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { +- "term": "Use animated wave progress bars for media playback", ++ "term": "Use automatic location detection (geoclue2)", + "translation": "", + "context": "", + "reference": "", +@@ -3912,20 +3835,6 @@ + "reference": "", + "comment": "" + }, +- { +- "term": "WiFi disabled", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, +- { +- "term": "WiFi enabled", +- "translation": "", +- "context": "", +- "reference": "", +- "comment": "" +- }, + { + "term": "WiFi is off", + "translation": "",