1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00
This commit is contained in:
bbedward
2025-10-21 23:13:54 -04:00
parent 90bc890190
commit 967b7d05de
43 changed files with 2203 additions and 7271 deletions

View File

@@ -17,9 +17,91 @@ 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,6 +207,9 @@ 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,7 +441,10 @@ Singleton {
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
SessionData.setLightMode(light)
if (!isGreeterMode) {
PortalService.setLightMode(light)
// Skip with matugen becuase, our script runner will do it.
if (!matugenAvailable) {
PortalService.setLightMode(light)
}
generateSystemThemesFromCurrentTheme()
}
}
@@ -719,15 +722,16 @@ 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}' --run`
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' '${syncModeWithPortal}' --run`
]
} else {
console.log("Theme: Starting matugen worker")
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, "--run"]
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, syncModeWithPortal, "--run"]
}
systemThemeGenerator.running = true
}

View File

@@ -1,29 +1,11 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
import qs.Common
import qs.Modules.Greetd
Item {
Scope {
id: root
WlSessionLock {
id: sessionLock
locked: false
Component.onCompleted: {
Qt.callLater(() => { locked = true })
}
onLockedChanged: {
if (!locked) {
console.log("Greetd session unlocked, exiting")
}
}
GreeterSurface {
lock: sessionLock
}
}
GreeterSurface {}
}

View File

@@ -22,6 +22,7 @@ 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
@@ -63,8 +64,11 @@ 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()
@@ -193,17 +197,19 @@ Item {
}
}
LazyLoader {
id: wifiPasswordModalLoader
WifiPasswordModal {
id: wifiPasswordModal
active: false
Component.onCompleted: {
PopoutService.wifiPasswordModal = wifiPasswordModal
}
}
WifiPasswordModal {
id: wifiPasswordModal
Connections {
target: NetworkService
Component.onCompleted: {
PopoutService.wifiPasswordModal = wifiPasswordModal
}
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason) {
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason)
}
}
@@ -496,6 +502,7 @@ Item {
notepadSlideoutVariants: notepadSlideoutVariants
hyprKeybindsModalLoader: hyprKeybindsModalLoader
dankBarLoader: dankBarLoader
hyprlandOverviewLoader: hyprlandOverviewLoader
}
Variants {
@@ -538,4 +545,12 @@ Item {
modelData: item
}
}
LazyLoader {
id: hyprlandOverviewLoader
active: CompositorService.isHyprland
component: HyprlandOverview {
id: hyprlandOverview
}
}
}

View File

