1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

feat: Wallpapers built into the Media Hub

- Thanks @TaylanTatli for the inspiration
This commit is contained in:
purian23
2025-10-21 23:09:10 -04:00
parent 2a89885437
commit 647c358b72
46 changed files with 7271 additions and 2401 deletions

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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 = ""

View File

@@ -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

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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: {

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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) {

View File

@@ -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
}
}

View File

@@ -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
}
}
}
}
}
}
}

View File

@@ -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
}
}
}
}
}
}
}
}
}

View File

@@ -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
}
}
}
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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",
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"]

View File

@@ -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

View File

@@ -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:

View File

@@ -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";
}
@@ -479,7 +464,7 @@ 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, Y, exec, dms ipc call dankdash wallpaper
# Audio controls (function keys)
bindl = , XF86AudioRaiseVolume, exec, dms ipc call audio increment 3

View File

@@ -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")
}
}

View File

@@ -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)

View File

@@ -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")
}
})
}

View File

@@ -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)
}
}
}

View File

@@ -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";
}

View File

@@ -25,6 +25,7 @@ PanelWindow {
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> 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

View File

@@ -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)
}
}

View File

@@ -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 ""
```

View File

@@ -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";
};
};
})

View File

@@ -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

View File

@@ -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",

View File

@@ -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はオフ中"
},

View File

@@ -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"
},

View File

@@ -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 已关闭"
},

View File

@@ -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": "",

5884
wall.patch Normal file

File diff suppressed because it is too large Load Diff