mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
Revert
This commit is contained in:
82
.github/workflows/poeditor-export.yml
vendored
82
.github/workflows/poeditor-export.yml
vendored
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
|
||||
31
DMSShell.qml
31
DMSShell.qml
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
284
Modules/HyprWorkspaces/HyprlandOverview.qml
Normal file
284
Modules/HyprWorkspaces/HyprlandOverview.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
428
Modules/HyprWorkspaces/OverviewWidget.qml
Normal file
428
Modules/HyprWorkspaces/OverviewWidget.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Modules/HyprWorkspaces/OverviewWindow.qml
Normal file
140
Modules/HyprWorkspaces/OverviewWindow.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
49
README.md
49
README.md
@@ -258,7 +258,7 @@ There are a lot of possible configurations that you can enable/disable in the fl
|
||||
|
||||
#### Other Distributions - via manual installation
|
||||
|
||||
**1. Install Quickshell (Varies by Distribution)**
|
||||
#### 1. Install Quickshell (Varies by Distribution)
|
||||
```bash
|
||||
# Arch
|
||||
paru -S quickshell-git
|
||||
@@ -267,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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
38
docs/IPC.md
38
docs/IPC.md
@@ -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 ""
|
||||
```
|
||||
|
||||
12
nix/niri.nix
12
nix/niri.nix
@@ -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";
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
5884
wall.patch
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user