@@ -15,6 +15,7 @@ Item {
required property var notepadSlideoutVariants
required property var hyprKeybindsModalLoader
required property var dankBarLoader
required property var hyprlandOverviewLoader
IpcHandler {
function open() {
@@ -347,6 +348,30 @@ 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,6 +7,10 @@ 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
@@ -90,19 +94,32 @@ 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: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !listIconImg.visible
visible: !parent.isMaterial && !listIconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 1
@@ -120,7 +137,7 @@ Rectangle {
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - resultsList.iconSize - Theme.spacingL
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - resultsList.iconSize - Theme.spacingL) : parent.width
spacing: Theme.spacingXS
StyledText {
@@ -255,20 +272,33 @@ 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: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !gridIconImg.visible
visible: !parent.isMaterial && !gridIconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 1

View File

@@ -15,12 +15,23 @@ 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
@@ -37,6 +48,41 @@ 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
@@ -60,6 +106,9 @@ DankModal {
})
}
onBackgroundClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -90,6 +139,9 @@ DankModal {
anchors.fill: parent
focus: true
Keys.onEscapePressed: event => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -117,12 +169,28 @@ DankModal {
font.weight: Font.Medium
}
StyledText {
text: requiresEnterprise ? I18n.tr("Enter credentials for ") + wifiPasswordSSID : I18n.tr("Enter password for ") + wifiPasswordSSID
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
Column {
width: parent.width
elide: Text.ElideRight
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
}
}
}
@@ -131,6 +199,9 @@ DankModal {
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -208,14 +279,26 @@ DankModal {
wifiPasswordInput = text
}
onAccepted: () => {
const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
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
)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -395,6 +478,9 @@ DankModal {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""
@@ -430,14 +516,26 @@ DankModal {
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
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
)
}
close()
wifiPasswordInput = ""
wifiUsernameInput = ""

View File

@@ -404,16 +404,29 @@ 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: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
@@ -421,7 +434,7 @@ DankPopout {
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingM
visible: !listIconImg.visible
visible: !parent.isMaterial && listIconImg.status !== Image.Ready
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 0
@@ -435,11 +448,12 @@ DankPopout {
font.weight: Font.Bold
}
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - appList.iconSize - Theme.spacingL
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - appList.iconSize - Theme.spacingL) : parent.width
spacing: Theme.spacingXS
StyledText {
@@ -513,6 +527,7 @@ 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
@@ -578,6 +593,19 @@ 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
@@ -586,10 +614,10 @@ DankPopout {
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
source: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
@@ -597,7 +625,7 @@ DankPopout {
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
visible: !gridIconImg.visible
visible: !parent.isMaterial && gridIconImg.status !== Image.Ready
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 0

View File

@@ -8,6 +8,10 @@ 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"
@@ -163,7 +167,7 @@ Item {
filteredModel.append({
"name": app.name || "",
"exec": app.execString || app.exec || app.action || "",
"icon": app.icon || "application-x-executable",
"icon": app.icon !== undefined ? app.icon : (isPluginItem ? "" : "application-x-executable"),
"comment": app.comment || "",
"categories": app.categories || [],
"isPlugin": isPluginItem,

View File

@@ -509,7 +509,11 @@ Rectangle {
onClicked: function(event) {
if (modelData.ssid !== NetworkService.currentWifiSSID) {
if (modelData.secured && !modelData.saved) {
wifiPasswordModal.show(modelData.ssid)
if (DMSService.apiVersion >= 7) {
NetworkService.connectToWifi(modelData.ssid)
} else if (PopoutService.wifiPasswordModal) {
PopoutService.wifiPasswordModal.show(modelData.ssid)
}
} else {
NetworkService.connectToWifi(modelData.ssid)
}
@@ -563,7 +567,11 @@ Rectangle {
NetworkService.disconnectWifi()
} else {
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
wifiPasswordModal.show(networkContextMenu.currentSSID)
if (DMSService.apiVersion >= 7) {
NetworkService.connectToWifi(networkContextMenu.currentSSID)
} else if (PopoutService.wifiPasswordModal) {
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID)
}
} else {
NetworkService.connectToWifi(networkContextMenu.currentSSID)
}
@@ -618,10 +626,6 @@ Rectangle {
}
}
WifiPasswordModal {
id: wifiPasswordModal
}
NetworkInfoModal {
id: networkInfoModal
}

View File

@@ -23,6 +23,7 @@ Item {
signal colorPickerRequested
property alias barVariants: barVariants
property var hyprlandOverviewLoader: null
function triggerControlCenterOnFocusedScreen() {
let focusedScreenName = ""
@@ -48,30 +49,6 @@ 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")
@@ -80,7 +57,6 @@ Item {
id: barWindow
property var controlCenterButtonRef: null
property var clockButtonRef: null
function triggerControlCenter() {
controlCenterLoader.active = true
@@ -103,27 +79,6 @@ 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":
@@ -818,6 +773,7 @@ Item {
section: topBarContent.getWidgetSection(parent)
popupTarget: appDrawerLoader.item
parentScreen: barWindow.screen
hyprlandOverviewLoader: root.hyprlandOverviewLoader
onClicked: {
appDrawerLoader.active = true
appDrawerLoader.item?.toggle()
@@ -831,6 +787,7 @@ Item {
WorkspaceSwitcher {
screenName: barWindow.screenName
widgetHeight: barWindow.widgetThickness
hyprlandOverviewLoader: root.hyprlandOverviewLoader
}
}
@@ -868,17 +825,6 @@ 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) {
@@ -928,7 +874,7 @@ Item {
dankDashPopoutLoader.active = true
if (dankDashPopoutLoader.item) {
dankDashPopoutLoader.item.dashVisible = !dankDashPopoutLoader.item.dashVisible
dankDashPopoutLoader.item.currentTabIndex = 3
dankDashPopoutLoader.item.currentTabIndex = 2
}
}
}
@@ -1262,15 +1208,4 @@ 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,14 +74,20 @@ Rectangle {
return false
}
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
const activeHyprToplevel = hyprlandToplevels.find(t => t.wayland === activeWindow)
try {
if (!Hyprland.toplevels) return false
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
const activeHyprToplevel = hyprlandToplevels.find(t => t?.wayland === activeWindow)
if (!activeHyprToplevel || !activeHyprToplevel.workspace) {
if (!activeHyprToplevel || !activeHyprToplevel.workspace) {
return false
}
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id
} catch (e) {
console.error("FocusedApp: hasWindowsOnCurrentWorkspace error:", e)
return false
}
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id
}
return activeWindow && activeWindow.title

View File

@@ -17,6 +17,7 @@ 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()
@@ -35,6 +36,8 @@ 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,22 +30,28 @@ Rectangle {
if (!SettingsData.runningAppsGroupByApp) {
return [];
}
const appGroups = new Map();
sortedToplevels.forEach((toplevel, index) => {
const appId = toplevel.appId || "unknown";
if (!appGroups.has(appId)) {
appGroups.set(appId, {
appId: appId,
windows: []
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)"
});
}
appGroups.get(appId).windows.push({
toplevel: toplevel,
windowId: index,
windowTitle: toplevel.title || "(Unnamed)"
});
});
return Array.from(appGroups.values());
return Array.from(appGroups.values());
} catch (e) {
console.error("RunningApps: groupedWindows error:", e);
return [];
}
}
readonly property int windowCount: SettingsData.runningAppsGroupByApp ? groupedWindows.length : sortedToplevels.length
readonly property int calculatedSize: {

View File

@@ -15,6 +15,7 @@ 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);
}
@@ -244,11 +245,17 @@ Rectangle {
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
acceptedButtons: Qt.RightButton
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,8 +16,6 @@ DankPopout {
property var triggerScreen: null
property int currentTabIndex: 0
keyboardFocusMode: WlrKeyboardFocus.Exclusive
function setTriggerPosition(x, y, width, section, screen) {
triggerSection = section
triggerScreen = screen
@@ -45,49 +43,15 @@ 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
}
@@ -103,18 +67,7 @@ DankPopout {
Component.onCompleted: {
if (root.shouldBeVisible) {
mainContainer.forceActiveFocus()
}
}
Connections {
target: root
function onShouldBeVisibleChanged() {
if (root.shouldBeVisible) {
Qt.callLater(function() {
mainContainer.forceActiveFocus()
})
}
forceActiveFocus()
}
}
@@ -122,46 +75,18 @@ DankPopout {
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
Connections {
function onShouldBeVisibleChanged() {
if (root.shouldBeVisible) {
Qt.callLater(function() {
mainContainer.forceActiveFocus()
})
}
}
target: root
}
Rectangle {
@@ -203,23 +128,11 @@ 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: "wallpaper", text: I18n.tr("Wallpapers") }
{ icon: "music_note", text: I18n.tr("Media") }
]
if (SettingsData.weatherEnabled) {
@@ -235,7 +148,7 @@ DankPopout {
}
onActionTriggered: function(index) {
let settingsIndex = SettingsData.weatherEnabled ? 4 : 3
let settingsIndex = SettingsData.weatherEnabled ? 3 : 2
if (index === settingsIndex) {
dashVisible = false
settingsModal.show()
@@ -255,8 +168,7 @@ DankPopout {
implicitHeight: {
if (currentIndex === 0) return overviewTab.implicitHeight
if (currentIndex === 1) return mediaTab.implicitHeight
if (currentIndex === 2) return wallpaperTab.implicitHeight
if (SettingsData.weatherEnabled && currentIndex === 3) return weatherTab.implicitHeight
if (SettingsData.weatherEnabled && currentIndex === 2) return weatherTab.implicitHeight
return overviewTab.implicitHeight
}
currentIndex: root.currentTabIndex
@@ -266,8 +178,8 @@ DankPopout {
onSwitchToWeatherTab: {
if (SettingsData.weatherEnabled) {
tabBar.currentIndex = 3
tabBar.tabClicked(3)
tabBar.currentIndex = 2
tabBar.tabClicked(2)
}
}
@@ -281,16 +193,9 @@ DankPopout {
id: mediaTab
}
WallpaperTab {
id: wallpaperTab
active: root.currentTabIndex === 2
tabBarItem: tabBar
keyForwardTarget: mainContainer
}
WeatherTab {
id: weatherTab
visible: SettingsData.weatherEnabled && root.currentTabIndex === 3
visible: SettingsData.weatherEnabled && root.currentTabIndex === 2
}
}
}

View File

@@ -1,523 +0,0 @@
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,7 +7,6 @@ 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
@@ -16,8 +15,6 @@ 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: ""
@@ -117,21 +114,6 @@ 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
@@ -673,180 +655,11 @@ Item {
height: 24
color: Qt.rgba(255, 255, 255, 0.2)
anchors.verticalCenter: parent.verticalCenter
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
}
}
}
}
}
visible: {
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) ||
(CompositorService.isHyprland && hyprlandLayoutCount > 1)
return keyboardVisible && WeatherService.weather.available
}
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 {
@@ -1247,7 +1060,6 @@ Item {
}
function onReadyToLaunch() {
root.sessionLock.locked = false
GreeterState.unlocking = true
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]
if (sessionCmd) {

View File

@@ -1,18 +1,34 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
import qs.Common
WlSessionLockSurface {
id: root
Variants {
model: Quickshell.screens
required property WlSessionLock lock
PanelWindow {
id: root
color: "transparent"
property var modelData
GreeterContent {
anchors.fill: parent
screenName: root.screen?.name ?? ""
sessionLock: root.lock
screen: modelData
anchors {
left: true
right: true
top: true
bottom: true
}
exclusionMode: ExclusionMode.Normal
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
GreeterContent {
anchors.fill: parent
screenName: root.screen?.name ?? ""
}
}
}

View File

@@ -0,0 +1,284 @@
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

@@ -0,0 +1,428 @@
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

@@ -0,0 +1,140 @@
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("Clear")
text: I18n.tr("Dismiss")
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("Clear")
text: I18n.tr("Dismiss")
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 All")
text: I18n.tr("Clear")
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("Clear")
readonly property string clearText: I18n.tr("Dismiss")
signal entered
signal exitFinished
@@ -512,7 +512,7 @@ PanelWindow {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
acceptedButtons: Qt.LeftButton | Qt.RightButton
propagateComposedEvents: true
z: -1
onEntered: {
@@ -523,9 +523,20 @@ PanelWindow {
if (notificationData && notificationData.popup && notificationData.timer)
notificationData.timer.restart()
}
onClicked: {
if (notificationData && !win.exiting)
notificationData.popup = false
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
}
}
}
}
}

View File

@@ -26,35 +26,35 @@ Item {
const baseItems = [
{
name: "Test Item 1",
icon: "lightbulb",
icon: "material: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: "star",
name: "Test Item 2",
icon: "material:star",
comment: "Another test item with different action",
action: "toast:Test Item 2 clicked!",
categories: ["LauncherExample"]
},
{
name: "Test Item 3",
icon: "favorite",
icon: "material:favorite",
comment: "Third test item for demonstration",
action: "toast:Test Item 3 activated!",
categories: ["LauncherExample"]
},
{
name: "Example Copy Action",
icon: "content_copy",
icon: "material: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: "terminal",
icon: "material:terminal",
comment: "Demonstrates running a simple command",
action: "script:echo 'Hello from launcher plugin!'",
categories: ["LauncherExample"]

View File

@@ -88,13 +88,41 @@ function executeItem(item): void
```javascript
{
name: "Item Name", // Display name
icon: "icon_name", // Material icon
icon: "icon_name", // Icon (optional, see Icon Types below)
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,11 +1221,51 @@ Item {
Each item returned by `getItems()` must include:
- `name` (string): Display name shown in launcher
- `icon` (string): Material Design icon name
- `icon` (string, optional): Icon specification (see Icon Types below)
- `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,43 +267,61 @@ 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. Install the shell
#### 3.1. Clone latest QML
**3.1. Clone latest master**
```bash
mkdir ~/.config/quickshell && git clone https://github.com/AvengeMedia/DankMaterialShell.git ~/.config/quickshell/dms
```
**3.2. Install latest dms CLI**
**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
```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.
**4. Optional Features (system monitoring, clipboard history, brightness controls, etc.)**
If preferred, you can build the dms-cli yourself (requires GO 1.24+)
**4.1 Core optional dependencies**
```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
```bash
# Arch Linux
sudo pacman -S cava wl-clipboard cliphist brightnessctl qt6-multimedia
@@ -317,7 +335,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.
@@ -393,9 +411,6 @@ 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";
}
@@ -463,8 +478,8 @@ bind = SUPER, comma, exec, dms ipc call settings toggle
bind = SUPER, P, exec, dms ipc call notepad toggle
bind = SUPERALT, L, exec, dms ipc call lock lock
bind = SUPER, X, exec, dms ipc call powermenu toggle
bind = SUPER, C, exec, dms ipc call control-center toggle
bind = SUPER, Y, exec, dms ipc call dankdash wallpaper
bind = SUPER, C, exec, dms ipc call control-center toggle
bind = SUPER, TAB, exec, dms ipc call hypr toggleOverview
# Audio controls (function keys)
bindl = , XF86AudioRaiseVolume, exec, dms ipc call audio increment 3

View File

@@ -1,5 +1,4 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
@@ -17,67 +16,300 @@ 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: {
if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) {
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)
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 }
}
if (isHyprland) {
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
let snap = []
let missingAnyPosition = false
let hasNewWindow = false
for (let i = 0; i < items.length; i++) {
const t = items[i]
if (!t) continue
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 addr = t.address || ""
const li = t.lastIpcObject || null
if (a.workspace && b.workspace) {
const workspaceCompare = a.workspace.id - b.workspace.id
if (workspaceCompare !== 0) {
return workspaceCompare
}
}
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.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 wsId = _get(li, ["workspace", "id"], null) ?? _get(t, ["workspace", "id"], Number.MAX_SAFE_INTEGER)
const xCompare = aX - bX
if (Math.abs(xCompare) > 10) {
return xCompare
}
return aY - bY
}
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
if (a.lastIpcObject && !b.lastIpcObject) {
return -1
}
if (!a.lastIpcObject && b.lastIpcObject) {
return 1
}
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.title && b.title) {
return a.title.localeCompare(b.title)
}
const relX = Number.isFinite(monX) ? (atX - monX) : atX
const relY = Number.isFinite(monY) ? (atY - monY) : atY
return 0
})
return sortedHyprland.map(hyprToplevel => hyprToplevel.wayland).filter(wayland => wayland !== null)
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 ToplevelManager.toplevels.values
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)
}
Timer {
@@ -91,86 +323,30 @@ 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", root.niriSocket], (output, exitCode) => {
Proc.runCommand("niriSocketCheck", ["test", "-S", niriSocket], (output, exitCode) => {
if (exitCode === 0) {
root.isNiri = true
root.isHyprland = false
root.compositor = "niri"
console.log("CompositorService: Detected Niri with socket:", root.niriSocket)
isNiri = true
isHyprland = false
compositor = "niri"
console.log("CompositorService: Detected Niri with socket:", niriSocket)
NiriService.generateNiriBinds()
} else {
root.isHyprland = false
root.isNiri = true
root.compositor = "niri"
isHyprland = false
isNiri = true
compositor = "niri"
console.warn("CompositorService: Niri socket check failed, defaulting to Niri anyway")
}
}, 0)
@@ -183,22 +359,14 @@ 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,6 +41,7 @@ Singleton {
signal loginctlStateUpdate(var data)
signal loginctlEvent(var event)
signal capabilitiesReceived()
signal credentialsRequest(var data)
Component.onCompleted: {
if (socketPath && socketPath.length > 0) {
@@ -261,6 +262,8 @@ 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,8 +79,21 @@ 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")
@@ -120,6 +133,10 @@ Singleton {
function onCapabilitiesChanged() {
checkDMSCapabilities()
}
function onCredentialsRequest(data) {
handleCredentialsRequest(data)
}
}
function checkDMSCapabilities() {
@@ -143,6 +160,18 @@ 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) {
@@ -177,6 +206,9 @@ Singleton {
}
function updateState(state) {
const previousConnecting = isConnecting
const previousConnectingSSID = connectingSSID
networkStatus = state.networkStatus || "disconnected"
primaryConnection = state.primaryConnection || ""
@@ -225,6 +257,45 @@ 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()
}
@@ -242,11 +313,11 @@ Singleton {
connectionError = response.error
lastConnectionError = response.error
connectionStatus = "failed"
ToastService.showError(`Failed to activate configuration`)
ToastService.showError(I18n.tr("Failed to activate configuration"))
} else {
connectionError = ""
connectionStatus = "connected"
ToastService.showInfo(`Configuration activated`)
ToastService.showInfo(I18n.tr("Configuration activated"))
}
isConnecting = false
@@ -280,42 +351,47 @@ Singleton {
function connectToWifi(ssid, password = "", username = "", anonymousIdentity = "", domainSuffixMatch = "") {
if (!networkAvailable || isConnecting) return
connectingSSID = ssid
pendingConnectionSSID = ssid
pendingConnectionStartTime = Date.now()
connectionError = ""
connectionStatus = "connecting"
credentialsRequested = false
const params = { ssid: ssid }
if (password) params.password = password
if (username) params.username = username
if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity
if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch
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
}
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
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}`)
}
pendingConnectionSSID = ""
connectionStatus = "failed"
ToastService.showError(I18n.tr("Failed to start connection to ") + ssid)
} else {
connectionError = ""
connectionStatus = "connected"
ToastService.showInfo(`Connected to ${ssid}`)
if (userPreference === "wifi" || userPreference === "auto") {
setConnectionPriority("wifi")
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Connection request sent for", ssid)
}
}
isConnecting = false
connectingSSID = ""
})
}
@@ -324,15 +400,60 @@ Singleton {
DMSService.sendRequest("network.wifi.disconnect", null, response => {
if (response.error) {
ToastService.showError("Failed to disconnect WiFi")
ToastService.showError(I18n.tr("Failed to disconnect WiFi"))
} else {
ToastService.showInfo("Disconnected from WiFi")
ToastService.showInfo(I18n.tr("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
@@ -341,7 +462,7 @@ Singleton {
if (response.error) {
console.warn("Failed to forget network:", response.error)
} else {
ToastService.showInfo(`Forgot network ${ssid}`)
ToastService.showInfo(I18n.tr("Forgot network ") + ssid)
savedConnections = savedConnections.filter(s => s.ssid !== ssid)
savedWifiNetworks = savedWifiNetworks.filter(s => s.ssid !== ssid)
@@ -374,7 +495,7 @@ Singleton {
console.warn("Failed to toggle WiFi:", response.error)
} else if (response.result) {
wifiEnabled = response.result.enabled
ToastService.showInfo(wifiEnabled ? "WiFi enabled" : "WiFi disabled")
ToastService.showInfo(wifiEnabled ? I18n.tr("WiFi enabled") : I18n.tr("WiFi disabled"))
}
})
}
@@ -384,9 +505,9 @@ Singleton {
DMSService.sendRequest("network.wifi.enable", null, response => {
if (response.error) {
ToastService.showError("Failed to enable WiFi")
ToastService.showError(I18n.tr("Failed to enable WiFi"))
} else {
ToastService.showInfo("WiFi enabled")
ToastService.showInfo(I18n.tr("WiFi enabled"))
}
})
}

View File

@@ -69,8 +69,17 @@ 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
@@ -122,6 +131,9 @@ Singleton {
if (activeService.connectionChanged) {
activeService.connectionChanged.connect(root.connectionChanged)
}
if (activeService.credentialsNeeded) {
activeService.credentialsNeeded.connect(root.credentialsNeeded)
}
}
}
@@ -258,4 +270,16 @@ 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,10 +19,6 @@ 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,7 +25,6 @@ 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
@@ -64,7 +63,7 @@ PanelWindow {
color: "transparent"
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: shouldBeVisible ? keyboardFocusMode : WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
anchors {
top: true

View File

@@ -2,7 +2,7 @@ import QtQuick
import qs.Common
import qs.Widgets
FocusScope {
Item {
id: tabBar
property alias model: tabRepeater.model
@@ -11,101 +11,12 @@ FocusScope {
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
@@ -166,6 +77,7 @@ FocusScope {
if (tabItem.isAction) {
tabBar.actionTriggered(index)
} else {
tabBar.currentIndex = index
tabBar.tabClicked(index)
}
}

View File

@@ -501,13 +501,6 @@ 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.
@@ -519,7 +512,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 keybinds cheatsheet modal control (Hyprland only).
Hyprland-specific controls including keybinds cheatsheet and workspace overview (Hyprland only).
**Functions:**
- `openBinds` - Show Hyprland keybinds cheatsheet modal
@@ -531,13 +524,31 @@ Hyprland keybinds cheatsheet modal control (Hyprland only).
- `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"
**Description:**
**Keybinds Cheatsheet 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
@@ -570,9 +581,6 @@ 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
@@ -580,6 +588,11 @@ 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
@@ -607,6 +620,7 @@ 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,6 +59,11 @@ 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" = {
@@ -81,13 +86,6 @@ 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,14 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
if [ $# -lt 4 ]; then
echo "Usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR --run" >&2
if [ $# -lt 5 ]; then
echo "Usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL --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
@@ -25,10 +26,10 @@ if [ ! -d "$CONFIG_DIR" ]; then
exit 1
fi
shift 3 # Remove STATE_DIR, SHELL_DIR, and CONFIG_DIR from arguments
shift 4
if [[ "${1:-}" != "--run" ]]; then
echo "usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR --run" >&2
echo "usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL --run" >&2
exit 1
fi
@@ -62,6 +63,27 @@ 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
@@ -237,7 +259,7 @@ EOF
rm -f "$TMP_CONTENT_CFG"
popd >/dev/null
echo "$JSON" | grep -q '"primary"' || { echo "matugen JSON missing primary" >&2; return 2; }
echo "$JSON" | grep -q '"primary"' || { echo "matugen JSON missing primary" >&2; set_system_color_scheme "$mode"; return 2; }
printf "%s" "$JSON" > "$LAST_JSON"
GTK_CSS="$CONFIG_DIR/gtk-3.0/gtk.css"
@@ -289,6 +311,8 @@ 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": "Modals/Settings/SettingsSidebar.qml:44, Modules/Settings/AboutTab.qml:251",
"reference": "Modules/Settings/AboutTab.qml:251, Modals/Settings/SettingsSidebar.qml:44",
"comment": ""
},
{
@@ -116,7 +116,7 @@
{
"term": "All",
"context": "All",
"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",
"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",
"comment": ""
},
{
@@ -128,7 +128,7 @@
{
"term": "All displays",
"context": "All displays",
"reference": "Modules/Settings/DisplaysTab.qml:666",
"reference": "Modules/Settings/DisplaysTab.qml:670",
"comment": ""
},
{
@@ -158,7 +158,7 @@
{
"term": "Anonymous Identity (optional)",
"context": "Anonymous Identity (optional)",
"reference": "Modals/WifiPasswordModal.qml:282",
"reference": "Modals/WifiPasswordModal.qml:365",
"comment": ""
},
{
@@ -299,12 +299,6 @@
"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...",
@@ -314,7 +308,7 @@
{
"term": "Automatic Control",
"context": "Automatic Control",
"reference": "Modules/Settings/DisplaysTab.qml:162",
"reference": "Modules/Settings/DisplaysTab.qml:164",
"comment": ""
},
{
@@ -335,6 +329,12 @@
"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:509",
"reference": "Modules/Settings/DisplaysTab.qml:513",
"comment": ""
},
{
@@ -524,7 +524,7 @@
{
"term": "Cancel",
"context": "Cancel",
"reference": "Modals/DankColorPickerModal.qml:510, Modals/WifiPasswordModal.qml:385, Modals/FileBrowser/FileBrowserModal.qml:952, Modules/Settings/PluginsTab.qml:1220",
"reference": "Modals/DankColorPickerModal.qml:510, Modals/WifiPasswordModal.qml:468, Modules/Settings/PluginsTab.qml:1220, Modals/FileBrowser/FileBrowserModal.qml:952",
"comment": ""
},
{
@@ -578,13 +578,13 @@
{
"term": "Clear",
"context": "Clear",
"reference": "Modules/Notifications/Popup/NotificationPopup.qml:24, Modules/Notifications/Center/NotificationCard.qml:540, Modules/Notifications/Center/NotificationCard.qml:633",
"reference": "Modules/Notifications/Center/NotificationHeader.qml:102",
"comment": ""
},
{
"term": "Clear All",
"context": "Clear All",
"reference": "Modals/Clipboard/ClipboardHistoryModal.qml:157, Modules/Notifications/Center/NotificationHeader.qml:102",
"reference": "Modals/Clipboard/ClipboardHistoryModal.qml:157",
"comment": ""
},
{
@@ -620,7 +620,7 @@
{
"term": "Close",
"context": "Close",
"reference": "Modules/SystemUpdatePopout.qml:333, Modals/NetworkWiredInfoModal.qml:129, Modals/NetworkInfoModal.qml:129, Modules/DankBar/Widgets/RunningApps.qml:711",
"reference": "Modals/NetworkInfoModal.qml:129, Modals/NetworkWiredInfoModal.qml:129, Modules/SystemUpdatePopout.qml:333, Modules/DankBar/Widgets/RunningApps.qml:717",
"comment": ""
},
{
@@ -638,7 +638,7 @@
{
"term": "Color temperature for night mode",
"context": "Color temperature for night mode",
"reference": "Modules/Settings/DisplaysTab.qml:143",
"reference": "Modules/Settings/DisplaysTab.qml:145",
"comment": ""
},
{
@@ -701,6 +701,12 @@
"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.",
@@ -710,25 +716,31 @@
{
"term": "Configure which displays show shell components",
"context": "Configure which displays show shell components",
"reference": "Modules/Settings/DisplaysTab.qml:493",
"reference": "Modules/Settings/DisplaysTab.qml:497",
"comment": ""
},
{
"term": "Connect",
"context": "Connect",
"reference": "Modals/WifiPasswordModal.qml:419",
"reference": "Modals/WifiPasswordModal.qml:505",
"comment": ""
},
{
"term": "Connect to Wi-Fi",
"context": "Connect to Wi-Fi",
"reference": "Modals/WifiPasswordModal.qml:114",
"reference": "Modals/WifiPasswordModal.qml:166",
"comment": ""
},
{
"term": "Connected Displays",
"context": "Connected Displays",
"reference": "Modules/Settings/DisplaysTab.qml:486",
"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",
"comment": ""
},
{
@@ -872,7 +884,7 @@
{
"term": "DMS out of date",
"context": "DMS out of date",
"reference": "Services/DMSService.qml:234",
"reference": "Services/DMSService.qml:235",
"comment": ""
},
{
@@ -971,6 +983,12 @@
"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",
@@ -983,6 +1001,12 @@
"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",
@@ -1034,7 +1058,7 @@
{
"term": "Do Not Disturb",
"context": "Do Not Disturb",
"reference": "Modules/Notifications/Center/NotificationHeader.qml:41, Modules/Notifications/Center/NotificationSettings.qml:131",
"reference": "Modules/Notifications/Center/NotificationSettings.qml:131, Modules/Notifications/Center/NotificationHeader.qml:41",
"comment": ""
},
{
@@ -1058,7 +1082,7 @@
{
"term": "Domain (optional)",
"context": "Domain (optional)",
"reference": "Modals/WifiPasswordModal.qml:314",
"reference": "Modals/WifiPasswordModal.qml:397",
"comment": ""
},
{
@@ -1136,13 +1160,13 @@
{
"term": "End",
"context": "End",
"reference": "Modules/Settings/DisplaysTab.qml:315",
"reference": "Modules/Settings/DisplaysTab.qml:318",
"comment": ""
},
{
"term": "Enter credentials for ",
"context": "Enter credentials for ",
"reference": "Modals/WifiPasswordModal.qml:121",
"reference": "Modals/WifiPasswordModal.qml:178",
"comment": ""
},
{
@@ -1166,7 +1190,7 @@
{
"term": "Enter password for ",
"context": "Enter password for ",
"reference": "Modals/WifiPasswordModal.qml:121",
"reference": "Modals/WifiPasswordModal.qml:178",
"comment": ""
},
{
@@ -1187,6 +1211,30 @@
"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",
@@ -1199,6 +1247,12 @@
"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",
@@ -1238,7 +1292,7 @@
{
"term": "Font Family",
"context": "Font Family",
"reference": "Modules/Notepad/NotepadSettings.qml:220, Modules/Settings/ThemeColorsTab.qml:944",
"reference": "Modules/Settings/ThemeColorsTab.qml:944, Modules/Notepad/NotepadSettings.qml:220",
"comment": ""
},
{
@@ -1280,7 +1334,13 @@
{
"term": "Forget Network",
"context": "Forget Network",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:598",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:606",
"comment": ""
},
{
"term": "Forgot network ",
"context": "Forgot network ",
"reference": "Services/NetworkManagerService.qml:465",
"comment": ""
},
{
@@ -1332,9 +1392,9 @@
"comment": ""
},
{
"term": "Geoclue service not running - cannot auto-detect location",
"context": "Geoclue service not running - cannot auto-detect location",
"reference": "Modules/Settings/DisplaysTab.qml:365",
"term": "Gamma control not available. Requires DMS API v6+.",
"context": "Gamma control not available. Requires DMS API v6+.",
"reference": "Modules/Settings/DisplaysTab.qml:119",
"comment": ""
},
{
@@ -1424,7 +1484,7 @@
{
"term": "Hour",
"context": "Hour",
"reference": "Modules/Settings/DisplaysTab.qml:252",
"reference": "Modules/Settings/DisplaysTab.qml:255",
"comment": ""
},
{
@@ -1487,6 +1547,12 @@
"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",
@@ -1574,13 +1640,13 @@
{
"term": "Latitude",
"context": "Latitude",
"reference": "Modules/Settings/DisplaysTab.qml:398, Modules/Settings/TimeWeatherTab.qml:665",
"reference": "Modules/Settings/DisplaysTab.qml:402, Modules/Settings/TimeWeatherTab.qml:665",
"comment": ""
},
{
"term": "Launch",
"context": "Launch",
"reference": "Modals/Spotlight/SpotlightContextMenu.qml:251, Modules/AppDrawer/AppDrawerPopout.qml:895",
"reference": "Modules/AppDrawer/AppDrawerPopout.qml:923, Modals/Spotlight/SpotlightContextMenu.qml:251",
"comment": ""
},
{
@@ -1592,7 +1658,7 @@
{
"term": "Launch on dGPU",
"context": "Launch on dGPU",
"reference": "Modals/Spotlight/SpotlightContextMenu.qml:312, Modules/AppDrawer/AppDrawerPopout.qml:955, Modules/Dock/DockContextMenu.qml:417",
"reference": "Modules/AppDrawer/AppDrawerPopout.qml:983, Modules/Dock/DockContextMenu.qml:417, Modals/Spotlight/SpotlightContextMenu.qml:312",
"comment": ""
},
{
@@ -1682,7 +1748,7 @@
{
"term": "Longitude",
"context": "Longitude",
"reference": "Modules/Settings/DisplaysTab.qml:421, Modules/Settings/TimeWeatherTab.qml:716",
"reference": "Modules/Settings/DisplaysTab.qml:425, Modules/Settings/TimeWeatherTab.qml:716",
"comment": ""
},
{
@@ -1700,7 +1766,7 @@
{
"term": "Manual Coordinates",
"context": "Manual Coordinates",
"reference": "Modules/Settings/DisplaysTab.qml:386",
"reference": "Modules/Settings/DisplaysTab.qml:390",
"comment": ""
},
{
@@ -1724,7 +1790,7 @@
{
"term": "Matugen Palette",
"context": "Matugen Palette",
"reference": "Modules/Settings/ThemeColorsTab.qml:629, Modules/Settings/PersonalizationTab.qml:1276",
"reference": "Modules/Settings/PersonalizationTab.qml:1276, Modules/Settings/ThemeColorsTab.qml:629",
"comment": ""
},
{
@@ -1790,7 +1856,7 @@
{
"term": "Minute",
"context": "Minute",
"reference": "Modules/Settings/DisplaysTab.qml:260",
"reference": "Modules/Settings/DisplaysTab.qml:263",
"comment": ""
},
{
@@ -1862,13 +1928,13 @@
{
"term": "Network Info",
"context": "Network Info",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:340, Modules/ControlCenter/Details/NetworkDetail.qml:575",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:340, Modules/ControlCenter/Details/NetworkDetail.qml:583",
"comment": ""
},
{
"term": "Network Information",
"context": "Network Information",
"reference": "Modals/NetworkWiredInfoModal.qml:59, Modals/NetworkInfoModal.qml:59",
"reference": "Modals/NetworkInfoModal.qml:59, Modals/NetworkWiredInfoModal.qml:59",
"comment": ""
},
{
@@ -1988,7 +2054,7 @@
{
"term": "Notepad",
"context": "Notepad",
"reference": "DMSShell.qml:407, Modules/Settings/DankBarTab.qml:175",
"reference": "DMSShell.qml:413, Modules/Settings/DankBarTab.qml:175",
"comment": ""
},
{
@@ -2024,7 +2090,7 @@
{
"term": "Notification Popups",
"context": "Notification Popups",
"reference": "Modules/Settings/DisplaysTab.qml:24, Modules/Settings/WidgetTweaksTab.qml:585",
"reference": "Modules/Settings/WidgetTweaksTab.qml:585, Modules/Settings/DisplaysTab.qml:24",
"comment": ""
},
{
@@ -2078,7 +2144,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:163",
"reference": "Modules/Settings/DisplaysTab.qml:165",
"comment": ""
},
{
@@ -2126,7 +2192,7 @@
{
"term": "Password",
"context": "Password",
"reference": "Modals/WifiPasswordModal.qml:203",
"reference": "Modals/WifiPasswordModal.qml:274",
"comment": ""
},
{
@@ -2168,7 +2234,7 @@
{
"term": "Pin to Dock",
"context": "Pin to Dock",
"reference": "Modals/Spotlight/SpotlightContextMenu.qml:110, Modals/Spotlight/SpotlightContextMenu.qml:113, Modules/AppDrawer/AppDrawerPopout.qml:758, Modules/Dock/DockContextMenu.qml:370",
"reference": "Modules/AppDrawer/AppDrawerPopout.qml:786, Modules/Dock/DockContextMenu.qml:370, Modals/Spotlight/SpotlightContextMenu.qml:110, Modals/Spotlight/SpotlightContextMenu.qml:113",
"comment": ""
},
{
@@ -2444,7 +2510,7 @@
{
"term": "Save",
"context": "Save",
"reference": "Modals/FileBrowser/FileBrowserModal.qml:818, Modules/Notepad/NotepadTextEditor.qml:511, Modules/Notepad/Notepad.qml:480",
"reference": "Modules/Notepad/Notepad.qml:480, Modules/Notepad/NotepadTextEditor.qml:511, Modals/FileBrowser/FileBrowserModal.qml:818",
"comment": ""
},
{
@@ -2570,7 +2636,7 @@
{
"term": "Select the palette algorithm used for wallpaper-based colors",
"context": "Select the palette algorithm used for wallpaper-based colors",
"reference": "Modules/Settings/ThemeColorsTab.qml:630, Modules/Settings/PersonalizationTab.qml:1277",
"reference": "Modules/Settings/PersonalizationTab.qml:1277, Modules/Settings/ThemeColorsTab.qml:630",
"comment": ""
},
{
@@ -2606,7 +2672,7 @@
{
"term": "Settings",
"context": "Settings",
"reference": "Services/AppSearchService.qml:176, Modals/Settings/SettingsModal.qml:165, Modules/DankDash/DankDashPopout.qml:142",
"reference": "Services/AppSearchService.qml:176, Modules/DankDash/DankDashPopout.qml:142, Modals/Settings/SettingsModal.qml:165",
"comment": ""
},
{
@@ -2654,13 +2720,13 @@
{
"term": "Show on all connected displays",
"context": "Show on all connected displays",
"reference": "Modules/Settings/DisplaysTab.qml:667",
"reference": "Modules/Settings/DisplaysTab.qml:671",
"comment": ""
},
{
"term": "Show on screens:",
"context": "Show on screens:",
"reference": "Modules/Settings/DisplaysTab.qml:651",
"reference": "Modules/Settings/DisplaysTab.qml:655",
"comment": ""
},
{
@@ -2678,7 +2744,7 @@
{
"term": "Show password",
"context": "Show password",
"reference": "Modals/WifiPasswordModal.qml:357",
"reference": "Modals/WifiPasswordModal.qml:440",
"comment": ""
},
{
@@ -2768,7 +2834,7 @@
{
"term": "Start",
"context": "Start",
"reference": "Modules/Settings/DisplaysTab.qml:272",
"reference": "Modules/Settings/DisplaysTab.qml:275",
"comment": ""
},
{
@@ -2816,7 +2882,7 @@
{
"term": "Switch User",
"context": "Switch User",
"reference": "Modules/Greetd/GreeterContent.qml:567",
"reference": "Modules/Greetd/GreeterContent.qml:549",
"comment": ""
},
{
@@ -2930,7 +2996,7 @@
{
"term": "Temperature",
"context": "Temperature",
"reference": "Modules/Settings/DisplaysTab.qml:142",
"reference": "Modules/Settings/DisplaysTab.qml:144",
"comment": ""
},
{
@@ -3008,7 +3074,7 @@
{
"term": "To update, run the following command:",
"context": "To update, run the following command:",
"reference": "Services/DMSService.qml:235",
"reference": "Services/DMSService.qml:236",
"comment": ""
},
{
@@ -3068,7 +3134,7 @@
{
"term": "Unpin from Dock",
"context": "Unpin from Dock",
"reference": "Modals/Spotlight/SpotlightContextMenu.qml:113, Modules/AppDrawer/AppDrawerPopout.qml:758, Modules/Dock/DockContextMenu.qml:370",
"reference": "Modules/AppDrawer/AppDrawerPopout.qml:786, Modules/Dock/DockContextMenu.qml:370, Modals/Spotlight/SpotlightContextMenu.qml:113",
"comment": ""
},
{
@@ -3125,6 +3191,12 @@
"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",
@@ -3143,12 +3215,6 @@
"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",
@@ -3188,13 +3254,13 @@
{
"term": "Username",
"context": "Username",
"reference": "Modals/WifiPasswordModal.qml:166",
"reference": "Modals/WifiPasswordModal.qml:237",
"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:442",
"reference": "Modules/Settings/DisplaysTab.qml:446",
"comment": ""
},
{
@@ -3287,6 +3353,18 @@
"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

@@ -349,13 +349,6 @@
"reference": "",
"comment": ""
},
{
"term": "Auto-location",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Auto-saving...",
"translation": "",
@@ -391,6 +384,13 @@
"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,6 +818,13 @@
"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": "",
@@ -853,6 +860,13 @@
"reference": "",
"comment": ""
},
{
"term": "Connection failed. Check password and try again.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Contrast",
"translation": "",
@@ -1133,6 +1147,13 @@
"reference": "",
"comment": ""
},
{
"term": "Disconnected from WiFi",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Disk",
"translation": "",
@@ -1147,6 +1168,13 @@
"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": "",
@@ -1385,6 +1413,34 @@
"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": "",
@@ -1399,6 +1455,13 @@
"reference": "",
"comment": ""
},
{
"term": "Failed to start connection to ",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Feels Like",
"translation": "",
@@ -1497,6 +1560,13 @@
"reference": "",
"comment": ""
},
{
"term": "Forgot network ",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Format Legend",
"translation": "",
@@ -1554,7 +1624,7 @@
"comment": ""
},
{
"term": "Geoclue service not running - cannot auto-detect location",
"term": "Gamma control not available. Requires DMS API v6+.",
"translation": "",
"context": "",
"reference": "",
@@ -1735,6 +1805,13 @@
"reference": "",
"comment": ""
},
{
"term": "Incorrect password",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Individual Batteries",
"translation": "",
@@ -3646,6 +3723,13 @@
"reference": "",
"comment": ""
},
{
"term": "Use IP Location",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Use Monospace Font",
"translation": "",
@@ -3667,13 +3751,6 @@
"reference": "",
"comment": ""
},
{
"term": "Use automatic location detection (geoclue2)",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Use custom command for update your system",
"translation": "",
@@ -3835,6 +3912,20 @@
"reference": "",
"comment": ""
},
{
"term": "WiFi disabled",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "WiFi enabled",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "WiFi is off",
"translation": "",

5884
wall.patch

File diff suppressed because it is too large Load Diff