mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3ada5b2bb | ||
|
|
3e167a2c52 | ||
|
|
38b3ad2b31 | ||
|
|
56e5cd13b7 | ||
|
|
d63c0fc6f0 | ||
|
|
6814b140fc | ||
|
|
5f7e478118 | ||
|
|
7317024da5 | ||
|
|
9b9fbabc3f | ||
|
|
3c5a23799f | ||
|
|
3bfdc6163c | ||
|
|
cf2f74a38d | ||
|
|
2a7f52c67e | ||
|
|
50fde1e308 | ||
|
|
46fd0ae413 | ||
|
|
65e32dc429 | ||
|
|
413675dfc1 | ||
|
|
a17343f40e | ||
|
|
5d023804c1 | ||
|
|
fa07a846b9 | ||
|
|
5df46b605e | ||
|
|
77cf371a21 | ||
|
|
89802dd040 | ||
|
|
59b95e9dd6 | ||
|
|
b836db5252 | ||
|
|
4dc4b15925 | ||
|
|
5c3062e699 | ||
|
|
3a7777c643 | ||
|
|
71543c35d6 | ||
|
|
f4cf66dc01 | ||
|
|
7870dff0fd | ||
|
|
9fc9c1ed19 | ||
|
|
4d0151350f | ||
|
|
dff10c8d13 | ||
|
|
362bcb9294 | ||
|
|
351b4f8a94 | ||
|
|
90955eb0a1 | ||
|
|
62669747ad | ||
|
|
466d00c666 | ||
|
|
63845ff875 | ||
|
|
d013748a51 | ||
|
|
474af3bc07 | ||
|
|
8e09e155fa | ||
|
|
7f9f4f96b9 | ||
|
|
cd488a8623 | ||
|
|
080b7a28b1 | ||
|
|
6949ed0ebd | ||
|
|
8465fa45bb | ||
|
|
40835ffc89 | ||
|
|
01a42ff330 | ||
|
|
ba49654a64 | ||
|
|
bc6577fe18 | ||
|
|
4ca3f0da67 | ||
|
|
7f2086488b | ||
|
|
3014fd8095 | ||
|
|
27885c8ac3 | ||
|
|
d6be0509ac | ||
|
|
1c85f5e857 | ||
|
|
abe5515aca | ||
|
|
6fba975490 | ||
|
|
2de6798f45 | ||
|
|
04fdfa2a35 | ||
|
|
8f3085290d | ||
|
|
0839fe45f5 | ||
|
|
18f4795fda | ||
|
|
55d9fa622a | ||
|
|
7dc723c764 | ||
|
|
5a63205972 | ||
|
|
a4ceeafb1e | ||
|
|
242e05cc0e | ||
|
|
065dddbe6e | ||
|
|
fa6825252b | ||
|
|
b06e48a444 | ||
|
|
97dbd40f07 | ||
|
|
bc23109f99 | ||
|
|
ecb9675e9c | ||
|
|
e1f9b9e7a4 | ||
|
|
067b485bb3 | ||
|
|
67a4e3074e | ||
|
|
010bc4e8c3 | ||
|
|
9de5e3253e | ||
|
|
e32622ac48 | ||
|
|
5e2371c2cb | ||
|
|
a6ce26ee87 | ||
|
|
2a72c126f1 | ||
|
|
36e1a5d379 |
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -26,6 +26,14 @@ assignees: ""
|
||||
- [ ] Hyprland
|
||||
- [ ] Other (specify)
|
||||
|
||||
## Distribution
|
||||
|
||||
<!-- Arch, Fedora, Debian, etc. -->
|
||||
|
||||
## dms version
|
||||
|
||||
<!-- Output of dms version command -->
|
||||
|
||||
## Description
|
||||
|
||||
<!-- Brief description of the issue -->
|
||||
@@ -45,6 +53,14 @@ assignees: ""
|
||||
## Error Messages/Logs
|
||||
|
||||
<!-- Please include any error messages, stack traces, or relevant logs -->
|
||||
<!-- you can get a log file with the following steps:
|
||||
dms kill
|
||||
mkdir ~/dms_logs
|
||||
nohup dms run > ~/dms_logs/dms-$(date +%s).txt 2>&1 &
|
||||
|
||||
Then trigger your issue, and share the contents of ~/dms_logs/dms-<timestamp>.txt
|
||||
|
||||
-->
|
||||
|
||||
```
|
||||
Paste error messages or logs here
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/support_request.md
vendored
8
.github/ISSUE_TEMPLATE/support_request.md
vendored
@@ -12,6 +12,14 @@ assignees: ""
|
||||
- [ ] Hyprland
|
||||
- [ ] other
|
||||
|
||||
## Distribution
|
||||
|
||||
<!-- Arch, Fedora, Debian, etc. -->
|
||||
|
||||
## dms version
|
||||
|
||||
<!-- Output of dms version command -->
|
||||
|
||||
## Description
|
||||
|
||||
<!-- Brief description of the support needed -->
|
||||
|
||||
130
.github/workflows/release.yml
vendored
130
.github/workflows/release.yml
vendored
@@ -57,7 +57,28 @@ jobs:
|
||||
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}")
|
||||
fi
|
||||
|
||||
cat > CHANGELOG.md << EOF
|
||||
cat > RELEASE_BODY.md << 'EOF'
|
||||
## Assets
|
||||
|
||||
### Complete Packages
|
||||
- **`dms-full-amd64.tar.gz`** - Complete package for x86_64 systems (CLI binary + QML source + installation guide)
|
||||
- **`dms-full-arm64.tar.gz`** - Complete package for ARM64 systems (CLI binary + QML source + installation guide)
|
||||
|
||||
### Individual Components
|
||||
- **`dms-cli-amd64.gz`** - DMS CLI binary for x86_64 systems
|
||||
- **`dms-cli-arm64.gz`** - DMS CLI binary for ARM64 systems
|
||||
- **`dms-qml.tar.gz`** - QML source code only
|
||||
|
||||
### Checksums
|
||||
- **`*.sha256`** - SHA256 checksums for verifying download integrity
|
||||
|
||||
**Installation:** Extract the `dms-full-*.tar.gz` package for your architecture and follow the `INSTALL.md` instructions inside.
|
||||
|
||||
---
|
||||
|
||||
EOF
|
||||
|
||||
cat >> RELEASE_BODY.md << EOF
|
||||
## What's Changed
|
||||
|
||||
$CHANGELOG
|
||||
@@ -66,7 +87,7 @@ jobs:
|
||||
EOF
|
||||
|
||||
echo "changelog<<EOF" >> $GITHUB_OUTPUT
|
||||
cat CHANGELOG.md >> $GITHUB_OUTPUT
|
||||
cat RELEASE_BODY.md >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create/Update DankMaterialShell Release
|
||||
@@ -80,18 +101,113 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Download DMS release assets
|
||||
- name: Download and prepare release assets
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
# gh is preinstalled on ubuntu-24.04; auth via GH_TOKEN env
|
||||
|
||||
mkdir -p _release_assets
|
||||
|
||||
# Download DMS CLI binaries from the danklinux repo
|
||||
gh release download "${TAG}" -R "${DMS_REPO}" --dir ./_dms_assets
|
||||
|
||||
- name: Attach DMS assets to DankMaterialShell release
|
||||
# Rename CLI binaries to dms-cli-* format
|
||||
for file in _dms_assets/dms-*.gz*; do
|
||||
if [ -f "$file" ]; then
|
||||
basename=$(basename "$file")
|
||||
# dms-amd64.gz -> dms-cli-amd64.gz
|
||||
# dms-amd64.gz.sha256 -> dms-cli-amd64.gz.sha256
|
||||
newname=$(echo "$basename" | sed 's/^dms-/dms-cli-/')
|
||||
cp "$file" "_release_assets/$newname"
|
||||
fi
|
||||
done
|
||||
|
||||
# Create QML source package (exclude .git, .github, build artifacts)
|
||||
tar --exclude='.git' \
|
||||
--exclude='.github' \
|
||||
--exclude='_dms_assets' \
|
||||
--exclude='_release_assets' \
|
||||
--exclude='*.tar.gz' \
|
||||
-czf _release_assets/dms-qml.tar.gz .
|
||||
|
||||
# Generate checksum for QML package
|
||||
(cd _release_assets && sha256sum dms-qml.tar.gz > dms-qml.tar.gz.sha256)
|
||||
|
||||
# Create full packages for each architecture
|
||||
for arch in amd64 arm64; do
|
||||
mkdir -p _temp_full/dms
|
||||
mkdir -p _temp_full/bin
|
||||
|
||||
# Extract QML source to temp directory
|
||||
tar -xzf _release_assets/dms-qml.tar.gz -C _temp_full/dms
|
||||
|
||||
# Copy CLI binary if it exists
|
||||
if [ -f "_dms_assets/dms-${arch}.gz" ]; then
|
||||
gunzip -c "_dms_assets/dms-${arch}.gz" > _temp_full/bin/dms
|
||||
chmod +x _temp_full/bin/dms
|
||||
fi
|
||||
|
||||
# Create INSTALL.md
|
||||
cat > _temp_full/INSTALL.md << 'EOF'
|
||||
# DankMaterialShell Installation
|
||||
|
||||
## Requirements
|
||||
|
||||
- Wayland compositor (niri or Hyprland recommended)
|
||||
- Quickshell framework
|
||||
- Qt6
|
||||
|
||||
## Installation Steps
|
||||
|
||||
1. **Install quickshell assets:**
|
||||
```bash
|
||||
mkdir -p ~/.config/quickshell
|
||||
cp -r dms ~/.config/quickshell/
|
||||
```
|
||||
|
||||
2. **Install the DMS CLI binary:**
|
||||
```bash
|
||||
sudo install -m 755 bin/dms /usr/local/bin/dms
|
||||
# or install to a local directory:
|
||||
mkdir -p ~/.local/bin
|
||||
install -m 755 bin/dms ~/.local/bin/dms
|
||||
```
|
||||
|
||||
3. **Start the shell:**
|
||||
```bash
|
||||
dms run
|
||||
# or directly with quickshell (will lack some dbus integrations & plugin management):
|
||||
quickshell -p ~/.config/quickshell/dms
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
- Settings are stored in `~/.config/DankMaterialShell/settings.json`
|
||||
- Plugins go in `~/.config/DankMaterialShell/plugins/`
|
||||
- See the documentation in the `dms/` directory for more details
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Run with verbose output: `quickshell -v -p ~/.config/quickshell/dms`
|
||||
- Check logs in `~/.local/state/DankMaterialShell/`
|
||||
- Ensure all dependencies are installed
|
||||
EOF
|
||||
|
||||
# Create the full package
|
||||
(cd _temp_full && tar -czf "../_release_assets/dms-full-${arch}.tar.gz" .)
|
||||
|
||||
# Generate checksum
|
||||
(cd _release_assets && sha256sum "dms-full-${arch}.tar.gz" > "dms-full-${arch}.tar.gz.sha256")
|
||||
|
||||
# Cleanup
|
||||
rm -rf _temp_full
|
||||
done
|
||||
|
||||
- name: Attach all assets to release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ env.TAG }}
|
||||
files: _dms_assets/**
|
||||
files: _release_assets/**
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
109
Common/I18n.qml
109
Common/I18n.qml
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import Qt.labs.folderlistmodel
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
@@ -7,60 +8,106 @@ pragma ComponentBehavior: Bound
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string currentLocale: Qt.locale().name.substring(0, 2)
|
||||
property var translations: ({})
|
||||
property bool translationsLoaded: false
|
||||
readonly property string _rawLocale: Qt.locale().name
|
||||
readonly property string _lang: _rawLocale.split(/[_-]/)[0]
|
||||
readonly property var _candidates: {
|
||||
const fullUnderscore = _rawLocale;
|
||||
const fullHyphen = _rawLocale.replace("_", "-");
|
||||
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
|
||||
}
|
||||
|
||||
|
||||
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
|
||||
|
||||
property string currentLocale: "en"
|
||||
property var translations: ({})
|
||||
property bool translationsLoaded: false
|
||||
|
||||
property url _selectedPath: ""
|
||||
|
||||
FolderListModel {
|
||||
id: dir
|
||||
folder: root.translationsFolder
|
||||
nameFilters: ["*.json"]
|
||||
showDirs: false
|
||||
showDotAndDotDot: false
|
||||
|
||||
onStatusChanged: if (status === FolderListModel.Ready) root._pickTranslation()
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: translationLoader
|
||||
path: root.currentLocale === "en" ? "" : Qt.resolvedUrl(`../translations/${root.currentLocale}.json`)
|
||||
path: root._selectedPath
|
||||
|
||||
onLoaded: {
|
||||
try {
|
||||
root.translations = JSON.parse(text())
|
||||
root.translationsLoaded = true
|
||||
console.log(`I18n: Loaded translations for locale '${root.currentLocale}' (${Object.keys(root.translations).length} contexts)`)
|
||||
console.log(`I18n: Loaded translations for '${root.currentLocale}' ` +
|
||||
`(${Object.keys(root.translations).length} contexts)`)
|
||||
} catch (e) {
|
||||
console.warn(`I18n: Error parsing translations for locale '${root.currentLocale}':`, e, "- falling back to English")
|
||||
root.translationsLoaded = false
|
||||
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e,
|
||||
"- falling back to English")
|
||||
root._fallbackToEnglish()
|
||||
}
|
||||
}
|
||||
|
||||
onLoadFailed: (error) => {
|
||||
console.warn(`I18n: Failed to load translations for locale '${root.currentLocale}' (${error}), falling back to English`)
|
||||
root.translationsLoaded = false
|
||||
console.warn(`I18n: Failed to load '${root.currentLocale}' (${error}), ` +
|
||||
"falling back to English")
|
||||
root._fallbackToEnglish()
|
||||
}
|
||||
}
|
||||
|
||||
function tr(term, context) {
|
||||
if (!translationsLoaded || !translations) {
|
||||
return term
|
||||
}
|
||||
|
||||
const actualContext = context || term
|
||||
|
||||
if (translations[actualContext] && translations[actualContext][term]) {
|
||||
return translations[actualContext][term]
|
||||
}
|
||||
|
||||
for (const ctx in translations) {
|
||||
if (translations[ctx][term]) {
|
||||
return translations[ctx][term]
|
||||
function _pickTranslation() {
|
||||
const present = new Set()
|
||||
for (let i = 0; i < dir.count; i++) {
|
||||
const name = dir.get(i, "fileName") // e.g. "zh_CN.json"
|
||||
if (name && name.endsWith(".json")) {
|
||||
present.add(name.slice(0, -5))
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < _candidates.length; i++) {
|
||||
const cand = _candidates[i]
|
||||
if (present.has(cand)) {
|
||||
_useLocale(cand, dir.folder + "/" + cand + ".json")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_fallbackToEnglish()
|
||||
}
|
||||
|
||||
function _useLocale(localeTag, fileUrl) {
|
||||
currentLocale = localeTag
|
||||
_selectedPath = fileUrl
|
||||
translationsLoaded = false
|
||||
translations = ({})
|
||||
console.log(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
|
||||
}
|
||||
|
||||
function _fallbackToEnglish() {
|
||||
currentLocale = "en"
|
||||
_selectedPath = ""
|
||||
translationsLoaded = false
|
||||
translations = ({})
|
||||
console.warn("I18n: Falling back to built-in English strings")
|
||||
}
|
||||
|
||||
function tr(term, context) {
|
||||
if (!translationsLoaded || !translations) return term
|
||||
const ctx = context || term
|
||||
if (translations[ctx] && translations[ctx][term]) return translations[ctx][term]
|
||||
for (const c in translations) {
|
||||
if (translations[c] && translations[c][term]) return translations[c][term]
|
||||
}
|
||||
return term
|
||||
}
|
||||
|
||||
function trContext(context, term) {
|
||||
if (!translationsLoaded || !translations) {
|
||||
return term
|
||||
}
|
||||
|
||||
if (translations[context] && translations[context][term]) {
|
||||
return translations[context][term]
|
||||
}
|
||||
|
||||
if (!translationsLoaded || !translations) return term
|
||||
if (translations[context] && translations[context][term]) return translations[context][term]
|
||||
return term
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ Singleton {
|
||||
return stringify(path).replace("file://", "")
|
||||
}
|
||||
|
||||
function toFileUrl(path: string): string {
|
||||
return path.startsWith("file://") ? path : "file://" + path
|
||||
}
|
||||
|
||||
function mkdir(path: url): void {
|
||||
Quickshell.execDetached(["mkdir", "-p", strip(path)])
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ Singleton {
|
||||
property int batteryHibernateTimeout: 0 // Never
|
||||
|
||||
property bool lockBeforeSuspend: false
|
||||
property bool loginctlLockIntegration: true
|
||||
property var recentColors: []
|
||||
property bool showThirdPartyPlugins: false
|
||||
|
||||
@@ -152,6 +153,7 @@ Singleton {
|
||||
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
|
||||
batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
|
||||
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
|
||||
loginctlLockIntegration = settings.loginctlLockIntegration !== undefined ? settings.loginctlLockIntegration : true
|
||||
recentColors = settings.recentColors !== undefined ? settings.recentColors : []
|
||||
showThirdPartyPlugins = settings.showThirdPartyPlugins !== undefined ? settings.showThirdPartyPlugins : false
|
||||
|
||||
@@ -215,6 +217,7 @@ Singleton {
|
||||
"batterySuspendTimeout": batterySuspendTimeout,
|
||||
"batteryHibernateTimeout": batteryHibernateTimeout,
|
||||
"lockBeforeSuspend": lockBeforeSuspend,
|
||||
"loginctlLockIntegration": loginctlLockIntegration,
|
||||
"recentColors": recentColors,
|
||||
"showThirdPartyPlugins": showThirdPartyPlugins
|
||||
}, null, 2))
|
||||
@@ -643,6 +646,11 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLoginctlLockIntegration(enabled) {
|
||||
loginctlLockIntegration = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setShowThirdPartyPlugins(enabled) {
|
||||
showThirdPartyPlugins = enabled
|
||||
saveSettings()
|
||||
|
||||
@@ -33,6 +33,7 @@ Singleton {
|
||||
property string currentThemeName: "blue"
|
||||
property string customThemeFile: ""
|
||||
property string matugenScheme: "scheme-tonal-spot"
|
||||
property bool runUserMatugenTemplates: true
|
||||
property real dankBarTransparency: 1.0
|
||||
property real dankBarWidgetTransparency: 1.0
|
||||
property real popupTransparency: 1.0
|
||||
@@ -155,6 +156,16 @@ Singleton {
|
||||
property bool dankBarNoBackground: false
|
||||
property bool dankBarGothCornersEnabled: false
|
||||
property bool dankBarBorderEnabled: false
|
||||
property string dankBarBorderColor: "surfaceText"
|
||||
property real dankBarBorderOpacity: 1.0
|
||||
property real dankBarBorderThickness: 1
|
||||
property bool popupGapsAuto: true
|
||||
property int popupGapsManual: 4
|
||||
|
||||
onDankBarBorderColorChanged: saveSettings()
|
||||
onDankBarBorderOpacityChanged: saveSettings()
|
||||
onDankBarBorderThicknessChanged: saveSettings()
|
||||
|
||||
property int dankBarPosition: SettingsData.Position.Top
|
||||
property bool dankBarIsVertical: dankBarPosition === SettingsData.Position.Left || dankBarPosition === SettingsData.Position.Right
|
||||
property bool lockScreenShowPowerActions: true
|
||||
@@ -166,6 +177,9 @@ Singleton {
|
||||
property int notificationTimeoutCritical: 0
|
||||
property int notificationPopupPosition: SettingsData.Position.Top
|
||||
property bool osdAlwaysShowValue: false
|
||||
property bool updaterUseCustomCommand: false
|
||||
property string updaterCustomCommand: ""
|
||||
property string updaterTerminalAdditionalParams: ""
|
||||
property var screenPreferences: ({})
|
||||
property int animationSpeed: SettingsData.AnimationSpeed.Short
|
||||
readonly property string defaultFontFamily: "Inter Variable"
|
||||
@@ -275,6 +289,7 @@ Singleton {
|
||||
}
|
||||
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
|
||||
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot"
|
||||
runUserMatugenTemplates = settings.runUserMatugenTemplates !== undefined ? settings.runUserMatugenTemplates : true
|
||||
dankBarTransparency = settings.dankBarTransparency !== undefined ? (settings.dankBarTransparency > 1 ? settings.dankBarTransparency / 100 : settings.dankBarTransparency) : (settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 1.0)
|
||||
dankBarWidgetTransparency = settings.dankBarWidgetTransparency !== undefined ? (settings.dankBarWidgetTransparency > 1 ? settings.dankBarWidgetTransparency / 100 : settings.dankBarWidgetTransparency) : (settings.topBarWidgetTransparency !== undefined ? (settings.topBarWidgetTransparency > 1 ? settings.topBarWidgetTransparency / 100 : settings.topBarWidgetTransparency) : 1.0)
|
||||
popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 1.0
|
||||
@@ -400,6 +415,9 @@ Singleton {
|
||||
notificationTimeoutCritical = settings.notificationTimeoutCritical !== undefined ? settings.notificationTimeoutCritical : 0
|
||||
notificationPopupPosition = settings.notificationPopupPosition !== undefined ? settings.notificationPopupPosition : SettingsData.Position.Top
|
||||
osdAlwaysShowValue = settings.osdAlwaysShowValue !== undefined ? settings.osdAlwaysShowValue : false
|
||||
updaterUseCustomCommand = settings.updaterUseCustomCommand !== undefined ? settings.updaterUseCustomCommand : false;
|
||||
updaterCustomCommand = settings.updaterCustomCommand !== undefined ? settings.updaterCustomCommand : "";
|
||||
updaterTerminalAdditionalParams = settings.updaterTerminalAdditionalParams !== undefined ? settings.updaterTerminalAdditionalParams : "";
|
||||
dankBarSpacing = settings.dankBarSpacing !== undefined ? settings.dankBarSpacing : (settings.topBarSpacing !== undefined ? settings.topBarSpacing : 4)
|
||||
dankBarBottomGap = settings.dankBarBottomGap !== undefined ? settings.dankBarBottomGap : (settings.topBarBottomGap !== undefined ? settings.topBarBottomGap : 0)
|
||||
dankBarInnerPadding = settings.dankBarInnerPadding !== undefined ? settings.dankBarInnerPadding : (settings.topBarInnerPadding !== undefined ? settings.topBarInnerPadding : 4)
|
||||
@@ -407,6 +425,11 @@ Singleton {
|
||||
dankBarNoBackground = settings.dankBarNoBackground !== undefined ? settings.dankBarNoBackground : (settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false)
|
||||
dankBarGothCornersEnabled = settings.dankBarGothCornersEnabled !== undefined ? settings.dankBarGothCornersEnabled : (settings.topBarGothCornersEnabled !== undefined ? settings.topBarGothCornersEnabled : false)
|
||||
dankBarBorderEnabled = settings.dankBarBorderEnabled !== undefined ? settings.dankBarBorderEnabled : false
|
||||
dankBarBorderColor = settings.dankBarBorderColor !== undefined ? settings.dankBarBorderColor : "surfaceText"
|
||||
dankBarBorderOpacity = settings.dankBarBorderOpacity !== undefined ? settings.dankBarBorderOpacity : 1.0
|
||||
dankBarBorderThickness = settings.dankBarBorderThickness !== undefined ? settings.dankBarBorderThickness : 1
|
||||
popupGapsAuto = settings.popupGapsAuto !== undefined ? settings.popupGapsAuto : true
|
||||
popupGapsManual = settings.popupGapsManual !== undefined ? settings.popupGapsManual : 4
|
||||
dankBarPosition = settings.dankBarPosition !== undefined ? settings.dankBarPosition : (settings.dankBarAtBottom !== undefined ? (settings.dankBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : (settings.topBarAtBottom !== undefined ? (settings.topBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : SettingsData.Position.Top))
|
||||
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true
|
||||
hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false
|
||||
@@ -441,6 +464,7 @@ Singleton {
|
||||
"currentThemeName": currentThemeName,
|
||||
"customThemeFile": customThemeFile,
|
||||
"matugenScheme": matugenScheme,
|
||||
"runUserMatugenTemplates": runUserMatugenTemplates,
|
||||
"dankBarTransparency": dankBarTransparency,
|
||||
"dankBarWidgetTransparency": dankBarWidgetTransparency,
|
||||
"popupTransparency": popupTransparency,
|
||||
@@ -533,6 +557,11 @@ Singleton {
|
||||
"dankBarNoBackground": dankBarNoBackground,
|
||||
"dankBarGothCornersEnabled": dankBarGothCornersEnabled,
|
||||
"dankBarBorderEnabled": dankBarBorderEnabled,
|
||||
"dankBarBorderColor": dankBarBorderColor,
|
||||
"dankBarBorderOpacity": dankBarBorderOpacity,
|
||||
"dankBarBorderThickness": dankBarBorderThickness,
|
||||
"popupGapsAuto": popupGapsAuto,
|
||||
"popupGapsManual": popupGapsManual,
|
||||
"dankBarPosition": dankBarPosition,
|
||||
"lockScreenShowPowerActions": lockScreenShowPowerActions,
|
||||
"hideBrightnessSlider": hideBrightnessSlider,
|
||||
@@ -543,6 +572,9 @@ Singleton {
|
||||
"notificationTimeoutCritical": notificationTimeoutCritical,
|
||||
"notificationPopupPosition": notificationPopupPosition,
|
||||
"osdAlwaysShowValue": osdAlwaysShowValue,
|
||||
"updaterUseCustomCommand": updaterUseCustomCommand,
|
||||
"updaterCustomCommand": updaterCustomCommand,
|
||||
"updaterTerminalAdditionalParams": updaterTerminalAdditionalParams,
|
||||
"screenPreferences": screenPreferences,
|
||||
"animationSpeed": animationSpeed
|
||||
}, null, 2))
|
||||
@@ -584,6 +616,21 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setUpdaterUseCustomCommandEnabled(enabled) {
|
||||
updaterUseCustomCommand = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setUpdaterCustomCommand(command) {
|
||||
updaterCustomCommand = command;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setUpdaterTerminalAdditionalParams(customArgs) {
|
||||
updaterTerminalAdditionalParams = customArgs;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setWorkspaceNameIcon(workspaceName, iconData) {
|
||||
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons))
|
||||
iconMap[workspaceName] = iconData
|
||||
@@ -697,6 +744,18 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function setRunUserMatugenTemplates(enabled) {
|
||||
if (runUserMatugenTemplates === enabled)
|
||||
return
|
||||
|
||||
runUserMatugenTemplates = enabled
|
||||
saveSettings()
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
|
||||
function setDankBarTransparency(transparency) {
|
||||
dankBarTransparency = transparency
|
||||
saveSettings()
|
||||
@@ -969,21 +1028,21 @@ Singleton {
|
||||
updateQtIconTheme(themeName)
|
||||
saveSettings()
|
||||
if (typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic)
|
||||
Theme.generateSystemThemes()
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
|
||||
function updateGtkIconTheme(themeName) {
|
||||
var gtkThemeName = (themeName === "System Default") ? systemDefaultIconTheme : themeName
|
||||
if (gtkThemeName !== "System Default" && gtkThemeName !== "") {
|
||||
var script = "if command -v gsettings >/dev/null 2>&1 && gsettings list-schemas | grep -q org.gnome.desktop.interface; then\n"
|
||||
+ " gsettings set org.gnome.desktop.interface icon-theme '" + gtkThemeName + "'\n" + " echo 'Updated via gsettings'\n" + "elif command -v dconf >/dev/null 2>&1; then\n" + " dconf write /org/gnome/desktop/interface/icon-theme \\\"" + gtkThemeName + "\\\"\n"
|
||||
+ " echo 'Updated via dconf'\n" + "fi\n" + "\n" + "# Ensure config directories exist\n" + "mkdir -p " + _configDir + "/gtk-3.0 " + _configDir
|
||||
+ "/gtk-4.0\n" + "\n" + "# Update settings.ini files (keep existing gtk-theme-name)\n" + "for config_dir in " + _configDir + "/gtk-3.0 " + _configDir + "/gtk-4.0; do\n"
|
||||
+ " settings_file=\"$config_dir/settings.ini\"\n" + " if [ -f \"$settings_file\" ]; then\n" + " # Update existing icon-theme-name line or add it\n" + " if grep -q '^gtk-icon-theme-name=' \"$settings_file\"; then\n" + " sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=" + gtkThemeName + "/' \"$settings_file\"\n" + " else\n"
|
||||
+ " # Add icon theme setting to [Settings] section or create it\n" + " if grep -q '\\[Settings\\]' \"$settings_file\"; then\n" + " sed -i '/\\[Settings\\]/a gtk-icon-theme-name=" + gtkThemeName + "' \"$settings_file\"\n" + " else\n" + " echo -e '\\n[Settings]\\ngtk-icon-theme-name=" + gtkThemeName
|
||||
+ "' >> \"$settings_file\"\n" + " fi\n" + " fi\n" + " else\n" + " # Create new settings.ini file\n" + " echo -e '[Settings]\\ngtk-icon-theme-name=" + gtkThemeName + "' > \"$settings_file\"\n"
|
||||
+ " fi\n" + " echo \"Updated $settings_file\"\n" + "done\n" + "\n" + "# Clear icon cache and force refresh\n" + "rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true\n" + "# Send SIGHUP to running GTK applications to reload themes (Fedora-specific)\n" + "pkill -HUP -f 'gtk' 2>/dev/null || true\n"
|
||||
Quickshell.execDetached(["sh", "-lc", script])
|
||||
if (DMSService.apiVersion >= 3) {
|
||||
PortalService.setSystemIconTheme(gtkThemeName)
|
||||
}
|
||||
|
||||
var configScript = "mkdir -p " + _configDir + "/gtk-3.0 " + _configDir + "/gtk-4.0\n" + "\n" + "for config_dir in " + _configDir + "/gtk-3.0 " + _configDir + "/gtk-4.0; do\n"
|
||||
+ " settings_file=\"$config_dir/settings.ini\"\n" + " if [ -f \"$settings_file\" ]; then\n" + " if grep -q '^gtk-icon-theme-name=' \"$settings_file\"; then\n" + " sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=" + gtkThemeName + "/' \"$settings_file\"\n" + " else\n"
|
||||
+ " if grep -q '\\[Settings\\]' \"$settings_file\"; then\n" + " sed -i '/\\[Settings\\]/a gtk-icon-theme-name=" + gtkThemeName + "' \"$settings_file\"\n" + " else\n" + " echo -e '\\n[Settings]\\ngtk-icon-theme-name=" + gtkThemeName + "' >> \"$settings_file\"\n" + " fi\n"
|
||||
+ " fi\n" + " else\n" + " echo -e '[Settings]\\ngtk-icon-theme-name=" + gtkThemeName + "' > \"$settings_file\"\n" + " fi\n" + "done\n" + "\n" + "rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true\n" + "pkill -HUP -f 'gtk' 2>/dev/null || true\n"
|
||||
Quickshell.execDetached(["sh", "-lc", configScript])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1224,6 +1283,9 @@ Singleton {
|
||||
function setDankBarSpacing(spacing) {
|
||||
dankBarSpacing = spacing
|
||||
saveSettings()
|
||||
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
|
||||
NiriService.generateNiriLayoutConfig()
|
||||
}
|
||||
}
|
||||
|
||||
function setDankBarBottomGap(gap) {
|
||||
@@ -1256,6 +1318,16 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setPopupGapsAuto(enabled) {
|
||||
popupGapsAuto = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setPopupGapsManual(value) {
|
||||
popupGapsManual = value
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setDankBarPosition(position) {
|
||||
dankBarPosition = position
|
||||
if (position === SettingsData.Position.Bottom && dockPosition === SettingsData.Position.Bottom && showDock) {
|
||||
|
||||
@@ -16,8 +16,10 @@ Singleton {
|
||||
|
||||
readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true"
|
||||
|
||||
// ! TODO - Synchronize with niri/hyprland gaps?
|
||||
readonly property real popupDistance: 2
|
||||
readonly property real popupDistance: {
|
||||
if (typeof SettingsData === "undefined") return 4
|
||||
return SettingsData.popupGapsAuto ? Math.max(4, SettingsData.dankBarSpacing) : SettingsData.popupGapsManual
|
||||
}
|
||||
|
||||
property string currentTheme: "blue"
|
||||
property string currentThemeCategory: "generic"
|
||||
@@ -74,7 +76,6 @@ Singleton {
|
||||
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
|
||||
property var workerRunning: false
|
||||
property var matugenColors: ({})
|
||||
property int colorUpdateTrigger: 0
|
||||
property var customThemeData: null
|
||||
|
||||
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell"
|
||||
@@ -84,7 +85,6 @@ Singleton {
|
||||
matugenCheck.running = true
|
||||
if (typeof SessionData !== "undefined") {
|
||||
SessionData.isLightModeChanged.connect(root.onLightModeChanged)
|
||||
isLightMode = SessionData.isLightMode
|
||||
}
|
||||
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
|
||||
@@ -100,7 +100,6 @@ Singleton {
|
||||
}
|
||||
|
||||
function getMatugenColor(path, fallback) {
|
||||
colorUpdateTrigger
|
||||
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
|
||||
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode]
|
||||
for (const part of path.split(".")) {
|
||||
@@ -578,10 +577,6 @@ Singleton {
|
||||
|
||||
|
||||
function onLightModeChanged() {
|
||||
if (matugenColors && Object.keys(matugenColors).length > 0) {
|
||||
colorUpdateTrigger++
|
||||
}
|
||||
|
||||
if (currentTheme === "custom" && customThemeFileView.path) {
|
||||
customThemeFileView.reload()
|
||||
}
|
||||
@@ -603,7 +598,8 @@ Singleton {
|
||||
"mode": isLight ? "light" : "dark",
|
||||
"iconTheme": iconTheme || "System Default",
|
||||
"matugenType": matugenType || "scheme-tonal-spot",
|
||||
"surfaceBase": (typeof SettingsData !== "undefined" && SettingsData.surfaceBase) ? SettingsData.surfaceBase : "sc"
|
||||
"surfaceBase": (typeof SettingsData !== "undefined" && SettingsData.surfaceBase) ? SettingsData.surfaceBase : "sc",
|
||||
"runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true
|
||||
}
|
||||
|
||||
const json = JSON.stringify(desired)
|
||||
@@ -692,7 +688,17 @@ Singleton {
|
||||
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
|
||||
|
||||
function snap(value, dpr) {
|
||||
return Math.round(value * dpr) / dpr
|
||||
const s = dpr || 1
|
||||
return Math.round(value * s) / s
|
||||
}
|
||||
|
||||
function px(value, dpr) {
|
||||
const s = dpr || 1
|
||||
return Math.round(value * s) / s
|
||||
}
|
||||
|
||||
function hairline(dpr) {
|
||||
return 1 / (dpr || 1)
|
||||
}
|
||||
|
||||
function invertHex(hex) {
|
||||
@@ -905,7 +911,6 @@ Singleton {
|
||||
const colorsText = dynamicColorsFileView.text()
|
||||
if (colorsText) {
|
||||
root.matugenColors = JSON.parse(colorsText)
|
||||
root.colorUpdateTrigger++
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.clearWallpaperError()
|
||||
}
|
||||
|
||||
24
DMSShell.qml
24
DMSShell.qml
@@ -54,26 +54,8 @@ Item {
|
||||
|
||||
WallpaperBackground {}
|
||||
|
||||
LazyLoader {
|
||||
id: lockLoader
|
||||
active: false
|
||||
|
||||
Lock {
|
||||
id: lock
|
||||
anchors.fill: parent
|
||||
|
||||
Component.onCompleted: {
|
||||
IdleService.lockComponent = lock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: lockInitTimer
|
||||
interval: 100
|
||||
running: true
|
||||
repeat: false
|
||||
onTriggered: lockLoader.active = true
|
||||
Lock {
|
||||
id: lock
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -195,7 +177,7 @@ Item {
|
||||
powerMenuModalLoader: controlCenterLoader.powerModalLoaderRef
|
||||
|
||||
onLockRequested: {
|
||||
lockLoader.item.activate()
|
||||
lock.activate()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -46,6 +46,7 @@ Item {
|
||||
leftIconName: "search"
|
||||
showClearButton: true
|
||||
focus: true
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [modal.modalFocusScope]
|
||||
onTextChanged: {
|
||||
modal.searchText = text
|
||||
|
||||
@@ -60,6 +60,7 @@ DankModal {
|
||||
open()
|
||||
clipboardHistoryModal.searchText = ""
|
||||
clipboardHistoryModal.activeImageLoads = 0
|
||||
clipboardHistoryModal.shouldHaveFocus = true
|
||||
refreshClipboard()
|
||||
keyboardController.reset()
|
||||
|
||||
|
||||
@@ -68,9 +68,11 @@ DankModal {
|
||||
}
|
||||
}
|
||||
onOpened: {
|
||||
modalFocusScope.forceActiveFocus()
|
||||
modalFocusScope.focus = true
|
||||
shouldHaveFocus = true
|
||||
Qt.callLater(function () {
|
||||
modalFocusScope.forceActiveFocus()
|
||||
modalFocusScope.focus = true
|
||||
shouldHaveFocus = true
|
||||
})
|
||||
}
|
||||
modalFocusScope.Keys.onPressed: function (event) {
|
||||
switch (event.key) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
@@ -14,14 +16,16 @@ PanelWindow {
|
||||
property real height: 300
|
||||
readonly property real screenWidth: screen ? screen.width : 1920
|
||||
readonly property real screenHeight: screen ? screen.height : 1080
|
||||
readonly property real dpr: (screen && screen.devicePixelRatio) || 1
|
||||
|
||||
function snap(v) {
|
||||
return Math.round(v * dpr) / dpr
|
||||
}
|
||||
|
||||
function px(v) {
|
||||
return Math.round(v)
|
||||
readonly property real dpr: {
|
||||
if (CompositorService.isNiri && screen) {
|
||||
const niriScale = NiriService.displayScales[screen.name]
|
||||
if (niriScale !== undefined) return niriScale
|
||||
}
|
||||
if (CompositorService.isHyprland && screen) {
|
||||
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === screen.name)
|
||||
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
|
||||
}
|
||||
return (screen?.devicePixelRatio) || 1
|
||||
}
|
||||
property bool showBackground: true
|
||||
property real backgroundOpacity: 0.5
|
||||
@@ -142,26 +146,26 @@ PanelWindow {
|
||||
Rectangle {
|
||||
id: contentContainer
|
||||
|
||||
width: px(root.width)
|
||||
height: px(root.height)
|
||||
width: Theme.px(root.width, dpr)
|
||||
height: Theme.px(root.height, dpr)
|
||||
anchors.centerIn: undefined
|
||||
x: {
|
||||
if (positioning === "center") {
|
||||
return snap((root.screenWidth - width) / 2)
|
||||
return Theme.snap((root.screenWidth - width) / 2, dpr)
|
||||
} else if (positioning === "top-right") {
|
||||
return px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL))
|
||||
return Theme.px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL), dpr)
|
||||
} else if (positioning === "custom") {
|
||||
return snap(root.customPosition.x)
|
||||
return Theme.snap(root.customPosition.x, dpr)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
y: {
|
||||
if (positioning === "center") {
|
||||
return snap((root.screenHeight - height) / 2)
|
||||
return Theme.snap((root.screenHeight - height) / 2, dpr)
|
||||
} else if (positioning === "top-right") {
|
||||
return px(Theme.barHeight + Theme.spacingXS)
|
||||
return Theme.px(Theme.barHeight + Theme.spacingXS, dpr)
|
||||
} else if (positioning === "custom") {
|
||||
return snap(root.customPosition.y)
|
||||
return Theme.snap(root.customPosition.y, dpr)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -170,6 +174,7 @@ PanelWindow {
|
||||
border.color: root.borderColor
|
||||
border.width: root.borderWidth
|
||||
clip: false
|
||||
layer.enabled: true
|
||||
opacity: root.shouldBeVisible ? 1 : 0
|
||||
transform: root.animationType === "slide" ? slideTransform : null
|
||||
|
||||
@@ -179,8 +184,8 @@ PanelWindow {
|
||||
readonly property real rawX: root.shouldBeVisible ? 0 : 15
|
||||
readonly property real rawY: root.shouldBeVisible ? 0 : -30
|
||||
|
||||
x: snap(rawX)
|
||||
y: snap(rawY)
|
||||
x: Theme.snap(rawX, root.dpr)
|
||||
y: Theme.snap(rawY, root.dpr)
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
|
||||
@@ -621,7 +621,6 @@ DankModal {
|
||||
required property bool fileIsDir
|
||||
required property string filePath
|
||||
required property string fileName
|
||||
required property url fileURL
|
||||
required property int index
|
||||
|
||||
width: weMode ? 245 : 140
|
||||
|
||||
@@ -54,6 +54,9 @@ DankModal {
|
||||
height: 700
|
||||
visible: false
|
||||
onBackgroundClicked: hide()
|
||||
onOpened: () => {
|
||||
Qt.callLater(() => modalFocusScope.forceActiveFocus());
|
||||
}
|
||||
onShouldBeVisibleChanged: (shouldBeVisible) => {
|
||||
if (!shouldBeVisible) {
|
||||
notificationModalOpen = false
|
||||
|
||||
@@ -78,7 +78,7 @@ DankModal {
|
||||
}
|
||||
onOpened: () => {
|
||||
selectedIndex = 0;
|
||||
modalFocusScope.forceActiveFocus();
|
||||
Qt.callLater(() => modalFocusScope.forceActiveFocus());
|
||||
}
|
||||
modalFocusScope.Keys.onPressed: (event) => {
|
||||
switch (event.key) {
|
||||
|
||||
@@ -25,6 +25,81 @@ Item {
|
||||
visible: !BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: lockScreenSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: lockScreenSection
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "lock"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Lock Screen")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Show Power Actions")
|
||||
description: "Show power, restart, and logout buttons on the lock screen"
|
||||
checked: SettingsData.lockScreenShowPowerActions
|
||||
onToggled: checked => SettingsData.setLockScreenShowPowerActions(checked)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("loginctl not available - lock integration requires DMS socket connection")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.warning
|
||||
visible: !SessionService.loginctlAvailable
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Enable loginctl lock integration")
|
||||
description: "Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen."
|
||||
checked: SessionService.loginctlAvailable && SessionData.loginctlLockIntegration
|
||||
enabled: SessionService.loginctlAvailable
|
||||
onToggled: checked => {
|
||||
if (SessionService.loginctlAvailable) {
|
||||
SessionData.setLoginctlLockIntegration(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Lock before suspend")
|
||||
description: "Automatically lock the screen when the system prepares to suspend"
|
||||
checked: SessionData.lockBeforeSuspend
|
||||
visible: SessionService.loginctlAvailable && SessionData.loginctlLockIntegration
|
||||
onToggled: checked => SessionData.setLockBeforeSuspend(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: timeoutSection.implicitHeight + Theme.spacingL * 2
|
||||
@@ -219,14 +294,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Lock before suspend")
|
||||
description: "Automatically lock the screen when the system prepares to suspend"
|
||||
checked: SessionData.lockBeforeSuspend
|
||||
onToggled: checked => SessionData.setLockBeforeSuspend(checked)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Idle monitoring not supported - requires newer Quickshell version")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
|
||||
@@ -35,27 +35,14 @@ FocusScope {
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: timeLoader
|
||||
id: timeWeatherLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 1
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: TimeTab {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: weatherLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 2
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: WeatherTab {
|
||||
sourceComponent: TimeWeatherTab {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,7 +51,7 @@ FocusScope {
|
||||
id: topBarLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 3
|
||||
active: root.currentIndex === 2
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
@@ -78,7 +65,7 @@ FocusScope {
|
||||
id: widgetsLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 4
|
||||
active: root.currentIndex === 3
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
@@ -91,7 +78,7 @@ FocusScope {
|
||||
id: dockLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 5
|
||||
active: root.currentIndex === 4
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
@@ -107,7 +94,7 @@ FocusScope {
|
||||
id: displaysLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 6
|
||||
active: root.currentIndex === 5
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
@@ -120,7 +107,7 @@ FocusScope {
|
||||
id: launcherLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 7
|
||||
active: root.currentIndex === 6
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
@@ -133,7 +120,7 @@ FocusScope {
|
||||
id: themeColorsLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 8
|
||||
active: root.currentIndex === 7
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
@@ -146,7 +133,7 @@ FocusScope {
|
||||
id: powerLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 9
|
||||
active: root.currentIndex === 8
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
@@ -159,7 +146,7 @@ FocusScope {
|
||||
id: pluginsLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 10
|
||||
active: root.currentIndex === 9
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
@@ -173,7 +160,7 @@ FocusScope {
|
||||
id: aboutLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 11
|
||||
active: root.currentIndex === 10
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
|
||||
@@ -12,11 +12,8 @@ Rectangle {
|
||||
"text": I18n.tr("Personalization"),
|
||||
"icon": "person"
|
||||
}, {
|
||||
"text": I18n.tr("Time & Date"),
|
||||
"text": I18n.tr("Time & Weather"),
|
||||
"icon": "schedule"
|
||||
}, {
|
||||
"text": I18n.tr("Weather"),
|
||||
"icon": "cloud"
|
||||
}, {
|
||||
"text": I18n.tr("Dank Bar"),
|
||||
"icon": "toolbar"
|
||||
@@ -36,8 +33,8 @@ Rectangle {
|
||||
"text": I18n.tr("Theme & Colors"),
|
||||
"icon": "palette"
|
||||
}, {
|
||||
"text": I18n.tr("Power"),
|
||||
"icon": "power_settings_new"
|
||||
"text": I18n.tr("Idle & Lock Screen"),
|
||||
"icon": "lock"
|
||||
}, {
|
||||
"text": I18n.tr("Plugins"),
|
||||
"icon": "extension"
|
||||
|
||||
@@ -128,6 +128,7 @@ Item {
|
||||
enabled: parentModal ? parentModal.spotlightOpen : true
|
||||
placeholderText: ""
|
||||
ignoreLeftRightKeys: appLauncher.viewMode !== "list"
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [spotlightKeyHandler]
|
||||
text: appLauncher.searchQuery
|
||||
onTextEdited: () => {
|
||||
|
||||
@@ -77,12 +77,14 @@ Popup {
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
implicitWidth: pinRow.implicitWidth + Theme.spacingS * 2
|
||||
width: implicitWidth
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: pinRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -155,16 +157,16 @@ Popup {
|
||||
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
implicitWidth: actionRow.implicitWidth + Theme.spacingS * 2
|
||||
width: implicitWidth
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: actionRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
@@ -189,8 +191,6 @@ Popup {
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
width: parent.width - (modelData.icon && modelData.icon !== "" ? (Theme.iconSize - 2 + Theme.spacingS) : 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,12 +228,14 @@ Popup {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
implicitWidth: launchRow.implicitWidth + Theme.spacingS * 2
|
||||
width: implicitWidth
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: launchRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -288,12 +290,14 @@ Popup {
|
||||
|
||||
Rectangle {
|
||||
visible: SessionService.hasPrimeRun
|
||||
width: parent.width
|
||||
implicitWidth: primeRunRow.implicitWidth + Theme.spacingS * 2
|
||||
width: implicitWidth
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: primeRunRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -112,6 +112,8 @@ DankPopout {
|
||||
mappings[Qt.Key_Up] = () => appLauncher.selectPrevious()
|
||||
mappings[Qt.Key_Return] = () => appLauncher.launchSelected()
|
||||
mappings[Qt.Key_Enter] = () => appLauncher.launchSelected()
|
||||
mappings[Qt.Key_Tab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : appLauncher.selectNext()
|
||||
mappings[Qt.Key_Backtab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : appLauncher.selectPrevious()
|
||||
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow()
|
||||
@@ -217,6 +219,7 @@ DankPopout {
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
enabled: appDrawerPopout.shouldBeVisible
|
||||
ignoreLeftRightKeys: appLauncher.viewMode !== "list"
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [keyHandler]
|
||||
onTextEdited: {
|
||||
appLauncher.searchQuery = text
|
||||
@@ -241,7 +244,7 @@ DankPopout {
|
||||
return
|
||||
}
|
||||
|
||||
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right]
|
||||
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right, Qt.Key_Tab, Qt.Key_Backtab]
|
||||
const isNavigationKey = navigationKeys.includes(event.key)
|
||||
const isEmptyEnter = isEnterKey && !hasText
|
||||
|
||||
@@ -797,12 +800,13 @@ DankPopout {
|
||||
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
width: Math.max(parent.width, actionRow.implicitWidth + Theme.spacingS * 2)
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: actionRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -18,7 +18,7 @@ Item {
|
||||
property int debounceInterval: 50
|
||||
property bool keyboardNavigationActive: false
|
||||
property bool suppressUpdatesWhileLaunching: false
|
||||
readonly property var categories: {
|
||||
property var categories: {
|
||||
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
|
||||
const result = [I18n.tr("All")]
|
||||
return result.concat(allCategories.filter(cat => cat !== I18n.tr("All")))
|
||||
@@ -27,11 +27,30 @@ Item {
|
||||
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
|
||||
property alias model: filteredModel
|
||||
property var _watchApplications: AppSearchService.applications
|
||||
property var _uniqueApps: []
|
||||
property bool _isTriggered: false
|
||||
property string _triggeredCategory: ""
|
||||
property bool _updatingFromTrigger: false
|
||||
|
||||
signal appLaunched(var app)
|
||||
signal categorySelected(string category)
|
||||
signal viewModeSelected(string mode)
|
||||
|
||||
function updateCategories() {
|
||||
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
|
||||
const result = [I18n.tr("All")]
|
||||
categories = result.concat(allCategories.filter(cat => cat !== I18n.tr("All")))
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PluginService
|
||||
function onPluginLoaded() { updateCategories() }
|
||||
function onPluginUnloaded() { updateCategories() }
|
||||
function onPluginListUpdated() { updateCategories() }
|
||||
}
|
||||
|
||||
|
||||
|
||||
function updateFilteredModel() {
|
||||
if (suppressUpdatesWhileLaunching) {
|
||||
suppressUpdatesWhileLaunching = false
|
||||
@@ -41,21 +60,64 @@ Item {
|
||||
selectedIndex = 0
|
||||
keyboardNavigationActive = false
|
||||
|
||||
const triggerResult = checkPluginTriggers(searchQuery)
|
||||
if (triggerResult.triggered) {
|
||||
console.log("AppLauncher: Plugin trigger detected:", triggerResult.trigger, "for plugin:", triggerResult.pluginId)
|
||||
}
|
||||
|
||||
let apps = []
|
||||
const allCategory = I18n.tr("All")
|
||||
if (searchQuery.length === 0) {
|
||||
apps = selectedCategory === allCategory ? AppSearchService.getAppsInCategory(allCategory) : AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
|
||||
const emptyTriggerPlugins = typeof PluginService !== "undefined" ? PluginService.getPluginsWithEmptyTrigger() : []
|
||||
|
||||
if (triggerResult.triggered) {
|
||||
_isTriggered = true
|
||||
_triggeredCategory = triggerResult.pluginCategory
|
||||
_updatingFromTrigger = true
|
||||
selectedCategory = triggerResult.pluginCategory
|
||||
_updatingFromTrigger = false
|
||||
apps = AppSearchService.getPluginItems(triggerResult.pluginCategory, triggerResult.query)
|
||||
} else {
|
||||
if (selectedCategory === allCategory) {
|
||||
apps = AppSearchService.searchApplications(searchQuery)
|
||||
} else {
|
||||
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||
if (categoryApps.length > 0) {
|
||||
const allSearchResults = AppSearchService.searchApplications(searchQuery)
|
||||
const categoryNames = new Set(categoryApps.map(app => app.name))
|
||||
apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults)
|
||||
if (_isTriggered) {
|
||||
_updatingFromTrigger = true
|
||||
selectedCategory = allCategory
|
||||
_updatingFromTrigger = false
|
||||
_isTriggered = false
|
||||
_triggeredCategory = ""
|
||||
}
|
||||
if (searchQuery.length === 0) {
|
||||
if (selectedCategory === allCategory) {
|
||||
let emptyTriggerItems = []
|
||||
emptyTriggerPlugins.forEach(pluginId => {
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId)
|
||||
const pluginCategory = plugin.name || pluginId
|
||||
const items = AppSearchService.getPluginItems(pluginCategory, "")
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items)
|
||||
})
|
||||
apps = AppSearchService.applications.concat(emptyTriggerItems)
|
||||
} else {
|
||||
apps = []
|
||||
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
|
||||
}
|
||||
} else {
|
||||
if (selectedCategory === allCategory) {
|
||||
apps = AppSearchService.searchApplications(searchQuery)
|
||||
|
||||
let emptyTriggerItems = []
|
||||
emptyTriggerPlugins.forEach(pluginId => {
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId)
|
||||
const pluginCategory = plugin.name || pluginId
|
||||
const items = AppSearchService.getPluginItems(pluginCategory, searchQuery)
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items)
|
||||
})
|
||||
apps = apps.concat(emptyTriggerItems)
|
||||
} else {
|
||||
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||
if (categoryApps.length > 0) {
|
||||
const allSearchResults = AppSearchService.searchApplications(searchQuery)
|
||||
const categoryNames = new Set(categoryApps.map(app => app.name))
|
||||
apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults)
|
||||
} else {
|
||||
apps = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,18 +135,31 @@ Item {
|
||||
})
|
||||
}
|
||||
|
||||
const seenNames = new Set()
|
||||
const uniqueApps = []
|
||||
apps.forEach(app => {
|
||||
if (app) {
|
||||
const itemKey = app.name + "|" + (app.execString || app.exec || app.action || "")
|
||||
if (seenNames.has(itemKey)) {
|
||||
return
|
||||
}
|
||||
seenNames.add(itemKey)
|
||||
uniqueApps.push(app)
|
||||
|
||||
const isPluginItem = app.action !== undefined
|
||||
filteredModel.append({
|
||||
"name": app.name || "",
|
||||
"exec": app.execString || "",
|
||||
"exec": app.execString || app.exec || app.action || "",
|
||||
"icon": app.icon || "application-x-executable",
|
||||
"comment": app.comment || "",
|
||||
"categories": app.categories || [],
|
||||
"desktopEntry": app
|
||||
"isPlugin": isPluginItem,
|
||||
"appIndex": uniqueApps.length - 1
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
root._uniqueApps = uniqueApps
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
@@ -128,13 +203,25 @@ Item {
|
||||
}
|
||||
|
||||
function launchApp(appData) {
|
||||
if (!appData) {
|
||||
if (!appData || typeof appData.appIndex === "undefined" || appData.appIndex < 0 || appData.appIndex >= _uniqueApps.length) {
|
||||
return
|
||||
}
|
||||
suppressUpdatesWhileLaunching = true
|
||||
SessionService.launchDesktopEntry(appData.desktopEntry)
|
||||
appLaunched(appData)
|
||||
AppUsageHistoryData.addAppUsage(appData.desktopEntry)
|
||||
|
||||
const actualApp = _uniqueApps[appData.appIndex]
|
||||
|
||||
if (appData.isPlugin) {
|
||||
const pluginId = getPluginIdForItem(actualApp)
|
||||
if (pluginId) {
|
||||
AppSearchService.executePluginItem(actualApp, pluginId)
|
||||
appLaunched(appData)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
SessionService.launchDesktopEntry(actualApp)
|
||||
appLaunched(appData)
|
||||
AppUsageHistoryData.addAppUsage(actualApp)
|
||||
}
|
||||
}
|
||||
|
||||
function setCategory(category) {
|
||||
@@ -154,7 +241,12 @@ Item {
|
||||
updateFilteredModel()
|
||||
}
|
||||
}
|
||||
onSelectedCategoryChanged: updateFilteredModel()
|
||||
onSelectedCategoryChanged: {
|
||||
if (_updatingFromTrigger) {
|
||||
return
|
||||
}
|
||||
updateFilteredModel()
|
||||
}
|
||||
onAppUsageRankingChanged: updateFilteredModel()
|
||||
on_WatchApplicationsChanged: updateFilteredModel()
|
||||
Component.onCompleted: {
|
||||
@@ -172,4 +264,63 @@ Item {
|
||||
repeat: false
|
||||
onTriggered: updateFilteredModel()
|
||||
}
|
||||
|
||||
// Plugin trigger system functions
|
||||
function checkPluginTriggers(query) {
|
||||
if (!query || typeof PluginService === "undefined") {
|
||||
return { triggered: false, pluginCategory: "", query: "" }
|
||||
}
|
||||
|
||||
const triggers = PluginService.getAllPluginTriggers()
|
||||
|
||||
for (const trigger in triggers) {
|
||||
if (query.startsWith(trigger)) {
|
||||
const pluginId = triggers[trigger]
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId)
|
||||
|
||||
if (plugin) {
|
||||
const remainingQuery = query.substring(trigger.length).trim()
|
||||
const result = {
|
||||
triggered: true,
|
||||
pluginId: pluginId,
|
||||
pluginCategory: plugin.name || pluginId,
|
||||
query: remainingQuery,
|
||||
trigger: trigger
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { triggered: false, pluginCategory: "", query: "" }
|
||||
}
|
||||
|
||||
function getPluginIdForItem(item) {
|
||||
if (!item || !item.categories || typeof PluginService === "undefined") {
|
||||
return null
|
||||
}
|
||||
|
||||
const launchers = PluginService.getLauncherPlugins()
|
||||
for (const pluginId in launchers) {
|
||||
const plugin = launchers[pluginId]
|
||||
const pluginCategory = plugin.name || pluginId
|
||||
|
||||
let hasCategory = false
|
||||
if (Array.isArray(item.categories)) {
|
||||
hasCategory = item.categories.includes(pluginCategory)
|
||||
} else if (item.categories && typeof item.categories.count !== "undefined") {
|
||||
for (let i = 0; i < item.categories.count; i++) {
|
||||
if (item.categories.get(i) === pluginCategory) {
|
||||
hasCategory = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCategory) {
|
||||
return pluginId
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ Rectangle {
|
||||
signal powerButtonClicked()
|
||||
signal lockRequested()
|
||||
signal editModeToggled()
|
||||
signal settingsButtonClicked()
|
||||
|
||||
implicitHeight: 70
|
||||
radius: Theme.cornerRadius
|
||||
@@ -96,6 +97,7 @@ Rectangle {
|
||||
iconColor: Theme.surfaceText
|
||||
backgroundColor: "transparent"
|
||||
onClicked: {
|
||||
root.settingsButtonClicked()
|
||||
settingsModal.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,9 @@ DankPopout {
|
||||
root.close()
|
||||
root.lockRequested()
|
||||
}
|
||||
onSettingsButtonClicked: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
DragDropGrid {
|
||||
|
||||
@@ -16,6 +16,21 @@ Item {
|
||||
anchors.topMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "bottom" ? barWindow._wingR : 0)
|
||||
anchors.bottomMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "top" ? barWindow._wingR : 0)
|
||||
|
||||
function requestRepaint() {
|
||||
debounceTimer.restart()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: debounceTimer
|
||||
interval: 50
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
barShape.requestPaint()
|
||||
barTint.requestPaint()
|
||||
barBorder.requestPaint()
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: barShape
|
||||
anchors.fill: parent
|
||||
@@ -25,44 +40,41 @@ Item {
|
||||
|
||||
readonly property real correctWidth: root.width
|
||||
readonly property real correctHeight: root.height
|
||||
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight))
|
||||
canvasSize: Qt.size(Math.ceil(correctWidth), Math.ceil(correctHeight))
|
||||
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
|
||||
|
||||
onWingChanged: requestPaint()
|
||||
onRtChanged: requestPaint()
|
||||
onCorrectWidthChanged: requestPaint()
|
||||
onCorrectHeightChanged: requestPaint()
|
||||
onVisibleChanged: if (visible) requestPaint()
|
||||
Component.onCompleted: requestPaint()
|
||||
onWingChanged: root.requestRepaint()
|
||||
onRtChanged: root.requestRepaint()
|
||||
onCorrectWidthChanged: root.requestRepaint()
|
||||
onCorrectHeightChanged: root.requestRepaint()
|
||||
onVisibleChanged: if (visible) root.requestRepaint()
|
||||
Component.onCompleted: root.requestRepaint()
|
||||
|
||||
Connections {
|
||||
target: barWindow
|
||||
function on_BgColorChanged() { barShape.requestPaint() }
|
||||
function on_DprChanged() { barShape.requestPaint() }
|
||||
function on_BgColorChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onIsLightModeChanged() { barShape.requestPaint() }
|
||||
function onIsLightModeChanged() { root.requestRepaint() }
|
||||
function onSurfaceContainerChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const scale = barWindow._dpr
|
||||
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth)
|
||||
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight)
|
||||
const R = barWindow.px(wing)
|
||||
const RT = barWindow.px(rt)
|
||||
const W = barWindow.isVertical ? correctHeight : correctWidth
|
||||
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
|
||||
const R = wing
|
||||
const RT = rt
|
||||
const H = H_raw - (R > 0 ? R : 0)
|
||||
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
|
||||
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
|
||||
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
|
||||
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
|
||||
|
||||
ctx.scale(scale, scale)
|
||||
|
||||
function drawTopPath() {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(RT, 0)
|
||||
@@ -89,7 +101,7 @@ Item {
|
||||
}
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, W, H_raw)
|
||||
ctx.clearRect(0, 0, Math.ceil(W), Math.ceil(H_raw))
|
||||
|
||||
ctx.save()
|
||||
if (isBottom) {
|
||||
@@ -120,46 +132,43 @@ Item {
|
||||
|
||||
readonly property real correctWidth: root.width
|
||||
readonly property real correctHeight: root.height
|
||||
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight))
|
||||
canvasSize: Qt.size(Math.ceil(correctWidth), Math.ceil(correctHeight))
|
||||
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
|
||||
property real alphaTint: (barWindow._bgColor?.a ?? 1) < 0.99 ? (Theme.stateLayerOpacity ?? 0) : 0
|
||||
|
||||
onWingChanged: requestPaint()
|
||||
onRtChanged: requestPaint()
|
||||
onAlphaTintChanged: requestPaint()
|
||||
onCorrectWidthChanged: requestPaint()
|
||||
onCorrectHeightChanged: requestPaint()
|
||||
onVisibleChanged: if (visible) requestPaint()
|
||||
Component.onCompleted: requestPaint()
|
||||
onWingChanged: root.requestRepaint()
|
||||
onRtChanged: root.requestRepaint()
|
||||
onAlphaTintChanged: root.requestRepaint()
|
||||
onCorrectWidthChanged: root.requestRepaint()
|
||||
onCorrectHeightChanged: root.requestRepaint()
|
||||
onVisibleChanged: if (visible) root.requestRepaint()
|
||||
Component.onCompleted: root.requestRepaint()
|
||||
|
||||
Connections {
|
||||
target: barWindow
|
||||
function on_BgColorChanged() { barTint.requestPaint() }
|
||||
function on_DprChanged() { barTint.requestPaint() }
|
||||
function on_BgColorChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onIsLightModeChanged() { barTint.requestPaint() }
|
||||
function onIsLightModeChanged() { root.requestRepaint() }
|
||||
function onSurfaceChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const scale = barWindow._dpr
|
||||
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth)
|
||||
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight)
|
||||
const R = barWindow.px(wing)
|
||||
const RT = barWindow.px(rt)
|
||||
const W = barWindow.isVertical ? correctHeight : correctWidth
|
||||
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
|
||||
const R = wing
|
||||
const RT = rt
|
||||
const H = H_raw - (R > 0 ? R : 0)
|
||||
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
|
||||
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
|
||||
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
|
||||
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
|
||||
|
||||
ctx.scale(scale, scale)
|
||||
|
||||
function drawTopPath() {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(RT, 0)
|
||||
@@ -186,7 +195,7 @@ Item {
|
||||
}
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, W, H_raw)
|
||||
ctx.clearRect(0, 0, Math.ceil(W), Math.ceil(H_raw))
|
||||
|
||||
ctx.save()
|
||||
if (isBottom) {
|
||||
@@ -211,53 +220,53 @@ Item {
|
||||
Canvas {
|
||||
id: barBorder
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
antialiasing: false
|
||||
visible: SettingsData.dankBarBorderEnabled
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
renderStrategy: Canvas.Cooperative
|
||||
|
||||
readonly property real correctWidth: root.width
|
||||
readonly property real correctHeight: root.height
|
||||
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight))
|
||||
canvasSize: Qt.size(Math.ceil(correctWidth), Math.ceil(correctHeight))
|
||||
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
|
||||
property bool borderEnabled: SettingsData.dankBarBorderEnabled
|
||||
|
||||
onWingChanged: requestPaint()
|
||||
onRtChanged: requestPaint()
|
||||
onBorderEnabledChanged: requestPaint()
|
||||
onCorrectWidthChanged: requestPaint()
|
||||
onCorrectHeightChanged: requestPaint()
|
||||
onVisibleChanged: if (visible) requestPaint()
|
||||
Component.onCompleted: requestPaint()
|
||||
|
||||
Connections {
|
||||
target: barWindow
|
||||
function on_DprChanged() { barBorder.requestPaint() }
|
||||
}
|
||||
onWingChanged: root.requestRepaint()
|
||||
onRtChanged: root.requestRepaint()
|
||||
onBorderEnabledChanged: root.requestRepaint()
|
||||
onCorrectWidthChanged: root.requestRepaint()
|
||||
onCorrectHeightChanged: root.requestRepaint()
|
||||
onVisibleChanged: if (visible) root.requestRepaint()
|
||||
Component.onCompleted: root.requestRepaint()
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onSecondaryChanged() { barBorder.requestPaint() }
|
||||
function onIsLightModeChanged() { root.requestRepaint() }
|
||||
function onSurfaceTextChanged() { root.requestRepaint() }
|
||||
function onPrimaryChanged() { root.requestRepaint() }
|
||||
function onSecondaryChanged() { root.requestRepaint() }
|
||||
function onOutlineChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onDankBarSpacingChanged() { barBorder.requestPaint() }
|
||||
function onDankBarSquareCornersChanged() { barBorder.requestPaint() }
|
||||
function onCornerRadiusChanged() { barBorder.requestPaint() }
|
||||
function onDankBarBorderColorChanged() { root.requestRepaint() }
|
||||
function onDankBarBorderOpacityChanged() { root.requestRepaint() }
|
||||
function onDankBarBorderThicknessChanged() { root.requestRepaint() }
|
||||
function onDankBarSpacingChanged() { root.requestRepaint() }
|
||||
function onDankBarSquareCornersChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
if (!borderEnabled) return
|
||||
|
||||
const ctx = getContext("2d")
|
||||
const scale = barWindow._dpr
|
||||
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth)
|
||||
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight)
|
||||
const R = barWindow.px(wing)
|
||||
const RT = barWindow.px(rt)
|
||||
const W = barWindow.isVertical ? correctHeight : correctWidth
|
||||
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
|
||||
const R = wing
|
||||
const RT = rt
|
||||
const H = H_raw - (R > 0 ? R : 0)
|
||||
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
|
||||
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
|
||||
@@ -267,8 +276,6 @@ Item {
|
||||
const spacing = SettingsData.dankBarSpacing
|
||||
const hasEdgeGap = spacing > 0 || RT > 0
|
||||
|
||||
ctx.scale(scale, scale)
|
||||
|
||||
function drawTopBorder() {
|
||||
ctx.beginPath()
|
||||
|
||||
@@ -302,7 +309,7 @@ Item {
|
||||
}
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, W, H_raw)
|
||||
ctx.clearRect(0, 0, Math.ceil(W), Math.ceil(H_raw))
|
||||
|
||||
ctx.save()
|
||||
if (isBottom) {
|
||||
@@ -319,9 +326,17 @@ Item {
|
||||
drawTopBorder()
|
||||
ctx.restore()
|
||||
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeStyle = Theme.secondary
|
||||
const key = SettingsData.dankBarBorderColor || "surfaceText"
|
||||
const base = (key === "surfaceText") ? Theme.surfaceText
|
||||
: (key === "primary") ? Theme.primary
|
||||
: Theme.secondary
|
||||
const color = Theme.withAlpha(base, SettingsData.dankBarBorderOpacity ?? 1.0)
|
||||
const thickness = Math.max(1, SettingsData.dankBarBorderThickness ?? 1)
|
||||
|
||||
ctx.globalCompositeOperation = "source-over"
|
||||
ctx.lineWidth = thickness
|
||||
ctx.strokeStyle = color
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Shapes
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Services.Notifications
|
||||
@@ -61,8 +62,17 @@ Item {
|
||||
property real wingtipsRadius: Theme.cornerRadius
|
||||
readonly property real _wingR: Math.max(0, wingtipsRadius)
|
||||
readonly property color _bgColor: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, topBarCore?.backgroundTransparency ?? SettingsData.dankBarTransparency)
|
||||
readonly property real _dpr: (barWindow.screen && barWindow.screen.devicePixelRatio) ? barWindow.screen.devicePixelRatio : 1
|
||||
function px(v) { return Math.round(v * _dpr) / _dpr }
|
||||
readonly property real _dpr: {
|
||||
if (CompositorService.isNiri && barWindow.screen) {
|
||||
const niriScale = NiriService.displayScales[barWindow.screen.name]
|
||||
if (niriScale !== undefined) return niriScale
|
||||
}
|
||||
if (CompositorService.isHyprland && barWindow.screen) {
|
||||
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === barWindow.screen.name)
|
||||
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
|
||||
}
|
||||
return (barWindow.screen?.devicePixelRatio) || 1
|
||||
}
|
||||
|
||||
property string screenName: modelData.name
|
||||
readonly property int notificationCount: NotificationService.notifications.length
|
||||
@@ -70,8 +80,8 @@ Item {
|
||||
readonly property real widgetThickness: Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
|
||||
|
||||
screen: modelData
|
||||
implicitHeight: !isVertical ? px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0)) : 0
|
||||
implicitWidth: isVertical ? px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0)) : 0
|
||||
implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0), _dpr) : 0
|
||||
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0), _dpr) : 0
|
||||
color: "transparent"
|
||||
|
||||
property var nativeInhibitor: null
|
||||
@@ -234,7 +244,7 @@ Item {
|
||||
Item {
|
||||
id: inputMask
|
||||
|
||||
readonly property int barThickness: px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing)
|
||||
readonly property int barThickness: Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr)
|
||||
|
||||
readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview
|
||||
readonly property bool effectiveVisible: SettingsData.dankBarVisible || inOverviewWithShow
|
||||
@@ -367,8 +377,8 @@ Item {
|
||||
id: topBarMouseArea
|
||||
y: !barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? parent.height - height : 0) : 0
|
||||
x: barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.width - width : 0) : 0
|
||||
height: !barWindow.isVertical ? px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
|
||||
width: barWindow.isVertical ? px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
|
||||
height: !barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr) : undefined
|
||||
width: barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr) : undefined
|
||||
anchors {
|
||||
left: !barWindow.isVertical ? parent.left : (SettingsData.dankBarPosition === SettingsData.Position.Left ? parent.left : undefined)
|
||||
right: !barWindow.isVertical ? parent.right : (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.right : undefined)
|
||||
@@ -387,8 +397,8 @@ Item {
|
||||
|
||||
transform: Translate {
|
||||
id: topBarSlide
|
||||
x: barWindow.isVertical ? px(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Right ? barWindow.implicitWidth : -barWindow.implicitWidth)) : 0
|
||||
y: !barWindow.isVertical ? px(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? barWindow.implicitHeight : -barWindow.implicitHeight)) : 0
|
||||
x: barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Right ? barWindow.implicitWidth : -barWindow.implicitWidth), barWindow._dpr) : 0
|
||||
y: !barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? barWindow.implicitHeight : -barWindow.implicitHeight), barWindow._dpr) : 0
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
@@ -408,10 +418,10 @@ Item {
|
||||
Item {
|
||||
id: barUnitInset
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: !barWindow.isVertical ? px(SettingsData.dankBarSpacing) : (axis.edge === "left" ? px(SettingsData.dankBarSpacing) : 0)
|
||||
anchors.rightMargin: !barWindow.isVertical ? px(SettingsData.dankBarSpacing) : (axis.edge === "right" ? px(SettingsData.dankBarSpacing) : 0)
|
||||
anchors.topMargin: barWindow.isVertical ? px(SettingsData.dankBarSpacing) : (axis.outerVisualEdge() === "bottom" ? 0 : px(SettingsData.dankBarSpacing))
|
||||
anchors.bottomMargin: barWindow.isVertical ? px(SettingsData.dankBarSpacing) : (axis.outerVisualEdge() === "bottom" ? px(SettingsData.dankBarSpacing) : 0)
|
||||
anchors.leftMargin: !barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.edge === "left" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
|
||||
anchors.rightMargin: !barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.edge === "right" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
|
||||
anchors.topMargin: barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.outerVisualEdge() === "bottom" ? 0 : Theme.px(SettingsData.dankBarSpacing, barWindow._dpr))
|
||||
anchors.bottomMargin: barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.outerVisualEdge() === "bottom" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
|
||||
|
||||
BarCanvas {
|
||||
id: barBackground
|
||||
|
||||
@@ -30,8 +30,15 @@ Item {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onPressed: function (mouse){
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
if (CompositorService.isNiri) {
|
||||
NiriService.toggleOverview()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
root.clicked();
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0);
|
||||
|
||||
@@ -22,7 +22,7 @@ Rectangle {
|
||||
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
|
||||
readonly property var sortedToplevels: {
|
||||
if (SettingsData.runningAppsCurrentWorkspace) {
|
||||
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, parentScreen.name);
|
||||
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, parentScreen?.name);
|
||||
}
|
||||
return CompositorService.sortedToplevels;
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ Rectangle {
|
||||
}
|
||||
if (CompositorService.isHyprland) {
|
||||
const baseList = getHyprlandWorkspaces()
|
||||
// Filter out special:scratch_term
|
||||
const filteredList = baseList.filter(ws => ws.name !== "special:scratch_term" && ws.id !== -98)
|
||||
return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList
|
||||
// Filter out special workspaces
|
||||
const filteredList = baseList.filter(ws => ws.id > -1)
|
||||
return SettingsData.showWorkspacePadding ? padWorkspaces(filteredList) : filteredList
|
||||
}
|
||||
return [1]
|
||||
}
|
||||
|
||||
@@ -491,7 +491,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color { ColorAnimation { duration: Anims.durShort } }
|
||||
Behavior on border.color { ColorAnimation { duration: Anims.durShort } }
|
||||
}
|
||||
}
|
||||
@@ -675,14 +674,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
@@ -858,14 +849,6 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,14 +1007,6 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,14 +60,6 @@ Variants {
|
||||
|
||||
|
||||
property bool contextMenuOpen: (dockVariants.contextMenu && dockVariants.contextMenu.visible && dockVariants.contextMenu.screen === modelData)
|
||||
property bool windowIsFullscreen: {
|
||||
if (!ToplevelManager.activeToplevel) {
|
||||
return false
|
||||
}
|
||||
const activeWindow = ToplevelManager.activeToplevel
|
||||
const fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"]
|
||||
return fullscreenApps.some(app => activeWindow.appId && activeWindow.appId.toLowerCase().includes(app))
|
||||
}
|
||||
property bool revealSticky: false
|
||||
|
||||
Timer {
|
||||
@@ -81,7 +73,7 @@ Variants {
|
||||
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
|
||||
return true
|
||||
}
|
||||
return (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky) && !windowIsFullscreen
|
||||
return !autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky
|
||||
}
|
||||
|
||||
onContextMenuOpenChanged: {
|
||||
|
||||
@@ -322,7 +322,21 @@ Item {
|
||||
}
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
if (appData && appData.appId) {
|
||||
if (appData && appData.type === "window") {
|
||||
const sortedToplevels = CompositorService.sortedToplevels
|
||||
for (var i = 0; i < sortedToplevels.length; i++) {
|
||||
const toplevel = sortedToplevels[i]
|
||||
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
|
||||
if (checkId === appData.uniqueId) {
|
||||
toplevel.close()
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (appData && appData.type === "grouped") {
|
||||
if (contextMenu) {
|
||||
contextMenu.showForButton(root, appData, 40, false, cachedDesktopEntry)
|
||||
}
|
||||
} else if (appData && appData.appId) {
|
||||
const desktopEntry = cachedDesktopEntry
|
||||
if (desktopEntry) {
|
||||
AppUsageHistoryData.addAppUsage({
|
||||
@@ -333,7 +347,7 @@ Item {
|
||||
"comment": desktopEntry.comment || ""
|
||||
})
|
||||
}
|
||||
SessionService.launchDesktopEntry(desktopEntry)
|
||||
SessionService.launchDesktopEntry(desktopEntry)
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (contextMenu && appData) {
|
||||
|
||||
@@ -34,30 +34,10 @@ Item {
|
||||
|
||||
signal launchRequested
|
||||
|
||||
property bool powerDialogVisible: false
|
||||
property string powerDialogTitle: ""
|
||||
property string powerDialogMessage: ""
|
||||
property string powerDialogConfirmText: ""
|
||||
property color powerDialogConfirmColor: Theme.primary
|
||||
property var powerDialogOnConfirm: function () {}
|
||||
|
||||
function pickRandomFact() {
|
||||
randomFact = Facts.getRandomFact()
|
||||
}
|
||||
|
||||
function showPowerDialog(title, message, confirmText, confirmColor, onConfirm) {
|
||||
powerDialogTitle = title
|
||||
powerDialogMessage = message
|
||||
powerDialogConfirmText = confirmText
|
||||
powerDialogConfirmColor = confirmColor
|
||||
powerDialogOnConfirm = onConfirm
|
||||
powerDialogVisible = true
|
||||
}
|
||||
|
||||
function hidePowerDialog() {
|
||||
powerDialogVisible = false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
pickRandomFact()
|
||||
WeatherService.addRef()
|
||||
@@ -373,11 +353,11 @@ Item {
|
||||
syncingFromState = true
|
||||
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput
|
||||
syncingFromState = false
|
||||
if (isPrimaryScreen)
|
||||
if (isPrimaryScreen && !powerMenu.isVisible)
|
||||
forceActiveFocus()
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (visible && isPrimaryScreen)
|
||||
if (visible && isPrimaryScreen && !powerMenu.isVisible)
|
||||
forceActiveFocus()
|
||||
}
|
||||
}
|
||||
@@ -1073,33 +1053,15 @@ Item {
|
||||
visible: root.randomFact !== ""
|
||||
}
|
||||
|
||||
Row {
|
||||
DankActionButton {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Theme.spacingXL
|
||||
spacing: Theme.spacingL
|
||||
visible: GreetdSettings.lockScreenShowPowerActions
|
||||
|
||||
DankActionButton {
|
||||
iconName: "power_settings_new"
|
||||
iconColor: Theme.error
|
||||
buttonSize: 40
|
||||
onClicked: {
|
||||
showPowerDialog("Power Off", "Power off this computer?", "Power Off", Theme.error, function () {
|
||||
SessionService.poweroff()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "refresh"
|
||||
buttonSize: 40
|
||||
onClicked: {
|
||||
showPowerDialog("Restart", "Restart this computer?", "Restart", Theme.primary, function () {
|
||||
SessionService.reboot()
|
||||
})
|
||||
}
|
||||
}
|
||||
iconName: "power_settings_new"
|
||||
iconColor: Theme.error
|
||||
buttonSize: 40
|
||||
onClicked: powerMenu.show()
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -1291,7 +1253,7 @@ Item {
|
||||
if (sessionCmd) {
|
||||
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex])
|
||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username)
|
||||
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"], true)
|
||||
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1314,90 +1276,12 @@ Item {
|
||||
onTriggered: GreeterState.pamState = ""
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.8)
|
||||
visible: powerDialogVisible
|
||||
z: 1000
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 320
|
||||
height: 180
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "power_settings_new"
|
||||
size: 32
|
||||
color: powerDialogConfirmColor
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: powerDialogMessage
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceVariant
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Cancel")
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: hidePowerDialog()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: powerDialogConfirmColor
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: powerDialogConfirmText
|
||||
color: Theme.primaryText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
hidePowerDialog()
|
||||
powerDialogOnConfirm()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LockPowerMenu {
|
||||
id: powerMenu
|
||||
showLogout: false
|
||||
onClosed: {
|
||||
if (isPrimaryScreen && inputField && inputField.forceActiveFocus) {
|
||||
Qt.callLater(() => inputField.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,40 +12,89 @@ A greeter for [greetd](https://github.com/kennylevinsen/greetd) that follows the
|
||||
|
||||
## Installation
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Arch linux users can install [greetd-dms-greeter-git](https://aur.archlinux.org/packages/greetd-dms-greeter-git) from the AUR.
|
||||
|
||||
```bash
|
||||
paru -S greetd-dms-greeter-git
|
||||
# Or with yay
|
||||
yay -S greetd-dms-greeter-git
|
||||
```
|
||||
|
||||
Then in your `/etc/greetd/config.toml` enable dms-greeter by replacing the greeter command with dms-greeter.
|
||||
|
||||
```bash
|
||||
# hyprland and sway are also supported as compositors
|
||||
command = "/usr/bin/dms-greeter --command niri"
|
||||
```
|
||||
|
||||
See `dms-greeter --help` for full options including custom compositor configurations.
|
||||
|
||||
Once installed, you should disable any existing greeter (such as gdm, sddm, lightdm), and you can configure the greeter to run at boot with:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable greetd
|
||||
```
|
||||
#### Syncing themes
|
||||
|
||||
To sync wallpapers, colors, and other settings from the logged in user, you can add your user to the `greeter` group and symlink the shell configurations.
|
||||
|
||||
```bash
|
||||
sudo usermod -aG greeter <username>
|
||||
# LOGOUT and LOGIN after adding user to group
|
||||
|
||||
|
||||
ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
|
||||
|
||||
ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
|
||||
|
||||
ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/colors.json
|
||||
```
|
||||
|
||||
### Automatic
|
||||
|
||||
The easiest thing is to run `dms greeter install` or `dms` for interactive installation.
|
||||
|
||||
### Manual
|
||||
|
||||
1. Install `greetd` (in most distro's standard repositories)
|
||||
2. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.conf` to `/etc/greetd`
|
||||
- niri if you want to run the greeter under niri, hypr if you want to run the greeter under Hyprland
|
||||
3. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/usr/local/bin/start-dms-greetd.sh`
|
||||
4. Edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` and replace `_DMS_PATH_` with the absolute path to dms, e.g. `/home/joecool/.config/quickshell/dms`
|
||||
5. Edit or create `/etc/greetd/config.toml`
|
||||
1. Install `greetd` (in most distro's standard repositories) and `quickshell`
|
||||
2. Clone the dms project to `/etc/xdg/quickshell/dms-greeter`
|
||||
```bash
|
||||
sudo git clone https://github.com/AvengeMedia/DankMaterialShell.git /etc/xdg/quickshell/dms-greeter
|
||||
```
|
||||
3. Copy `assets/dms-greeter` to `/usr/local/bin/dms-greeter`:
|
||||
```bash
|
||||
sudo cp assets/dms-greeter /usr/local/bin/dms-greeter
|
||||
sudo chmod +x /usr/local/bin/dms-greeter
|
||||
```
|
||||
4. Create greeter cache directory with proper permissions:
|
||||
```bash
|
||||
sudo mkdir -p /var/cache/dms-greeter
|
||||
sudo chown greeter:greeter /var/cache/dms-greeter
|
||||
sudo chmod 750 /var/cache/dms-greeter
|
||||
```
|
||||
6. Edit or create `/etc/greetd/config.toml`:
|
||||
```toml
|
||||
[terminal]
|
||||
# The VT to run the greeter on. Can be "next", "current" or a number
|
||||
# designating the VT.
|
||||
vt = 1
|
||||
|
||||
# The default session, also known as the greeter.
|
||||
[default_session]
|
||||
|
||||
# `agreety` is the bundled agetty/login-lookalike. You can replace `/bin/sh`
|
||||
# with whatever you want started, such as `sway`.
|
||||
|
||||
# The user to run the command as. The privileges this user must have depends
|
||||
# on the greeter. A graphical greeter may for example require the user to be
|
||||
# in the `video` group.
|
||||
user = "greeter"
|
||||
|
||||
command = "/usr/local/bin/start-dms-greetd.sh"
|
||||
# Change compositor to sway or hyprland if preferred
|
||||
command = "/usr/local/bin/dms-greeter --command niri"
|
||||
```
|
||||
|
||||
Enable the greeter with `sudo systemctl enable greetd`
|
||||
|
||||
#### Legacy installation (deprecated)
|
||||
|
||||
If you prefer the old method with separate shell scripts and config files:
|
||||
1. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.conf` to `/etc/greetd`
|
||||
2. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/usr/local/bin/start-dms-greetd.sh`
|
||||
3. Edit the config file and replace `_DMS_PATH_` with your DMS installation path
|
||||
4. Configure greetd to use `/usr/local/bin/start-dms-greetd.sh`
|
||||
|
||||
### NixOS
|
||||
|
||||
To install the greeter on NixOS add the repo to your flake inputs as described in the readme. Then somewhere in your NixOS config add this to imports:
|
||||
@@ -66,7 +115,30 @@ programs.dankMaterialShell.greeter = {
|
||||
|
||||
## Usage
|
||||
|
||||
To run dms in greeter mode you just need to set `DMS_RUN_GREETER=1` in the environment.
|
||||
### Using dms-greeter wrapper (recommended)
|
||||
|
||||
The `dms-greeter` wrapper simplifies running the greeter with any compositor:
|
||||
|
||||
```bash
|
||||
dms-greeter --command niri
|
||||
dms-greeter --command hyprland
|
||||
dms-greeter --command sway
|
||||
dms-greeter --command niri -C /path/to/custom-niri.kdl
|
||||
```
|
||||
|
||||
Configure greetd to use it in `/etc/greetd/config.toml`:
|
||||
```toml
|
||||
[terminal]
|
||||
vt = 1
|
||||
|
||||
[default_session]
|
||||
user = "greeter"
|
||||
command = "/usr/local/bin/dms-greeter --command niri"
|
||||
```
|
||||
|
||||
### Manual usage
|
||||
|
||||
To run dms in greeter mode you can also manually set environment variables:
|
||||
|
||||
```bash
|
||||
DMS_RUN_GREETER=1 qs -p /path/to/dms
|
||||
@@ -86,15 +158,17 @@ Wallpapers and themes and weather and clock formats and things are a TODO on the
|
||||
|
||||
You can synchronize those configurations with a specific user if you want greeter settings to always mirror the shell.
|
||||
|
||||
The greeter uses the `dms-greeter` group for file access permissions, so ensure your user and the greeter user are both members of this group.
|
||||
|
||||
```bash
|
||||
# For core settings (theme, clock formats, etc)
|
||||
sudo ln -sf ~/.config/DankMaterialShell/settings.json /etc/greetd/.dms/settings.json
|
||||
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
|
||||
# For state (mainly you would configure wallpaper in this file)
|
||||
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /etc/greetd/.dms/session.json
|
||||
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
|
||||
# For wallpaper based theming
|
||||
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /etc/greetd/.dms/dms-colors.json
|
||||
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/dms-colors.json
|
||||
```
|
||||
|
||||
You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable, the default is `/etc/greetd/.dms`
|
||||
You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable or the `--cache-dir` flag when using `dms-greeter`. The default is `/var/cache/dms-greeter`.
|
||||
|
||||
It should be writable by the greeter user.
|
||||
The cache directory should be owned by `greeter:greeter` with `770` permissions.
|
||||
174
Modules/Greetd/assets/dms-greeter
Executable file
174
Modules/Greetd/assets/dms-greeter
Executable file
@@ -0,0 +1,174 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
COMPOSITOR=""
|
||||
COMPOSITOR_CONFIG=""
|
||||
DMS_PATH="dms-greeter"
|
||||
CACHE_DIR="/var/cache/dms-greeter"
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
dms-greeter - DankMaterialShell greeter launcher
|
||||
|
||||
Usage: dms-greeter --command COMPOSITOR [OPTIONS]
|
||||
|
||||
Required:
|
||||
--command COMPOSITOR Compositor to use (niri, hyprland, or sway)
|
||||
|
||||
Options:
|
||||
-C, --config PATH Custom compositor config file
|
||||
-p, --path PATH DMS path (config name or absolute path)
|
||||
(default: dms-greeter)
|
||||
--cache-dir PATH Cache directory for greeter data
|
||||
(default: /var/cache/dms-greeter)
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
dms-greeter --command niri
|
||||
dms-greeter --command hyprland -C /etc/greetd/custom-hypr.conf
|
||||
dms-greeter --command sway -p /home/user/.config/quickshell/custom-dms
|
||||
dms-greeter --command niri --cache-dir /tmp/dmsgreeter
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--command)
|
||||
COMPOSITOR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-C|--config)
|
||||
COMPOSITOR_CONFIG="$2"
|
||||
shift 2
|
||||
;;
|
||||
-p|--path)
|
||||
DMS_PATH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--cache-dir)
|
||||
CACHE_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$COMPOSITOR" ]]; then
|
||||
echo "Error: --command COMPOSITOR is required" >&2
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export XDG_SESSION_TYPE=wayland
|
||||
export QT_QPA_PLATFORM=wayland
|
||||
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
||||
export EGL_PLATFORM=gbm
|
||||
export DMS_RUN_GREETER=1
|
||||
export DMS_GREET_CFG_DIR="$CACHE_DIR"
|
||||
|
||||
mkdir -p "$CACHE_DIR"
|
||||
|
||||
QS_CMD="qs"
|
||||
if [[ "$DMS_PATH" == /* ]]; then
|
||||
QS_CMD="qs -p $DMS_PATH"
|
||||
else
|
||||
QS_CMD="qs -c $DMS_PATH"
|
||||
fi
|
||||
|
||||
case "$COMPOSITOR" in
|
||||
niri)
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << NIRI_EOF
|
||||
hotkey-overlay {
|
||||
skip-at-startup
|
||||
}
|
||||
|
||||
environment {
|
||||
DMS_RUN_GREETER "1"
|
||||
}
|
||||
|
||||
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
|
||||
|
||||
debug {
|
||||
keep-max-bpc-unchanged
|
||||
}
|
||||
|
||||
gestures {
|
||||
hot-corners {
|
||||
off
|
||||
}
|
||||
}
|
||||
|
||||
layout {
|
||||
background-color "#000000"
|
||||
}
|
||||
NIRI_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << NIRI_EOF
|
||||
|
||||
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
|
||||
NIRI_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec niri -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
hyprland)
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||
env = DMS_RUN_GREETER,1
|
||||
|
||||
exec = sh -c "$QS_CMD; hyprctl dispatch exit"
|
||||
HYPRLAND_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||
|
||||
exec = sh -c "$QS_CMD; hyprctl dispatch exit"
|
||||
HYPRLAND_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec Hyprland -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
sway)
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << SWAY_EOF
|
||||
exec "$QS_CMD; swaymsg exit"
|
||||
SWAY_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << SWAY_EOF
|
||||
|
||||
exec "$QS_CMD; swaymsg exit"
|
||||
SWAY_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec sway -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Error: Unsupported compositor: $COMPOSITOR" >&2
|
||||
echo "Supported compositors: niri, hyprland, sway" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
@@ -5,84 +7,55 @@ import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
function activate() {
|
||||
loader.activeAsync = true
|
||||
}
|
||||
Scope {
|
||||
property string sharedPasswordBuffer: ""
|
||||
property bool shouldLock: false
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SessionService.loginctlAvailable || SessionService.sessionPath) {
|
||||
if (SessionService.locked || SessionService.lockedHint) {
|
||||
console.log("Lock: Session locked on startup")
|
||||
loader.activeAsync = true
|
||||
}
|
||||
}
|
||||
IdleService.lockComponent = this
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: IdleService
|
||||
function onLockRequested() {
|
||||
console.log("Lock: Received lock request from IdleService")
|
||||
loader.activeAsync = true
|
||||
}
|
||||
function activate() {
|
||||
shouldLock = true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionService
|
||||
|
||||
function onSessionLocked() {
|
||||
console.log("Lock: Lock signal received -> show lock")
|
||||
loader.activeAsync = true
|
||||
shouldLock = true
|
||||
}
|
||||
|
||||
function onSessionUnlocked() {
|
||||
console.log("Lock: Unlock signal received -> hide lock")
|
||||
loader.active = false
|
||||
}
|
||||
|
||||
function onLoginctlStateChanged() {
|
||||
if (SessionService.lockedHint && !loader.active) {
|
||||
console.log("Lock: LockedHint=true -> show lock")
|
||||
loader.activeAsync = true
|
||||
} else if (!SessionService.locked && !SessionService.lockedHint && loader.active) {
|
||||
console.log("Lock: LockedHint=false -> hide lock")
|
||||
loader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
function onPrepareForSleep() {
|
||||
if (SessionService.preparingForSleep && SessionData.lockBeforeSuspend) {
|
||||
console.log("Lock: PrepareForSleep -> lock before suspend")
|
||||
loader.activeAsync = true
|
||||
}
|
||||
shouldLock = false
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: loader
|
||||
Connections {
|
||||
target: IdleService
|
||||
|
||||
WlSessionLock {
|
||||
id: sessionLock
|
||||
function onLockRequested() {
|
||||
shouldLock = true
|
||||
}
|
||||
}
|
||||
|
||||
property bool unlocked: false
|
||||
property string sharedPasswordBuffer: ""
|
||||
WlSessionLock {
|
||||
id: sessionLock
|
||||
|
||||
locked: true
|
||||
locked: shouldLock
|
||||
|
||||
onLockedChanged: {
|
||||
if (!locked) {
|
||||
loader.active = false
|
||||
}
|
||||
}
|
||||
WlSessionLockSurface {
|
||||
color: "transparent"
|
||||
|
||||
LockSurface {
|
||||
id: lockSurface
|
||||
anchors.fill: parent
|
||||
lock: sessionLock
|
||||
sharedPasswordBuffer: sessionLock.sharedPasswordBuffer
|
||||
sharedPasswordBuffer: sharedPasswordBuffer
|
||||
onUnlockRequested: {
|
||||
shouldLock = false
|
||||
}
|
||||
onPasswordChanged: newPassword => {
|
||||
sessionLock.sharedPasswordBuffer = newPassword
|
||||
sharedPasswordBuffer = newPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,17 +69,15 @@ Item {
|
||||
target: "lock"
|
||||
|
||||
function lock() {
|
||||
console.log("Lock screen requested via IPC")
|
||||
loader.activeAsync = true
|
||||
shouldLock = true
|
||||
}
|
||||
|
||||
function demo() {
|
||||
console.log("Lock screen DEMO mode requested via IPC")
|
||||
demoWindow.showDemo()
|
||||
}
|
||||
|
||||
function isLocked(): bool {
|
||||
return SessionService.locked || loader.active
|
||||
return sessionLock.locked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
459
Modules/Lock/LockPowerMenu.qml
Normal file
459
Modules/Lock/LockPowerMenu.qml
Normal file
@@ -0,0 +1,459 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isVisible: false
|
||||
property bool showLogout: true
|
||||
property int selectedIndex: 0
|
||||
property int optionCount: {
|
||||
let count = 0
|
||||
if (showLogout) count++
|
||||
count++
|
||||
if (SessionService.hibernateSupported) count++
|
||||
count += 2
|
||||
return count
|
||||
}
|
||||
|
||||
signal closed()
|
||||
|
||||
function show() {
|
||||
isVisible = true
|
||||
selectedIndex = 0
|
||||
Qt.callLater(() => {
|
||||
if (powerMenuFocusScope && powerMenuFocusScope.forceActiveFocus) {
|
||||
powerMenuFocusScope.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function hide() {
|
||||
isVisible = false
|
||||
closed()
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
visible: isVisible
|
||||
z: 1000
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: powerMenuFocusScope
|
||||
anchors.fill: parent
|
||||
focus: root.isVisible
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
Qt.callLater(() => forceActiveFocus())
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
root.hide()
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Up:
|
||||
case Qt.Key_Backtab:
|
||||
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount
|
||||
event.accepted = true
|
||||
break
|
||||
case Qt.Key_Down:
|
||||
case Qt.Key_Tab:
|
||||
selectedIndex = (selectedIndex + 1) % optionCount
|
||||
event.accepted = true
|
||||
break
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
const actions = []
|
||||
if (showLogout) actions.push("logout")
|
||||
actions.push("suspend")
|
||||
if (SessionService.hibernateSupported) actions.push("hibernate")
|
||||
actions.push("reboot", "poweroff")
|
||||
if (selectedIndex < actions.length) {
|
||||
const action = actions[selectedIndex]
|
||||
hide()
|
||||
switch (action) {
|
||||
case "logout":
|
||||
SessionService.logout()
|
||||
break
|
||||
case "suspend":
|
||||
SessionService.suspend()
|
||||
break
|
||||
case "hibernate":
|
||||
SessionService.hibernate()
|
||||
break
|
||||
case "reboot":
|
||||
SessionService.reboot()
|
||||
break
|
||||
case "poweroff":
|
||||
SessionService.poweroff()
|
||||
break
|
||||
}
|
||||
}
|
||||
event.accepted = true
|
||||
break
|
||||
case Qt.Key_N:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
selectedIndex = (selectedIndex + 1) % optionCount
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_P:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_J:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
selectedIndex = (selectedIndex + 1) % optionCount
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_K:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 320
|
||||
implicitHeight: mainColumn.implicitHeight + Theme.spacingL * 2
|
||||
height: implicitHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Power Options")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 150
|
||||
height: 1
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
visible: showLogout
|
||||
color: {
|
||||
if (selectedIndex === 0) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
} else if (logoutArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
} else {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
}
|
||||
}
|
||||
border.color: selectedIndex === 0 ? Theme.primary : "transparent"
|
||||
border.width: selectedIndex === 0 ? 1 : 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "logout"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Log Out")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: logoutArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.hide()
|
||||
SessionService.logout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const suspendIdx = showLogout ? 1 : 0
|
||||
if (selectedIndex === suspendIdx) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
} else if (suspendArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
} else {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
}
|
||||
}
|
||||
border.color: selectedIndex === (showLogout ? 1 : 0) ? Theme.primary : "transparent"
|
||||
border.width: selectedIndex === (showLogout ? 1 : 0) ? 1 : 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "bedtime"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Suspend")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: suspendArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.hide()
|
||||
SessionService.suspend()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const hibernateIdx = showLogout ? 2 : 1
|
||||
if (selectedIndex === hibernateIdx) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
} else if (hibernateArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
} else {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
}
|
||||
}
|
||||
border.color: selectedIndex === (showLogout ? 2 : 1) ? Theme.primary : "transparent"
|
||||
border.width: selectedIndex === (showLogout ? 2 : 1) ? 1 : 0
|
||||
visible: SessionService.hibernateSupported
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "ac_unit"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Hibernate")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hibernateArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.hide()
|
||||
SessionService.hibernate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
let rebootIdx = showLogout ? 3 : 2
|
||||
if (!SessionService.hibernateSupported) rebootIdx--
|
||||
if (selectedIndex === rebootIdx) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
} else if (rebootArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
} else {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
}
|
||||
}
|
||||
border.color: {
|
||||
let rebootIdx = showLogout ? 3 : 2
|
||||
if (!SessionService.hibernateSupported) rebootIdx--
|
||||
return selectedIndex === rebootIdx ? Theme.primary : "transparent"
|
||||
}
|
||||
border.width: {
|
||||
let rebootIdx = showLogout ? 3 : 2
|
||||
if (!SessionService.hibernateSupported) rebootIdx--
|
||||
return selectedIndex === rebootIdx ? 1 : 0
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "restart_alt"
|
||||
size: Theme.iconSize
|
||||
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Reboot")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rebootArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.hide()
|
||||
SessionService.reboot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
let powerOffIdx = showLogout ? 4 : 3
|
||||
if (!SessionService.hibernateSupported) powerOffIdx--
|
||||
if (selectedIndex === powerOffIdx) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
} else if (powerOffArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
} else {
|
||||
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
}
|
||||
}
|
||||
border.color: {
|
||||
let powerOffIdx = showLogout ? 4 : 3
|
||||
if (!SessionService.hibernateSupported) powerOffIdx--
|
||||
return selectedIndex === powerOffIdx ? Theme.primary : "transparent"
|
||||
}
|
||||
border.width: {
|
||||
let powerOffIdx = showLogout ? 4 : 3
|
||||
if (!SessionService.hibernateSupported) powerOffIdx--
|
||||
return selectedIndex === powerOffIdx ? 1 : 0
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "power_settings_new"
|
||||
size: Theme.iconSize
|
||||
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Power Off")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: powerOffArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.hide()
|
||||
SessionService.poweroff()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
height: Theme.spacingS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
@@ -26,27 +27,6 @@ Item {
|
||||
|
||||
signal unlockRequested
|
||||
|
||||
// Internal power dialog state
|
||||
property bool powerDialogVisible: false
|
||||
property string powerDialogTitle: ""
|
||||
property string powerDialogMessage: ""
|
||||
property string powerDialogConfirmText: ""
|
||||
property color powerDialogConfirmColor: Theme.primary
|
||||
property var powerDialogOnConfirm: function () {}
|
||||
|
||||
function showPowerDialog(title, message, confirmText, confirmColor, onConfirm) {
|
||||
powerDialogTitle = title
|
||||
powerDialogMessage = message
|
||||
powerDialogConfirmText = confirmText
|
||||
powerDialogConfirmColor = confirmColor
|
||||
powerDialogOnConfirm = onConfirm
|
||||
powerDialogVisible = true
|
||||
}
|
||||
|
||||
function hidePowerDialog() {
|
||||
powerDialogVisible = false
|
||||
}
|
||||
|
||||
function pickRandomFact() {
|
||||
randomFact = Facts.getRandomFact()
|
||||
}
|
||||
@@ -63,6 +43,16 @@ Item {
|
||||
updateHyprlandLayout()
|
||||
hyprlandLayoutUpdateTimer.start()
|
||||
}
|
||||
|
||||
if (SessionService.loginctlAvailable && DMSService.apiVersion >= 2) {
|
||||
DMSService.sendRequest("loginctl.lockerReady", null, response => {
|
||||
if (response.error) {
|
||||
console.warn("LockScreenContent: Failed to signal locker ready:", response.error)
|
||||
} else {
|
||||
console.log("LockScreenContent: Locker ready signaled, inhibitor released")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
onDemoModeChanged: {
|
||||
if (demoMode) {
|
||||
@@ -336,7 +326,7 @@ Item {
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (!activeFocus && !demoMode && visible && passwordField) {
|
||||
if (!activeFocus && !demoMode && visible && passwordField && !powerMenu.isVisible) {
|
||||
Qt.callLater(() => {
|
||||
if (passwordField && passwordField.forceActiveFocus) {
|
||||
passwordField.forceActiveFocus()
|
||||
@@ -346,7 +336,7 @@ Item {
|
||||
}
|
||||
|
||||
onEnabledChanged: {
|
||||
if (enabled && !demoMode && visible && passwordField) {
|
||||
if (enabled && !demoMode && visible && passwordField && !powerMenu.isVisible) {
|
||||
Qt.callLater(() => {
|
||||
if (passwordField && passwordField.forceActiveFocus) {
|
||||
passwordField.forceActiveFocus()
|
||||
@@ -1070,53 +1060,19 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
DankActionButton {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Theme.spacingXL
|
||||
spacing: Theme.spacingL
|
||||
visible: SettingsData.lockScreenShowPowerActions
|
||||
|
||||
DankActionButton {
|
||||
iconName: "power_settings_new"
|
||||
iconColor: Theme.error
|
||||
buttonSize: 40
|
||||
onClicked: {
|
||||
if (demoMode) {
|
||||
console.log("Demo: Power")
|
||||
} else {
|
||||
showPowerDialog("Power Off", "Power off this computer?", "Power Off", Theme.error, function () {
|
||||
SessionService.poweroff()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "refresh"
|
||||
buttonSize: 40
|
||||
onClicked: {
|
||||
if (demoMode) {
|
||||
console.log("Demo: Reboot")
|
||||
} else {
|
||||
showPowerDialog("Restart", "Restart this computer?", "Restart", Theme.primary, function () {
|
||||
SessionService.reboot()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "logout"
|
||||
buttonSize: 40
|
||||
onClicked: {
|
||||
if (demoMode) {
|
||||
console.log("Demo: Logout")
|
||||
} else {
|
||||
showPowerDialog("Log Out", "End this session?", "Log Out", Theme.primary, function () {
|
||||
SessionService.logout()
|
||||
})
|
||||
}
|
||||
iconName: "power_settings_new"
|
||||
iconColor: Theme.error
|
||||
buttonSize: 40
|
||||
onClicked: {
|
||||
if (demoMode) {
|
||||
console.log("Demo: Power Menu")
|
||||
} else {
|
||||
powerMenu.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1197,91 +1153,12 @@ Item {
|
||||
onClicked: root.unlockRequested()
|
||||
}
|
||||
|
||||
// Internal power dialog
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.8)
|
||||
visible: powerDialogVisible
|
||||
z: 1000
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 320
|
||||
height: 180
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "power_settings_new"
|
||||
size: 32
|
||||
color: powerDialogConfirmColor
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: powerDialogMessage
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceVariant
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Cancel")
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: hidePowerDialog()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: powerDialogConfirmColor
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: powerDialogConfirmText
|
||||
color: Theme.primaryText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
hidePowerDialog()
|
||||
powerDialogOnConfirm()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LockPowerMenu {
|
||||
id: powerMenu
|
||||
showLogout: true
|
||||
onClosed: {
|
||||
if (!demoMode && passwordField && passwordField.forceActiveFocus) {
|
||||
Qt.callLater(() => passwordField.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
|
||||
WlSessionLockSurface {
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property WlSessionLock lock
|
||||
required property string sharedPasswordBuffer
|
||||
|
||||
signal passwordChanged(string newPassword)
|
||||
|
||||
readonly property bool locked: lock && !lock.locked
|
||||
|
||||
function unlock(): void {
|
||||
lock.locked = false
|
||||
}
|
||||
signal unlockRequested()
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Loader {
|
||||
LockScreenContent {
|
||||
anchors.fill: parent
|
||||
sourceComponent: LockScreenContent {
|
||||
demoMode: false
|
||||
passwordBuffer: root.sharedPasswordBuffer
|
||||
screenName: root.screen?.name ?? ""
|
||||
onUnlockRequested: root.unlock()
|
||||
onPasswordBufferChanged: {
|
||||
if (root.sharedPasswordBuffer !== passwordBuffer) {
|
||||
root.passwordChanged(passwordBuffer)
|
||||
}
|
||||
demoMode: false
|
||||
passwordBuffer: root.sharedPasswordBuffer
|
||||
screenName: ""
|
||||
onUnlockRequested: root.unlockRequested()
|
||||
onPasswordBufferChanged: {
|
||||
if (root.sharedPasswordBuffer !== passwordBuffer) {
|
||||
root.passwordChanged(passwordBuffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,24 @@ Item {
|
||||
|
||||
required property string pluginId
|
||||
property var pluginService: null
|
||||
default property alias content: settingsColumn.children
|
||||
default property list<QtObject> content
|
||||
|
||||
signal settingChanged()
|
||||
|
||||
property var variants: []
|
||||
property alias variantsModel: variantsListModel
|
||||
|
||||
implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight
|
||||
height: implicitHeight
|
||||
|
||||
readonly property bool hasPermission: pluginService && pluginService.hasPermission ? pluginService.hasPermission(pluginId, "settings_write") : true
|
||||
readonly property bool hasPermission: {
|
||||
if (!pluginService || !pluginId) return true
|
||||
const allPlugins = pluginService.availablePlugins
|
||||
const plugin = allPlugins[pluginId]
|
||||
if (!plugin) return true
|
||||
const permissions = Array.isArray(plugin.permissions) ? plugin.permissions : []
|
||||
return permissions.indexOf("settings_write") !== -1
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadVariants()
|
||||
@@ -26,8 +34,8 @@ Item {
|
||||
onPluginServiceChanged: {
|
||||
if (pluginService) {
|
||||
loadVariants()
|
||||
for (let i = 0; i < settingsColumn.children.length; i++) {
|
||||
const child = settingsColumn.children[i]
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const child = content[i]
|
||||
if (child.loadValue) {
|
||||
child.loadValue()
|
||||
}
|
||||
@@ -35,6 +43,15 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
onContentChanged: {
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const item = content[i]
|
||||
if (item instanceof Item) {
|
||||
item.parent = settingsColumn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: pluginService
|
||||
function onPluginDataChanged(changedPluginId) {
|
||||
@@ -50,6 +67,22 @@ Item {
|
||||
return
|
||||
}
|
||||
variants = pluginService.getPluginVariants(pluginId)
|
||||
syncVariantsToModel()
|
||||
}
|
||||
|
||||
function syncVariantsToModel() {
|
||||
variantsListModel.clear()
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
variantsListModel.append(variants[i])
|
||||
}
|
||||
}
|
||||
|
||||
onVariantsChanged: {
|
||||
syncVariantsToModel()
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: variantsListModel
|
||||
}
|
||||
|
||||
function createVariant(variantName, variantConfig) {
|
||||
@@ -94,11 +127,47 @@ Item {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
function findFlickable(item) {
|
||||
var current = item?.parent
|
||||
while (current) {
|
||||
if (current.contentY !== undefined && current.contentHeight !== undefined) {
|
||||
return current
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function ensureItemVisible(item) {
|
||||
if (!item) return
|
||||
|
||||
var flickable = findFlickable(root)
|
||||
if (!flickable) return
|
||||
|
||||
var itemGlobalY = item.mapToItem(null, 0, 0).y
|
||||
var itemHeight = item.height
|
||||
var flickableGlobalY = flickable.mapToItem(null, 0, 0).y
|
||||
var viewportHeight = flickable.height
|
||||
|
||||
var itemRelativeY = itemGlobalY - flickableGlobalY
|
||||
var viewportTop = 0
|
||||
var viewportBottom = viewportHeight
|
||||
|
||||
if (itemRelativeY < viewportTop) {
|
||||
flickable.contentY = Math.max(0, flickable.contentY - (viewportTop - itemRelativeY) - Theme.spacingL)
|
||||
} else if (itemRelativeY + itemHeight > viewportBottom) {
|
||||
flickable.contentY = Math.min(
|
||||
flickable.contentHeight - viewportHeight,
|
||||
flickable.contentY + (itemRelativeY + itemHeight - viewportBottom) + Theme.spacingL
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: errorText
|
||||
visible: pluginService && !root.hasPermission
|
||||
anchors.fill: parent
|
||||
text: qsTr("This plugin does not have 'settings_write' permission.\n\nAdd \"permissions\": [\"settings_read\", \"settings_write\"] to plugin.json")
|
||||
text: I18n.tr("This plugin does not have 'settings_write' permission.\n\nAdd \"permissions\": [\"settings_read\", \"settings_write\"] to plugin.json")
|
||||
color: Theme.error
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
@@ -258,7 +258,7 @@ Item {
|
||||
|
||||
StyledText {
|
||||
text: `dms is a highly customizable, modern desktop shell with a <a href="https://m3.material.io/" style="text-decoration:none; color:${Theme.primary};">material 3 inspired</a> design.
|
||||
<br /><br/>It is built on top of <a href="https://quickshell.org" style="text-decoration:none; color:${Theme.primary};">Quickshell</a>, a QT6 framework for building desktop shells.
|
||||
<br /><br/>It is built with <a href="https://quickshell.org" style="text-decoration:none; color:${Theme.primary};">Quickshell</a>, a QT6 framework for building desktop shells, and <a href="https://go.dev" style="text-decoration:none; color:${Theme.primary};">Go</a>, a statically typed, compiled programming language.
|
||||
`
|
||||
textFormat: Text.RichText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -352,7 +352,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("QML (Qt Modeling Language)")
|
||||
text: I18n.tr("QML, JavaScript, Go")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
@@ -942,14 +942,51 @@ Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Edge Spacing (0 = edge-to-edge)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Edge Spacing (0 = edge-to-edge)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - edgeSpacingText.implicitWidth - resetEdgeSpacingBtn.width - Theme.spacingS - Theme.spacingM
|
||||
height: 1
|
||||
|
||||
StyledText {
|
||||
id: edgeSpacingText
|
||||
visible: false
|
||||
text: I18n.tr("Edge Spacing (0 = edge-to-edge)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: resetEdgeSpacingBtn
|
||||
buttonSize: 20
|
||||
iconName: "refresh"
|
||||
iconSize: 12
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
SettingsData.setDankBarSpacing(4)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Theme.spacingS
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: edgeSpacingSlider
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: SettingsData.dankBarSpacing
|
||||
@@ -963,6 +1000,13 @@ Item {
|
||||
SettingsData.setDankBarSpacing(
|
||||
newValue)
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: edgeSpacingSlider
|
||||
property: "value"
|
||||
value: SettingsData.dankBarSpacing
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,19 +1014,56 @@ Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Exclusive Zone Offset")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Exclusive Zone Offset")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - exclusiveZoneText.implicitWidth - resetExclusiveZoneBtn.width - Theme.spacingS - Theme.spacingM
|
||||
height: 1
|
||||
|
||||
StyledText {
|
||||
id: exclusiveZoneText
|
||||
visible: false
|
||||
text: I18n.tr("Exclusive Zone Offset")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: resetExclusiveZoneBtn
|
||||
buttonSize: 20
|
||||
iconName: "refresh"
|
||||
iconSize: 12
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
SettingsData.setDankBarBottomGap(0)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Theme.spacingS
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: exclusiveZoneSlider
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: SettingsData.dankBarBottomGap
|
||||
minimum: -100
|
||||
maximum: 100
|
||||
minimum: -50
|
||||
maximum: 50
|
||||
unit: ""
|
||||
showValue: true
|
||||
wheelEnabled: false
|
||||
@@ -991,6 +1072,13 @@ Item {
|
||||
SettingsData.setDankBarBottomGap(
|
||||
newValue)
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: exclusiveZoneSlider
|
||||
property: "value"
|
||||
value: SettingsData.dankBarBottomGap
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,14 +1086,51 @@ Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Size")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Size")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - sizeText.implicitWidth - resetSizeBtn.width - Theme.spacingS - Theme.spacingM
|
||||
height: 1
|
||||
|
||||
StyledText {
|
||||
id: sizeText
|
||||
visible: false
|
||||
text: I18n.tr("Size")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: resetSizeBtn
|
||||
buttonSize: 20
|
||||
iconName: "refresh"
|
||||
iconSize: 12
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
SettingsData.setDankBarInnerPadding(4)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Theme.spacingS
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: sizeSlider
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: SettingsData.dankBarInnerPadding
|
||||
@@ -1019,9 +1144,115 @@ Item {
|
||||
SettingsData.setDankBarInnerPadding(
|
||||
newValue)
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: sizeSlider
|
||||
property: "value"
|
||||
value: SettingsData.dankBarInnerPadding
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Auto Popup Gaps")
|
||||
description: I18n.tr("Automatically calculate popup distance from bar edge.")
|
||||
checked: SettingsData.popupGapsAuto
|
||||
onToggled: checked => {
|
||||
SettingsData.setPopupGapsAuto(checked)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
leftPadding: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
visible: !SettingsData.popupGapsAuto
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - parent.leftPadding
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.2
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - parent.leftPadding
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Manual Gap Size")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - manualGapSizeText.implicitWidth - resetManualGapSizeBtn.width - Theme.spacingS - Theme.spacingM
|
||||
height: 1
|
||||
|
||||
StyledText {
|
||||
id: manualGapSizeText
|
||||
visible: false
|
||||
text: I18n.tr("Manual Gap Size")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: resetManualGapSizeBtn
|
||||
buttonSize: 20
|
||||
iconName: "refresh"
|
||||
iconSize: 12
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
SettingsData.setPopupGapsManual(4)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Theme.spacingS
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: popupGapsManualSlider
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: SettingsData.popupGapsManual
|
||||
minimum: 0
|
||||
maximum: 50
|
||||
unit: ""
|
||||
showValue: true
|
||||
wheelEnabled: false
|
||||
thumbOutlineColor: Theme.surfaceContainerHigh
|
||||
onSliderValueChanged: newValue => {
|
||||
SettingsData.setPopupGapsManual(newValue)
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: popupGapsManualSlider
|
||||
property: "value"
|
||||
value: SettingsData.popupGapsManual
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
@@ -1056,14 +1287,227 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
Column {
|
||||
width: parent.width
|
||||
text: I18n.tr("Border")
|
||||
description: "Add a 1px border to the bar. Smart edge detection only shows border on exposed sides."
|
||||
checked: SettingsData.dankBarBorderEnabled
|
||||
onToggled: checked => {
|
||||
SettingsData.setDankBarBorderEnabled(checked)
|
||||
}
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Border")
|
||||
description: "Add a 1px border to the bar. Smart edge detection only shows border on exposed sides."
|
||||
checked: SettingsData.dankBarBorderEnabled
|
||||
onToggled: checked => {
|
||||
SettingsData.setDankBarBorderEnabled(checked)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
leftPadding: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
visible: SettingsData.dankBarBorderEnabled
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - parent.leftPadding
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.2
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width - parent.leftPadding
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
width: parent.width - borderColorGroup.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Border Color")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Choose the border accent color")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
id: borderColorGroup
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
model: ["Surface", "Secondary", "Primary"]
|
||||
currentIndex: {
|
||||
const colorOption = SettingsData.dankBarBorderColor || "surfaceText"
|
||||
switch (colorOption) {
|
||||
case "surfaceText": return 0
|
||||
case "secondary": return 1
|
||||
case "primary": return 2
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (selected) {
|
||||
let newColor = "surfaceText"
|
||||
switch (index) {
|
||||
case 0: newColor = "surfaceText"; break
|
||||
case 1: newColor = "secondary"; break
|
||||
case 2: newColor = "primary"; break
|
||||
}
|
||||
if (SettingsData.dankBarBorderColor !== newColor) {
|
||||
SettingsData.dankBarBorderColor = newColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - parent.leftPadding
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Border Opacity")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - borderOpacityText.implicitWidth - resetBorderOpacityBtn.width - Theme.spacingS - Theme.spacingM
|
||||
height: 1
|
||||
|
||||
StyledText {
|
||||
id: borderOpacityText
|
||||
visible: false
|
||||
text: I18n.tr("Border Opacity")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: resetBorderOpacityBtn
|
||||
buttonSize: 20
|
||||
iconName: "refresh"
|
||||
iconSize: 12
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
SettingsData.dankBarBorderOpacity = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Theme.spacingS
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: borderOpacitySlider
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: (SettingsData.dankBarBorderOpacity ?? 1.0) * 100
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
unit: "%"
|
||||
showValue: true
|
||||
wheelEnabled: false
|
||||
thumbOutlineColor: Theme.surfaceContainerHigh
|
||||
onSliderValueChanged: newValue => {
|
||||
SettingsData.dankBarBorderOpacity = newValue / 100
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: borderOpacitySlider
|
||||
property: "value"
|
||||
value: (SettingsData.dankBarBorderOpacity ?? 1.0) * 100
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - parent.leftPadding
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Border Thickness")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - borderThicknessText.implicitWidth - resetBorderThicknessBtn.width - Theme.spacingS - Theme.spacingM
|
||||
height: 1
|
||||
|
||||
StyledText {
|
||||
id: borderThicknessText
|
||||
visible: false
|
||||
text: I18n.tr("Border Thickness")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: resetBorderThicknessBtn
|
||||
buttonSize: 20
|
||||
iconName: "refresh"
|
||||
iconSize: 12
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
SettingsData.dankBarBorderThickness = 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Theme.spacingS
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: borderThicknessSlider
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: SettingsData.dankBarBorderThickness ?? 1
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
unit: "px"
|
||||
showValue: true
|
||||
wheelEnabled: false
|
||||
thumbOutlineColor: Theme.surfaceContainerHigh
|
||||
onSliderValueChanged: newValue => {
|
||||
SettingsData.dankBarBorderThickness = newValue
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: borderThicknessSlider
|
||||
property: "value"
|
||||
value: SettingsData.dankBarBorderThickness ?? 1
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -76,6 +76,378 @@ Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: gammaSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: gammaSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "brightness_6"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Gamma Control")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: nightModeToggle
|
||||
|
||||
width: parent.width
|
||||
text: I18n.tr("Night Mode")
|
||||
description: "Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates."
|
||||
checked: DisplayService.nightModeEnabled
|
||||
onToggled: checked => {
|
||||
DisplayService.toggleNightMode()
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNightModeEnabledChanged() {
|
||||
nightModeToggle.checked = DisplayService.nightModeEnabled
|
||||
}
|
||||
|
||||
target: DisplayService
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
leftPadding: Theme.spacingM
|
||||
rightPadding: Theme.spacingM
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width - parent.leftPadding - parent.rightPadding
|
||||
text: I18n.tr("Temperature")
|
||||
description: I18n.tr("Color temperature for night mode")
|
||||
currentValue: SessionData.nightModeTemperature + "K"
|
||||
options: {
|
||||
var temps = []
|
||||
for (var i = 2500; i <= 6000; i += 500) {
|
||||
temps.push(i + "K")
|
||||
}
|
||||
return temps
|
||||
}
|
||||
onValueChanged: value => {
|
||||
var temp = parseInt(value.replace("K", ""))
|
||||
SessionData.setNightModeTemperature(temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: automaticToggle
|
||||
width: parent.width
|
||||
text: I18n.tr("Automatic Control")
|
||||
description: "Only adjust gamma based on time or location rules."
|
||||
checked: SessionData.nightModeAutoEnabled
|
||||
onToggled: checked => {
|
||||
if (checked && !DisplayService.nightModeEnabled) {
|
||||
DisplayService.toggleNightMode()
|
||||
} else if (!checked && DisplayService.nightModeEnabled) {
|
||||
DisplayService.toggleNightMode()
|
||||
}
|
||||
SessionData.setNightModeAutoEnabled(checked)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoEnabledChanged() {
|
||||
automaticToggle.checked = SessionData.nightModeAutoEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: automaticSettings
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: SessionData.nightModeAutoEnabled
|
||||
leftPadding: Theme.spacingM
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoEnabledChanged() {
|
||||
automaticSettings.visible = SessionData.nightModeAutoEnabled
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 200
|
||||
height: 45 + Theme.spacingM
|
||||
|
||||
DankTabBar {
|
||||
id: modeTabBarNight
|
||||
width: 200
|
||||
height: 45
|
||||
model: [{
|
||||
"text": "Time",
|
||||
"icon": "access_time"
|
||||
}, {
|
||||
"text": "Location",
|
||||
"icon": "place"
|
||||
}]
|
||||
|
||||
Component.onCompleted: {
|
||||
currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0
|
||||
Qt.callLater(updateIndicator)
|
||||
}
|
||||
|
||||
onTabClicked: index => {
|
||||
console.log("Tab clicked:", index, "Setting mode to:", index === 1 ? "location" : "time")
|
||||
DisplayService.setNightModeAutomationMode(index === 1 ? "location" : "time")
|
||||
currentIndex = index
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoModeChanged() {
|
||||
modeTabBarNight.currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0
|
||||
Qt.callLater(modeTabBarNight.updateIndicator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
property bool isTimeMode: SessionData.nightModeAutoMode === "time"
|
||||
visible: isTimeMode
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
height: 20
|
||||
leftPadding: 45
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Hour")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: 50
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Minute")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: 50
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
height: 32
|
||||
|
||||
StyledText {
|
||||
id: startLabel
|
||||
text: I18n.tr("Start")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: 50
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: 60
|
||||
height: 32
|
||||
text: ""
|
||||
currentValue: SessionData.nightModeStartHour.toString()
|
||||
options: {
|
||||
var hours = []
|
||||
for (var i = 0; i < 24; i++) {
|
||||
hours.push(i.toString())
|
||||
}
|
||||
return hours
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeStartHour(parseInt(value))
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: 60
|
||||
height: 32
|
||||
text: ""
|
||||
currentValue: SessionData.nightModeStartMinute.toString().padStart(2, '0')
|
||||
options: {
|
||||
var minutes = []
|
||||
for (var i = 0; i < 60; i += 5) {
|
||||
minutes.push(i.toString().padStart(2, '0'))
|
||||
}
|
||||
return minutes
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeStartMinute(parseInt(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
height: 32
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("End")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: startLabel.width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: 60
|
||||
height: 32
|
||||
text: ""
|
||||
currentValue: SessionData.nightModeEndHour.toString()
|
||||
options: {
|
||||
var hours = []
|
||||
for (var i = 0; i < 24; i++) {
|
||||
hours.push(i.toString())
|
||||
}
|
||||
return hours
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeEndHour(parseInt(value))
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: 60
|
||||
height: 32
|
||||
text: ""
|
||||
currentValue: SessionData.nightModeEndMinute.toString().padStart(2, '0')
|
||||
options: {
|
||||
var minutes = []
|
||||
for (var i = 0; i < 60; i += 5) {
|
||||
minutes.push(i.toString().padStart(2, '0'))
|
||||
}
|
||||
return minutes
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeEndMinute(parseInt(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
property bool isLocationMode: SessionData.nightModeAutoMode === "location"
|
||||
visible: isLocationMode
|
||||
spacing: Theme.spacingM
|
||||
width: parent.width
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Auto-location")
|
||||
description: DisplayService.geoclueAvailable ? "Use automatic location detection (geoclue2)" : "Geoclue service not running - cannot auto-detect location"
|
||||
checked: SessionData.nightModeLocationProvider === "geoclue2"
|
||||
enabled: DisplayService.geoclueAvailable
|
||||
onToggled: checked => {
|
||||
if (checked && DisplayService.geoclueAvailable) {
|
||||
SessionData.setNightModeLocationProvider("geoclue2")
|
||||
SessionData.setLatitude(0.0)
|
||||
SessionData.setLongitude(0.0)
|
||||
} else {
|
||||
SessionData.setNightModeLocationProvider("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Manual Coordinates")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
visible: SessionData.nightModeLocationProvider !== "geoclue2"
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
visible: SessionData.nightModeLocationProvider !== "geoclue2"
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Latitude")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: 120
|
||||
height: 40
|
||||
text: SessionData.latitude.toString()
|
||||
placeholderText: "0.0"
|
||||
onTextChanged: {
|
||||
const lat = parseFloat(text) || 0.0
|
||||
if (lat >= -90 && lat <= 90) {
|
||||
SessionData.setLatitude(lat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Longitude")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: 120
|
||||
height: 40
|
||||
text: SessionData.longitude.toString()
|
||||
placeholderText: "0.0"
|
||||
onTextChanged: {
|
||||
const lon = parseFloat(text) || 0.0
|
||||
if (lon >= -180 && lon <= 180) {
|
||||
SessionData.setLongitude(lon)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Uses sunrise/sunset times to automatically adjust night mode based on your location.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: screensInfoSection.implicitHeight + Theme.spacingL * 2
|
||||
|
||||
@@ -905,7 +905,63 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic Theme Section
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: lightModeRow.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
id: lightModeRow
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "contrast"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
rotation: SessionData.isLightMode ? 180 : 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - lightModeToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Light Mode")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Use light theme instead of dark theme")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: lightModeToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SessionData.isLightMode
|
||||
onToggleCompleted: checked => {
|
||||
Theme.screenTransition()
|
||||
Theme.setLightMode(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: dynamicThemeSection.implicitHeight + Theme.spacingL * 2
|
||||
@@ -932,6 +988,26 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Matugen Settings")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "auto_awesome"
|
||||
size: Theme.iconSize
|
||||
color: Theme.currentTheme === Theme.dynamic ? Theme.primary : Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - toggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
@@ -998,6 +1074,56 @@ Item {
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.2
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "code"
|
||||
size: Theme.iconSize
|
||||
color: SettingsData.runUserMatugenTemplates ? Theme.primary : Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - runUserTemplatesToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Run User Templates")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Execute templates from ~/.config/matugen/config.toml")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: runUserTemplatesToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.runUserMatugenTemplates
|
||||
enabled: Theme.matugenAvailable
|
||||
onToggled: checked => {
|
||||
SettingsData.setRunUserMatugenTemplates(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("matugen not detected - dynamic theming unavailable")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -1009,721 +1135,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Display Settings
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: displaySection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: displaySection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "monitor"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Display Settings")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Light Mode")
|
||||
description: I18n.tr("Use light theme instead of dark theme")
|
||||
checked: SessionData.isLightMode
|
||||
onToggleCompleted: checked => {
|
||||
Theme.screenTransition()
|
||||
Theme.setLightMode(checked)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.2
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: nightModeToggle
|
||||
|
||||
width: parent.width
|
||||
text: I18n.tr("Night Mode")
|
||||
description: "Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates."
|
||||
checked: DisplayService.nightModeEnabled
|
||||
onToggled: checked => {
|
||||
DisplayService.toggleNightMode()
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNightModeEnabledChanged() {
|
||||
nightModeToggle.checked = DisplayService.nightModeEnabled
|
||||
}
|
||||
|
||||
target: DisplayService
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Temperature")
|
||||
description: I18n.tr("Color temperature for night mode")
|
||||
currentValue: SessionData.nightModeTemperature + "K"
|
||||
options: {
|
||||
var temps = []
|
||||
for (var i = 2500; i <= 6000; i += 500) {
|
||||
temps.push(i + "K")
|
||||
}
|
||||
return temps
|
||||
}
|
||||
onValueChanged: value => {
|
||||
var temp = parseInt(value.replace("K", ""))
|
||||
SessionData.setNightModeTemperature(temp)
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: automaticToggle
|
||||
width: parent.width
|
||||
text: I18n.tr("Automatic Control")
|
||||
description: "Only adjust gamma based on time or location rules."
|
||||
checked: SessionData.nightModeAutoEnabled
|
||||
onToggled: checked => {
|
||||
if (checked && !DisplayService.nightModeEnabled) {
|
||||
DisplayService.toggleNightMode()
|
||||
} else if (!checked && DisplayService.nightModeEnabled) {
|
||||
DisplayService.toggleNightMode()
|
||||
}
|
||||
SessionData.setNightModeAutoEnabled(checked)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoEnabledChanged() {
|
||||
automaticToggle.checked = SessionData.nightModeAutoEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: automaticSettings
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: SessionData.nightModeAutoEnabled
|
||||
leftPadding: Theme.spacingM
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoEnabledChanged() {
|
||||
automaticSettings.visible = SessionData.nightModeAutoEnabled
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 200
|
||||
height: 45 + Theme.spacingM
|
||||
|
||||
DankTabBar {
|
||||
id: modeTabBarNight
|
||||
width: 200
|
||||
height: 45
|
||||
model: [{
|
||||
"text": "Time",
|
||||
"icon": "access_time"
|
||||
}, {
|
||||
"text": "Location",
|
||||
"icon": "place"
|
||||
}]
|
||||
|
||||
Component.onCompleted: {
|
||||
currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0
|
||||
Qt.callLater(updateIndicator)
|
||||
}
|
||||
|
||||
onTabClicked: index => {
|
||||
console.log("Tab clicked:", index, "Setting mode to:", index === 1 ? "location" : "time")
|
||||
DisplayService.setNightModeAutomationMode(index === 1 ? "location" : "time")
|
||||
currentIndex = index
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoModeChanged() {
|
||||
modeTabBarNight.currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0
|
||||
Qt.callLater(modeTabBarNight.updateIndicator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
property bool isTimeMode: SessionData.nightModeAutoMode === "time"
|
||||
visible: isTimeMode
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Header row
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
height: 20
|
||||
leftPadding: 45
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Hour")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: 50
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Minute")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: 50
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
// Start time row
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
height: 32
|
||||
|
||||
StyledText {
|
||||
id: startLabel
|
||||
text: I18n.tr("Start")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: 50
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: 60
|
||||
height: 32
|
||||
text: ""
|
||||
currentValue: SessionData.nightModeStartHour.toString()
|
||||
options: {
|
||||
var hours = []
|
||||
for (var i = 0; i < 24; i++) {
|
||||
hours.push(i.toString())
|
||||
}
|
||||
return hours
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeStartHour(parseInt(value))
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: 60
|
||||
height: 32
|
||||
text: ""
|
||||
currentValue: SessionData.nightModeStartMinute.toString().padStart(2, '0')
|
||||
options: {
|
||||
var minutes = []
|
||||
for (var i = 0; i < 60; i += 5) {
|
||||
minutes.push(i.toString().padStart(2, '0'))
|
||||
}
|
||||
return minutes
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeStartMinute(parseInt(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End time row
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
height: 32
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("End")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: startLabel.width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: 60
|
||||
height: 32
|
||||
text: ""
|
||||
currentValue: SessionData.nightModeEndHour.toString()
|
||||
options: {
|
||||
var hours = []
|
||||
for (var i = 0; i < 24; i++) {
|
||||
hours.push(i.toString())
|
||||
}
|
||||
return hours
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeEndHour(parseInt(value))
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: 60
|
||||
height: 32
|
||||
text: ""
|
||||
currentValue: SessionData.nightModeEndMinute.toString().padStart(2, '0')
|
||||
options: {
|
||||
var minutes = []
|
||||
for (var i = 0; i < 60; i += 5) {
|
||||
minutes.push(i.toString().padStart(2, '0'))
|
||||
}
|
||||
return minutes
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeEndMinute(parseInt(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
property bool isLocationMode: SessionData.nightModeAutoMode === "location"
|
||||
visible: isLocationMode
|
||||
spacing: Theme.spacingM
|
||||
width: parent.width
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Auto-location")
|
||||
description: DisplayService.geoclueAvailable ? "Use automatic location detection (geoclue2)" : "Geoclue service not running - cannot auto-detect location"
|
||||
checked: SessionData.nightModeLocationProvider === "geoclue2"
|
||||
enabled: DisplayService.geoclueAvailable
|
||||
onToggled: checked => {
|
||||
if (checked && DisplayService.geoclueAvailable) {
|
||||
SessionData.setNightModeLocationProvider("geoclue2")
|
||||
SessionData.setLatitude(0.0)
|
||||
SessionData.setLongitude(0.0)
|
||||
} else {
|
||||
SessionData.setNightModeLocationProvider("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Manual Coordinates")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
visible: SessionData.nightModeLocationProvider !== "geoclue2"
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
visible: SessionData.nightModeLocationProvider !== "geoclue2"
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Latitude")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: 120
|
||||
height: 40
|
||||
text: SessionData.latitude.toString()
|
||||
placeholderText: "0.0"
|
||||
onTextChanged: {
|
||||
const lat = parseFloat(text) || 0.0
|
||||
if (lat >= -90 && lat <= 90) {
|
||||
SessionData.setLatitude(lat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Longitude")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: 120
|
||||
height: 40
|
||||
text: SessionData.longitude.toString()
|
||||
placeholderText: "0.0"
|
||||
onTextChanged: {
|
||||
const lon = parseFloat(text) || 0.0
|
||||
if (lon >= -180 && lon <= 180) {
|
||||
SessionData.setLongitude(lon)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Uses sunrise/sunset times to automatically adjust night mode based on your location.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notification Popup Settings
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: notificationPopupSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: notificationPopupSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "notifications"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Notification Popups")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Popup Position")
|
||||
description: I18n.tr("Choose where notification popups appear on screen")
|
||||
currentValue: {
|
||||
if (SettingsData.notificationPopupPosition === -1) {
|
||||
return "Top Center"
|
||||
}
|
||||
switch (SettingsData.notificationPopupPosition) {
|
||||
case SettingsData.Position.Top:
|
||||
return "Top Right"
|
||||
case SettingsData.Position.Bottom:
|
||||
return "Bottom Left"
|
||||
case SettingsData.Position.Left:
|
||||
return "Top Left"
|
||||
case SettingsData.Position.Right:
|
||||
return "Bottom Right"
|
||||
default:
|
||||
return "Top Right"
|
||||
}
|
||||
}
|
||||
options: ["Top Right", "Top Left", "Top Center", "Bottom Right", "Bottom Left"]
|
||||
onValueChanged: value => {
|
||||
switch (value) {
|
||||
case "Top Right":
|
||||
SettingsData.setNotificationPopupPosition(SettingsData.Position.Top)
|
||||
break
|
||||
case "Top Left":
|
||||
SettingsData.setNotificationPopupPosition(SettingsData.Position.Left)
|
||||
break
|
||||
case "Top Center":
|
||||
SettingsData.setNotificationPopupPosition(-1)
|
||||
break
|
||||
case "Bottom Right":
|
||||
SettingsData.setNotificationPopupPosition(SettingsData.Position.Right)
|
||||
break
|
||||
case "Bottom Left":
|
||||
SettingsData.setNotificationPopupPosition(SettingsData.Position.Bottom)
|
||||
break
|
||||
}
|
||||
SettingsData.sendTestNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.2
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Always Show OSD Percentage")
|
||||
description: I18n.tr("Display volume and brightness percentage values by default in OSD popups")
|
||||
checked: SettingsData.osdAlwaysShowValue
|
||||
onToggled: checked => {
|
||||
SettingsData.setOsdAlwaysShowValue(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Font Settings
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: fontSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: fontSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "font_download"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Font Settings")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Font Family")
|
||||
description: I18n.tr("Select system font family")
|
||||
currentValue: {
|
||||
if (SettingsData.fontFamily === SettingsData.defaultFontFamily)
|
||||
return "Default"
|
||||
else
|
||||
return SettingsData.fontFamily || "Default"
|
||||
}
|
||||
enableFuzzySearch: true
|
||||
popupWidthOffset: 100
|
||||
maxPopupHeight: 400
|
||||
options: cachedFontFamilies
|
||||
onValueChanged: value => {
|
||||
if (value.startsWith("Default"))
|
||||
SettingsData.setFontFamily(SettingsData.defaultFontFamily)
|
||||
else
|
||||
SettingsData.setFontFamily(value)
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Font Weight")
|
||||
description: I18n.tr("Select font weight")
|
||||
currentValue: {
|
||||
switch (SettingsData.fontWeight) {
|
||||
case Font.Thin:
|
||||
return "Thin"
|
||||
case Font.ExtraLight:
|
||||
return "Extra Light"
|
||||
case Font.Light:
|
||||
return "Light"
|
||||
case Font.Normal:
|
||||
return "Regular"
|
||||
case Font.Medium:
|
||||
return "Medium"
|
||||
case Font.DemiBold:
|
||||
return "Demi Bold"
|
||||
case Font.Bold:
|
||||
return "Bold"
|
||||
case Font.ExtraBold:
|
||||
return "Extra Bold"
|
||||
case Font.Black:
|
||||
return "Black"
|
||||
default:
|
||||
return "Regular"
|
||||
}
|
||||
}
|
||||
options: ["Thin", "Extra Light", "Light", "Regular", "Medium", "Demi Bold", "Bold", "Extra Bold", "Black"]
|
||||
onValueChanged: value => {
|
||||
var weight
|
||||
switch (value) {
|
||||
case "Thin":
|
||||
weight = Font.Thin
|
||||
break
|
||||
case "Extra Light":
|
||||
weight = Font.ExtraLight
|
||||
break
|
||||
case "Light":
|
||||
weight = Font.Light
|
||||
break
|
||||
case "Regular":
|
||||
weight = Font.Normal
|
||||
break
|
||||
case "Medium":
|
||||
weight = Font.Medium
|
||||
break
|
||||
case "Demi Bold":
|
||||
weight = Font.DemiBold
|
||||
break
|
||||
case "Bold":
|
||||
weight = Font.Bold
|
||||
break
|
||||
case "Extra Bold":
|
||||
weight = Font.ExtraBold
|
||||
break
|
||||
case "Black":
|
||||
weight = Font.Black
|
||||
break
|
||||
default:
|
||||
weight = Font.Normal
|
||||
break
|
||||
}
|
||||
SettingsData.setFontWeight(weight)
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Monospace Font")
|
||||
description: I18n.tr("Select monospace font for process list and technical displays")
|
||||
currentValue: {
|
||||
if (SettingsData.monoFontFamily === SettingsData.defaultMonoFontFamily)
|
||||
return "Default"
|
||||
|
||||
return SettingsData.monoFontFamily || "Default"
|
||||
}
|
||||
enableFuzzySearch: true
|
||||
popupWidthOffset: 100
|
||||
maxPopupHeight: 400
|
||||
options: cachedFontFamilies
|
||||
onValueChanged: value => {
|
||||
if (value === "Default")
|
||||
SettingsData.setMonoFontFamily(SettingsData.defaultMonoFontFamily)
|
||||
else
|
||||
SettingsData.setMonoFontFamily(value)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: fontScaleControls.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Font Scale")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Scale all font sizes")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: fontScaleControls
|
||||
|
||||
width: 180
|
||||
height: 36
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 32
|
||||
iconName: "remove"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
enabled: SettingsData.fontScale > 1.0
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
var newScale = Math.max(1.0, SettingsData.fontScale - 0.05)
|
||||
SettingsData.setFontScale(newScale)
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: 60
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: (SettingsData.fontScale * 100).toFixed(0) + "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 32
|
||||
iconName: "add"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
enabled: SettingsData.fontScale < 2.0
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
var newScale = Math.min(2.0, SettingsData.fontScale + 0.05)
|
||||
SettingsData.setFontScale(newScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation Settings
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
@@ -1752,7 +1163,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Animations")
|
||||
text: I18n.tr("Animation Speed")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
@@ -1760,28 +1171,13 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Animation Speed")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Control the speed of animations throughout the interface")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
height: childrenRect.height
|
||||
|
||||
DankButtonGroup {
|
||||
id: animationSpeedGroup
|
||||
width: parent.width
|
||||
x: (parent.width - width) / 2
|
||||
model: ["None", "Shortest", "Short", "Medium", "Long"]
|
||||
selectionMode: "single"
|
||||
currentIndex: SettingsData.animationSpeed
|
||||
@@ -1794,54 +1190,6 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lock Screen Settings
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: lockScreenSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: lockScreenSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "lock"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Lock Screen")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Show Power Actions")
|
||||
description: "Show power, restart, and logout buttons on the lock screen"
|
||||
checked: SettingsData.lockScreenShowPowerActions
|
||||
onToggled: checked => {
|
||||
SettingsData.setLockScreenShowPowerActions(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -256,10 +256,9 @@ FocusScope {
|
||||
anchors.bottomMargin: pluginDelegate.isExpanded ? settingsContainer.height : 0
|
||||
hoverEnabled: true
|
||||
cursorShape: pluginDelegate.hasSettings ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: pluginDelegate.hasSettings
|
||||
onClicked: {
|
||||
if (pluginDelegate.hasSettings) {
|
||||
pluginsTab.expandedPluginId = pluginsTab.expandedPluginId === pluginDelegate.pluginId ? "" : pluginDelegate.pluginId
|
||||
}
|
||||
pluginsTab.expandedPluginId = pluginsTab.expandedPluginId === pluginDelegate.pluginId ? "" : pluginDelegate.pluginId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,6 +503,10 @@ FocusScope {
|
||||
clip: true
|
||||
focus: pluginDelegate.isExpanded && pluginDelegate.hasSettings
|
||||
|
||||
Keys.onPressed: event => {
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.surfaceContainerHighest
|
||||
@@ -600,6 +603,9 @@ FocusScope {
|
||||
pluginsTab.expandedPluginId = ""
|
||||
}
|
||||
}
|
||||
function onPluginListUpdated() {
|
||||
refreshPluginList()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -17,67 +17,37 @@ Item {
|
||||
property bool fontsEnumerated: false
|
||||
|
||||
function enumerateFonts() {
|
||||
var fonts = ["Default"]
|
||||
var fonts = []
|
||||
var availableFonts = Qt.fontFamilies()
|
||||
var rootFamilies = []
|
||||
var seenFamilies = new Set()
|
||||
|
||||
for (var i = 0; i < availableFonts.length; i++) {
|
||||
var fontName = availableFonts[i]
|
||||
if (fontName.startsWith("."))
|
||||
continue
|
||||
|
||||
if (fontName === SettingsData.defaultFontFamily)
|
||||
continue
|
||||
|
||||
var rootName = fontName.replace(
|
||||
/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i,
|
||||
"").replace(
|
||||
/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i,
|
||||
"").replace(/ (UI|Display|Text|Mono|Sans|Serif)$/i,
|
||||
function (match, suffix) {
|
||||
return match
|
||||
}).trim()
|
||||
if (!seenFamilies.has(rootName) && rootName !== "") {
|
||||
seenFamilies.add(rootName)
|
||||
rootFamilies.push(rootName)
|
||||
}
|
||||
fonts.push(fontName)
|
||||
}
|
||||
cachedFontFamilies = fonts.concat(rootFamilies.sort())
|
||||
var monoFonts = ["Default"]
|
||||
var monoFamilies = []
|
||||
var seenMonoFamilies = new Set()
|
||||
fonts.sort()
|
||||
fonts.unshift("Default")
|
||||
cachedFontFamilies = fonts
|
||||
|
||||
var monoFonts = []
|
||||
for (var j = 0; j < availableFonts.length; j++) {
|
||||
var fontName2 = availableFonts[j]
|
||||
if (fontName2.startsWith("."))
|
||||
continue
|
||||
|
||||
if (fontName2 === SettingsData.defaultMonoFontFamily)
|
||||
continue
|
||||
|
||||
var lowerName = fontName2.toLowerCase()
|
||||
if (lowerName.includes("mono") || lowerName.includes(
|
||||
"code") || lowerName.includes(
|
||||
"console") || lowerName.includes(
|
||||
"terminal") || lowerName.includes(
|
||||
"courier") || lowerName.includes(
|
||||
"dejavu sans mono") || lowerName.includes(
|
||||
"jetbrains") || lowerName.includes(
|
||||
"fira") || lowerName.includes(
|
||||
"hack") || lowerName.includes(
|
||||
"source code") || lowerName.includes(
|
||||
"ubuntu mono") || lowerName.includes("cascadia")) {
|
||||
var rootName2 = fontName2.replace(
|
||||
/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i,
|
||||
"").replace(
|
||||
/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i,
|
||||
"").trim()
|
||||
if (!seenMonoFamilies.has(rootName2) && rootName2 !== "") {
|
||||
seenMonoFamilies.add(rootName2)
|
||||
monoFamilies.push(rootName2)
|
||||
}
|
||||
if (lowerName.includes("mono") || lowerName.includes("code") ||
|
||||
lowerName.includes("console") || lowerName.includes("terminal") ||
|
||||
lowerName.includes("courier") || lowerName.includes("jetbrains") ||
|
||||
lowerName.includes("fira") || lowerName.includes("hack") ||
|
||||
lowerName.includes("source code") || lowerName.includes("cascadia")) {
|
||||
monoFonts.push(fontName2)
|
||||
}
|
||||
}
|
||||
cachedMonoFamilies = monoFonts.concat(monoFamilies.sort())
|
||||
monoFonts.sort()
|
||||
monoFonts.unshift("Default")
|
||||
cachedMonoFamilies = monoFonts
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -929,6 +899,238 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: fontSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: fontSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "font_download"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Font Settings")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Font Family")
|
||||
description: I18n.tr("Select system font family")
|
||||
currentValue: {
|
||||
if (SettingsData.fontFamily === SettingsData.defaultFontFamily)
|
||||
return "Default"
|
||||
else
|
||||
return SettingsData.fontFamily || "Default"
|
||||
}
|
||||
enableFuzzySearch: true
|
||||
popupWidthOffset: 100
|
||||
maxPopupHeight: 400
|
||||
options: cachedFontFamilies
|
||||
onValueChanged: value => {
|
||||
if (value.startsWith("Default"))
|
||||
SettingsData.setFontFamily(SettingsData.defaultFontFamily)
|
||||
else
|
||||
SettingsData.setFontFamily(value)
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Font Weight")
|
||||
description: I18n.tr("Select font weight")
|
||||
currentValue: {
|
||||
switch (SettingsData.fontWeight) {
|
||||
case Font.Thin:
|
||||
return "Thin"
|
||||
case Font.ExtraLight:
|
||||
return "Extra Light"
|
||||
case Font.Light:
|
||||
return "Light"
|
||||
case Font.Normal:
|
||||
return "Regular"
|
||||
case Font.Medium:
|
||||
return "Medium"
|
||||
case Font.DemiBold:
|
||||
return "Demi Bold"
|
||||
case Font.Bold:
|
||||
return "Bold"
|
||||
case Font.ExtraBold:
|
||||
return "Extra Bold"
|
||||
case Font.Black:
|
||||
return "Black"
|
||||
default:
|
||||
return "Regular"
|
||||
}
|
||||
}
|
||||
options: ["Thin", "Extra Light", "Light", "Regular", "Medium", "Demi Bold", "Bold", "Extra Bold", "Black"]
|
||||
onValueChanged: value => {
|
||||
var weight
|
||||
switch (value) {
|
||||
case "Thin":
|
||||
weight = Font.Thin
|
||||
break
|
||||
case "Extra Light":
|
||||
weight = Font.ExtraLight
|
||||
break
|
||||
case "Light":
|
||||
weight = Font.Light
|
||||
break
|
||||
case "Regular":
|
||||
weight = Font.Normal
|
||||
break
|
||||
case "Medium":
|
||||
weight = Font.Medium
|
||||
break
|
||||
case "Demi Bold":
|
||||
weight = Font.DemiBold
|
||||
break
|
||||
case "Bold":
|
||||
weight = Font.Bold
|
||||
break
|
||||
case "Extra Bold":
|
||||
weight = Font.ExtraBold
|
||||
break
|
||||
case "Black":
|
||||
weight = Font.Black
|
||||
break
|
||||
default:
|
||||
weight = Font.Normal
|
||||
break
|
||||
}
|
||||
SettingsData.setFontWeight(weight)
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Monospace Font")
|
||||
description: I18n.tr("Select monospace font for process list and technical displays")
|
||||
currentValue: {
|
||||
if (SettingsData.monoFontFamily === SettingsData.defaultMonoFontFamily)
|
||||
return "Default"
|
||||
|
||||
return SettingsData.monoFontFamily || "Default"
|
||||
}
|
||||
enableFuzzySearch: true
|
||||
popupWidthOffset: 100
|
||||
maxPopupHeight: 400
|
||||
options: cachedMonoFamilies
|
||||
onValueChanged: value => {
|
||||
if (value === "Default")
|
||||
SettingsData.setMonoFontFamily(SettingsData.defaultMonoFontFamily)
|
||||
else
|
||||
SettingsData.setMonoFontFamily(value)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: fontScaleControls.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Font Scale")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Scale all font sizes")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: fontScaleControls
|
||||
|
||||
width: 180
|
||||
height: 36
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 32
|
||||
iconName: "remove"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
enabled: SettingsData.fontScale > 1.0
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
var newScale = Math.max(1.0, SettingsData.fontScale - 0.05)
|
||||
SettingsData.setFontScale(newScale)
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: 60
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r,
|
||||
Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: (SettingsData.fontScale * 100).toFixed(
|
||||
0) + "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 32
|
||||
iconName: "add"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
enabled: SettingsData.fontScale < 2.0
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
var newScale = Math.min(2.0,
|
||||
SettingsData.fontScale + 0.05)
|
||||
SettingsData.setFontScale(newScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// System Configuration Warning
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
@@ -992,6 +1194,7 @@ Item {
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("Icon Theme")
|
||||
description: "DankShell & System Icons\n(requires restart)"
|
||||
|
||||
@@ -1,378 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: timeTab
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingL
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
// Time Format
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: timeSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: timeSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "schedule"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- toggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("24-Hour Format")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Use 24-hour time format instead of 12-hour AM/PM")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: toggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.use24HourClock
|
||||
onToggled: checked => {
|
||||
return SettingsData.setClockFormat(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Date Format Section
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: dateSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: dateSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "calendar_today"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Date Format")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
height: 50
|
||||
text: I18n.tr("Top Bar Format")
|
||||
description: "Preview: " + (SettingsData.clockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) : new Date().toLocaleDateString(Qt.locale(), "ddd d"))
|
||||
currentValue: {
|
||||
if (!SettingsData.clockDateFormat || SettingsData.clockDateFormat.length === 0) {
|
||||
return "System Default"
|
||||
}
|
||||
// Find matching preset or show "Custom"
|
||||
const presets = [{
|
||||
"format": "ddd d",
|
||||
"label": "Day Date"
|
||||
}, {
|
||||
"format": "ddd MMM d",
|
||||
"label": "Day Month Date"
|
||||
}, {
|
||||
"format": "MMM d",
|
||||
"label": "Month Date"
|
||||
}, {
|
||||
"format": "M/d",
|
||||
"label": "Numeric (M/D)"
|
||||
}, {
|
||||
"format": "d/M",
|
||||
"label": "Numeric (D/M)"
|
||||
}, {
|
||||
"format": "ddd d MMM yyyy",
|
||||
"label": "Full with Year"
|
||||
}, {
|
||||
"format": "yyyy-MM-dd",
|
||||
"label": "ISO Date"
|
||||
}, {
|
||||
"format": "dddd, MMMM d",
|
||||
"label": "Full Day & Month"
|
||||
}]
|
||||
const match = presets.find(p => {
|
||||
return p.format
|
||||
=== SettingsData.clockDateFormat
|
||||
})
|
||||
return match ? match.label: I18n.tr("Custom: ") + SettingsData.clockDateFormat
|
||||
}
|
||||
options: ["System Default", "Day Date", "Day Month Date", "Month Date", "Numeric (M/D)", "Numeric (D/M)", "Full with Year", "ISO Date", "Full Day & Month", "Custom..."]
|
||||
onValueChanged: value => {
|
||||
const formatMap = {
|
||||
"System Default": "",
|
||||
"Day Date": "ddd d",
|
||||
"Day Month Date": "ddd MMM d",
|
||||
"Month Date": "MMM d",
|
||||
"Numeric (M/D)": "M/d",
|
||||
"Numeric (D/M)": "d/M",
|
||||
"Full with Year": "ddd d MMM yyyy",
|
||||
"ISO Date": "yyyy-MM-dd",
|
||||
"Full Day & Month": "dddd, MMMM d"
|
||||
}
|
||||
if (value === "Custom...") {
|
||||
customFormatInput.visible = true
|
||||
} else {
|
||||
customFormatInput.visible = false
|
||||
SettingsData.setClockDateFormat(
|
||||
formatMap[value])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
height: 50
|
||||
text: I18n.tr("Lock Screen Format")
|
||||
description: "Preview: " + (SettingsData.lockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat) : new Date().toLocaleDateString(Qt.locale(), Locale.LongFormat))
|
||||
currentValue: {
|
||||
if (!SettingsData.lockDateFormat || SettingsData.lockDateFormat.length === 0) {
|
||||
return "System Default"
|
||||
}
|
||||
// Find matching preset or show "Custom"
|
||||
const presets = [{
|
||||
"format": "ddd d",
|
||||
"label": "Day Date"
|
||||
}, {
|
||||
"format": "ddd MMM d",
|
||||
"label": "Day Month Date"
|
||||
}, {
|
||||
"format": "MMM d",
|
||||
"label": "Month Date"
|
||||
}, {
|
||||
"format": "M/d",
|
||||
"label": "Numeric (M/D)"
|
||||
}, {
|
||||
"format": "d/M",
|
||||
"label": "Numeric (D/M)"
|
||||
}, {
|
||||
"format": "ddd d MMM yyyy",
|
||||
"label": "Full with Year"
|
||||
}, {
|
||||
"format": "yyyy-MM-dd",
|
||||
"label": "ISO Date"
|
||||
}, {
|
||||
"format": "dddd, MMMM d",
|
||||
"label": "Full Day & Month"
|
||||
}]
|
||||
const match = presets.find(p => {
|
||||
return p.format
|
||||
=== SettingsData.lockDateFormat
|
||||
})
|
||||
return match ? match.label: I18n.tr("Custom: ") + SettingsData.lockDateFormat
|
||||
}
|
||||
options: ["System Default", "Day Date", "Day Month Date", "Month Date", "Numeric (M/D)", "Numeric (D/M)", "Full with Year", "ISO Date", "Full Day & Month", "Custom..."]
|
||||
onValueChanged: value => {
|
||||
const formatMap = {
|
||||
"System Default": "",
|
||||
"Day Date": "ddd d",
|
||||
"Day Month Date": "ddd MMM d",
|
||||
"Month Date": "MMM d",
|
||||
"Numeric (M/D)": "M/d",
|
||||
"Numeric (D/M)": "d/M",
|
||||
"Full with Year": "ddd d MMM yyyy",
|
||||
"ISO Date": "yyyy-MM-dd",
|
||||
"Full Day & Month": "dddd, MMMM d"
|
||||
}
|
||||
if (value === "Custom...") {
|
||||
customLockFormatInput.visible = true
|
||||
} else {
|
||||
customLockFormatInput.visible = false
|
||||
SettingsData.setLockDateFormat(
|
||||
formatMap[value])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customFormatInput
|
||||
|
||||
width: parent.width
|
||||
visible: false
|
||||
placeholderText: I18n.tr("Enter custom top bar format (e.g., ddd MMM d)")
|
||||
text: SettingsData.clockDateFormat
|
||||
onTextChanged: {
|
||||
if (visible && text)
|
||||
SettingsData.setClockDateFormat(text)
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customLockFormatInput
|
||||
|
||||
width: parent.width
|
||||
visible: false
|
||||
placeholderText: I18n.tr("Enter custom lock screen format (e.g., dddd, MMMM d)")
|
||||
text: SettingsData.lockDateFormat
|
||||
onTextChanged: {
|
||||
if (visible && text)
|
||||
SettingsData.setLockDateFormat(text)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: formatHelp.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.1)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: formatHelp
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Format Legend")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingL) / 2
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• d - Day (1-31)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• dd - Day (01-31)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• ddd - Day name (Mon)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• dddd - Day name (Monday)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• M - Month (1-12)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingL) / 2
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• MM - Month (01-12)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• MMM - Month (Jan)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• MMMM - Month (January)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• yy - Year (24)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("• yyyy - Year (2024)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1288
Modules/Settings/TimeWeatherTab.qml
Normal file
1288
Modules/Settings/TimeWeatherTab.qml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,390 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: weatherTab
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacingL
|
||||
clip: true
|
||||
contentHeight: mainColumn.height
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
// Enable Weather
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: enableWeatherSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: enableWeatherSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "cloud"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- enableToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Enable Weather")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Show weather information in top bar and control center")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: enableToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.weatherEnabled
|
||||
onToggled: checked => {
|
||||
return SettingsData.setWeatherEnabled(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Temperature Unit
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: temperatureSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
visible: SettingsData.weatherEnabled
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
Column {
|
||||
id: temperatureSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "thermostat"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- temperatureToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Use Fahrenheit")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Use Fahrenheit instead of Celsius for temperature")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: temperatureToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.useFahrenheit
|
||||
onToggled: checked => {
|
||||
return SettingsData.setTemperatureUnit(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Location Settings
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: locationSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
visible: SettingsData.weatherEnabled
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
Column {
|
||||
id: locationSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "location_on"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
- autoLocationToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Auto Location")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Automatically determine your location using your IP address")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: autoLocationToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.useAutoLocation
|
||||
onToggled: checked => {
|
||||
return SettingsData.setAutoLocation(
|
||||
checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
visible: !SettingsData.useAutoLocation
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.2
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Custom Location")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Latitude")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: latitudeInput
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "40.7128"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
keyNavigationTab: longitudeInput
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.weatherCoordinates) {
|
||||
const coords = SettingsData.weatherCoordinates.split(',')
|
||||
if (coords.length > 0) {
|
||||
text = coords[0].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onWeatherCoordinatesChanged() {
|
||||
if (SettingsData.weatherCoordinates) {
|
||||
const coords = SettingsData.weatherCoordinates.split(',')
|
||||
if (coords.length > 0) {
|
||||
latitudeInput.text = coords[0].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
if (text && longitudeInput.text) {
|
||||
const coords = text + "," + longitudeInput.text
|
||||
SettingsData.weatherCoordinates = coords
|
||||
SettingsData.saveSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Longitude")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: longitudeInput
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "-74.0060"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
keyNavigationTab: locationSearchInput
|
||||
keyNavigationBacktab: latitudeInput
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.weatherCoordinates) {
|
||||
const coords = SettingsData.weatherCoordinates.split(',')
|
||||
if (coords.length > 1) {
|
||||
text = coords[1].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onWeatherCoordinatesChanged() {
|
||||
if (SettingsData.weatherCoordinates) {
|
||||
const coords = SettingsData.weatherCoordinates.split(',')
|
||||
if (coords.length > 1) {
|
||||
longitudeInput.text = coords[1].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
if (text && latitudeInput.text) {
|
||||
const coords = latitudeInput.text + "," + text
|
||||
SettingsData.weatherCoordinates = coords
|
||||
SettingsData.saveSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Location Search")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankLocationSearch {
|
||||
id: locationSearchInput
|
||||
width: parent.width
|
||||
currentLocation: ""
|
||||
placeholderText: I18n.tr("New York, NY")
|
||||
keyNavigationBacktab: longitudeInput
|
||||
onLocationSelected: (displayName, coordinates) => {
|
||||
SettingsData.setWeatherLocation(displayName, coordinates)
|
||||
|
||||
const coords = coordinates.split(',')
|
||||
if (coords.length >= 2) {
|
||||
latitudeInput.text = coords[0].trim()
|
||||
longitudeInput.text = coords[1].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,126 @@ Item {
|
||||
description: "Use animated wave progress bars for media playback"
|
||||
checked: SettingsData.waveProgressEnabled
|
||||
onToggled: checked => {
|
||||
return SettingsData.setWaveProgressEnabled(checked)
|
||||
return SettingsData.setWaveProgressEnabled(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: updaterSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: updaterSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "refresh"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("System Updater")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Use Custom Command")
|
||||
description: I18n.tr("Use custom command for update your system")
|
||||
checked: SettingsData.updaterUseCustomCommand
|
||||
onToggled: checked => {
|
||||
if (!checked) {
|
||||
updaterCustomCommand.text = "";
|
||||
updaterTerminalCustomClass.text = "";
|
||||
SettingsData.setUpdaterCustomCommand("");
|
||||
SettingsData.setUpdaterTerminalAdditionalParams("");
|
||||
}
|
||||
return SettingsData.setUpdaterUseCustomCommandEnabled(checked);
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("System update custom command")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: updaterCustomCommand
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "myPkgMngr --sysupdate"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.updaterCustomCommand) {
|
||||
text = SettingsData.updaterCustomCommand;
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
SettingsData.setUpdaterCustomCommand(text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Terminal custom additional parameters")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: updaterTerminalCustomClass
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "-T udpClass"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.updaterTerminalAdditionalParams) {
|
||||
text = SettingsData.updaterTerminalAdditionalParams;
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
SettingsData.setUpdaterTerminalAdditionalParams(text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,6 +515,151 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: notificationSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: notificationSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "notifications"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Notification Popups")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
leftPadding: Theme.spacingM
|
||||
rightPadding: Theme.spacingM
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width - parent.leftPadding - parent.rightPadding
|
||||
text: I18n.tr("Popup Position")
|
||||
description: I18n.tr("Choose where notification popups appear on screen")
|
||||
currentValue: {
|
||||
if (SettingsData.notificationPopupPosition === -1) {
|
||||
return "Top Center"
|
||||
}
|
||||
switch (SettingsData.notificationPopupPosition) {
|
||||
case SettingsData.Position.Top:
|
||||
return "Top Right"
|
||||
case SettingsData.Position.Bottom:
|
||||
return "Bottom Left"
|
||||
case SettingsData.Position.Left:
|
||||
return "Top Left"
|
||||
case SettingsData.Position.Right:
|
||||
return "Bottom Right"
|
||||
default:
|
||||
return "Top Right"
|
||||
}
|
||||
}
|
||||
options: ["Top Right", "Top Left", "Top Center", "Bottom Right", "Bottom Left"]
|
||||
onValueChanged: value => {
|
||||
switch (value) {
|
||||
case "Top Right":
|
||||
SettingsData.setNotificationPopupPosition(SettingsData.Position.Top)
|
||||
break
|
||||
case "Top Left":
|
||||
SettingsData.setNotificationPopupPosition(SettingsData.Position.Left)
|
||||
break
|
||||
case "Top Center":
|
||||
SettingsData.setNotificationPopupPosition(-1)
|
||||
break
|
||||
case "Bottom Right":
|
||||
SettingsData.setNotificationPopupPosition(SettingsData.Position.Right)
|
||||
break
|
||||
case "Bottom Left":
|
||||
SettingsData.setNotificationPopupPosition(SettingsData.Position.Bottom)
|
||||
break
|
||||
}
|
||||
SettingsData.sendTestNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: osdRow.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
id: osdRow
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "tune"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - osdToggle.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Always Show OSD Percentage")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Display volume and brightness percentage values by default in OSD popups")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: osdToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.osdAlwaysShowValue
|
||||
onToggleCompleted: checked => {
|
||||
SettingsData.setOsdAlwaysShowValue(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
width: Math.min(900, messageText.implicitWidth + statusIcon.width + Theme.spacingM + (ToastService.hasDetails ? (expandButton.width + closeButton.width + 4) : 0) + Theme.spacingL * 2 + Theme.spacingM * 2)
|
||||
width: shouldBeVisible ? Math.min(900, messageText.implicitWidth + statusIcon.width + Theme.spacingM + (ToastService.hasDetails ? (expandButton.width + closeButton.width + 4) : 0) + Theme.spacingL * 2 + Theme.spacingM * 2) : frozenWidth
|
||||
height: toastContent.height + Theme.spacingL * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: Theme.barHeight - 4 + SettingsData.dankBarSpacing + 2
|
||||
@@ -206,84 +206,121 @@ PanelWindow {
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: detailsText.height + Theme.spacingS * 2
|
||||
height: detailsColumn.height + Theme.spacingS * 2
|
||||
color: Qt.rgba(0, 0, 0, 0.2)
|
||||
radius: Theme.cornerRadius / 2
|
||||
visible: toast.expanded && ToastService.hasDetails
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
id: detailsText
|
||||
text: ToastService.currentDetails
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError:
|
||||
case ToastService.levelWarn:
|
||||
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
|
||||
default:
|
||||
return Theme.surfaceText
|
||||
}
|
||||
}
|
||||
isMonospace: true
|
||||
Column {
|
||||
id: detailsColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: copyButton.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: copyButton
|
||||
iconName: "content_copy"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
iconColor: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError:
|
||||
case ToastService.levelWarn:
|
||||
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
|
||||
default:
|
||||
return Theme.surfaceText
|
||||
}
|
||||
}
|
||||
buttonSize: Theme.iconSizeSmall + 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
property bool showTooltip: false
|
||||
|
||||
onClicked: {
|
||||
Quickshell.execDetached(
|
||||
["wl-copy", ToastService.currentDetails])
|
||||
showTooltip = true
|
||||
tooltipTimer.start()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: tooltipTimer
|
||||
interval: 1500
|
||||
onTriggered: copyButton.showTooltip = false
|
||||
StyledText {
|
||||
id: detailsText
|
||||
text: ToastService.currentDetails
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError:
|
||||
case ToastService.levelWarn:
|
||||
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
|
||||
default:
|
||||
return Theme.surfaceText
|
||||
}
|
||||
}
|
||||
visible: ToastService.currentDetails.length > 0
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: copyButton.showTooltip
|
||||
width: tooltipLabel.implicitWidth + 16
|
||||
height: tooltipLabel.implicitHeight + 8
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
y: -height - 4
|
||||
x: -width / 2 + copyButton.width / 2
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: commandText.height + Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
radius: Theme.cornerRadius / 2
|
||||
visible: ToastService.currentCommand.length > 0
|
||||
|
||||
StyledText {
|
||||
id: tooltipLabel
|
||||
anchors.centerIn: parent
|
||||
text: root.copiedText
|
||||
id: commandText
|
||||
text: ToastService.currentCommand
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
color: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError:
|
||||
case ToastService.levelWarn:
|
||||
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
|
||||
default:
|
||||
return Theme.surfaceText
|
||||
}
|
||||
}
|
||||
isMonospace: true
|
||||
anchors.left: parent.left
|
||||
anchors.right: copyButton.visible ? copyButton.left : parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Theme.spacingS / 2
|
||||
anchors.rightMargin: Theme.spacingS / 2
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: copyButton
|
||||
iconName: "content_copy"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
iconColor: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError:
|
||||
case ToastService.levelWarn:
|
||||
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
|
||||
default:
|
||||
return Theme.surfaceText
|
||||
}
|
||||
}
|
||||
buttonSize: Theme.iconSizeSmall + 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Theme.spacingS / 2
|
||||
visible: ToastService.currentCommand.length > 0
|
||||
|
||||
property bool showTooltip: false
|
||||
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["wl-copy", ToastService.currentCommand])
|
||||
showTooltip = true
|
||||
tooltipTimer.start()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: tooltipTimer
|
||||
interval: 1500
|
||||
onTriggered: copyButton.showTooltip = false
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: copyButton.showTooltip
|
||||
width: tooltipLabel.implicitWidth + 16
|
||||
height: tooltipLabel.implicitHeight + 8
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
y: -height - 4
|
||||
x: -width / 2 + copyButton.width / 2
|
||||
|
||||
StyledText {
|
||||
id: tooltipLabel
|
||||
anchors.centerIn: parent
|
||||
text: root.copiedText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
137
PLUGINS/LauncherExample/LauncherExampleLauncher.qml
Normal file
137
PLUGINS/LauncherExample/LauncherExampleLauncher.qml
Normal file
@@ -0,0 +1,137 @@
|
||||
import QtQuick
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Plugin properties
|
||||
property var pluginService: null
|
||||
property string trigger: "#"
|
||||
|
||||
// Plugin interface signals
|
||||
signal itemsChanged()
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("LauncherExample: Plugin loaded")
|
||||
|
||||
// Load custom trigger from settings
|
||||
if (pluginService) {
|
||||
trigger = pluginService.loadPluginData("launcherExample", "trigger", "#")
|
||||
}
|
||||
}
|
||||
|
||||
// Required function: Get items for launcher
|
||||
function getItems(query) {
|
||||
const baseItems = [
|
||||
{
|
||||
name: "Test Item 1",
|
||||
icon: "lightbulb",
|
||||
comment: "This is a test item that shows a toast notification",
|
||||
action: "toast:Test Item 1 executed!",
|
||||
categories: ["LauncherExample"]
|
||||
},
|
||||
{
|
||||
name: "Test Item 2",
|
||||
icon: "star",
|
||||
comment: "Another test item with different action",
|
||||
action: "toast:Test Item 2 clicked!",
|
||||
categories: ["LauncherExample"]
|
||||
},
|
||||
{
|
||||
name: "Test Item 3",
|
||||
icon: "favorite",
|
||||
comment: "Third test item for demonstration",
|
||||
action: "toast:Test Item 3 activated!",
|
||||
categories: ["LauncherExample"]
|
||||
},
|
||||
{
|
||||
name: "Example Copy Action",
|
||||
icon: "content_copy",
|
||||
comment: "Demonstrates copying text to clipboard",
|
||||
action: "copy:This text was copied by the launcher plugin!",
|
||||
categories: ["LauncherExample"]
|
||||
},
|
||||
{
|
||||
name: "Example Script Action",
|
||||
icon: "terminal",
|
||||
comment: "Demonstrates running a simple command",
|
||||
action: "script:echo 'Hello from launcher plugin!'",
|
||||
categories: ["LauncherExample"]
|
||||
}
|
||||
]
|
||||
|
||||
if (!query || query.length === 0) {
|
||||
return baseItems
|
||||
}
|
||||
|
||||
// Filter items based on query
|
||||
const lowerQuery = query.toLowerCase()
|
||||
return baseItems.filter(item => {
|
||||
return item.name.toLowerCase().includes(lowerQuery) ||
|
||||
item.comment.toLowerCase().includes(lowerQuery)
|
||||
})
|
||||
}
|
||||
|
||||
// Required function: Execute item action
|
||||
function executeItem(item) {
|
||||
if (!item || !item.action) {
|
||||
console.warn("LauncherExample: Invalid item or action")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("LauncherExample: Executing item:", item.name, "with action:", item.action)
|
||||
|
||||
const actionParts = item.action.split(":")
|
||||
const actionType = actionParts[0]
|
||||
const actionData = actionParts.slice(1).join(":")
|
||||
|
||||
switch (actionType) {
|
||||
case "toast":
|
||||
showToast(actionData)
|
||||
break
|
||||
case "copy":
|
||||
copyToClipboard(actionData)
|
||||
break
|
||||
case "script":
|
||||
runScript(actionData)
|
||||
break
|
||||
default:
|
||||
console.warn("LauncherExample: Unknown action type:", actionType)
|
||||
showToast("Unknown action: " + actionType)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for different action types
|
||||
function showToast(message) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showInfo("LauncherExample", message)
|
||||
} else {
|
||||
console.log("LauncherExample Toast:", message)
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
if (typeof globalThis !== "undefined" && globalThis.clipboard) {
|
||||
globalThis.clipboard.setText(text)
|
||||
showToast("Copied to clipboard: " + text)
|
||||
} else {
|
||||
console.log("LauncherExample: Would copy to clipboard:", text)
|
||||
showToast("Copy feature not available")
|
||||
}
|
||||
}
|
||||
|
||||
function runScript(command) {
|
||||
console.log("LauncherExample: Would run script:", command)
|
||||
showToast("Script executed: " + command)
|
||||
|
||||
// In a real plugin, you might create a Process component here
|
||||
// For demo purposes, we just show what would happen
|
||||
}
|
||||
|
||||
// Watch for trigger changes
|
||||
onTriggerChanged: {
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("launcherExample", "trigger", trigger)
|
||||
}
|
||||
}
|
||||
}
|
||||
244
PLUGINS/LauncherExample/LauncherExampleSettings.qml
Normal file
244
PLUGINS/LauncherExample/LauncherExampleSettings.qml
Normal file
@@ -0,0 +1,244 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
property var pluginService: null
|
||||
|
||||
implicitHeight: settingsColumn.implicitHeight
|
||||
height: implicitHeight
|
||||
|
||||
Column {
|
||||
id: settingsColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
text: "Launcher Example Plugin Settings"
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.Bold
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "This plugin demonstrates the launcher plugin system with example items and actions."
|
||||
font.pixelSize: 14
|
||||
color: "#CCFFFFFF"
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width - 32
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - 32
|
||||
height: 1
|
||||
color: "#30FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 12
|
||||
width: parent.width - 32
|
||||
|
||||
Text {
|
||||
text: "Trigger Configuration"
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.Medium
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: noTriggerToggle.checked ? "Items will always show in the launcher (no trigger needed)." : "Set the trigger text to activate this plugin. Type the trigger in the launcher to filter to this plugin's items."
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 12
|
||||
|
||||
CheckBox {
|
||||
id: noTriggerToggle
|
||||
text: "No trigger (always show)"
|
||||
checked: loadSettings("noTrigger", false)
|
||||
|
||||
contentItem: Text {
|
||||
text: noTriggerToggle.text
|
||||
font.pixelSize: 14
|
||||
color: "#FFFFFF"
|
||||
leftPadding: noTriggerToggle.indicator.width + 8
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
radius: 4
|
||||
border.color: noTriggerToggle.checked ? "#4CAF50" : "#60FFFFFF"
|
||||
border.width: 2
|
||||
color: noTriggerToggle.checked ? "#4CAF50" : "transparent"
|
||||
|
||||
Rectangle {
|
||||
width: 12
|
||||
height: 12
|
||||
anchors.centerIn: parent
|
||||
radius: 2
|
||||
color: "#FFFFFF"
|
||||
visible: noTriggerToggle.checked
|
||||
}
|
||||
}
|
||||
|
||||
onCheckedChanged: {
|
||||
saveSettings("noTrigger", checked)
|
||||
if (checked) {
|
||||
saveSettings("trigger", "")
|
||||
} else {
|
||||
saveSettings("trigger", triggerField.text || "#")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 12
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
visible: !noTriggerToggle.checked
|
||||
|
||||
Text {
|
||||
text: "Trigger:"
|
||||
font.pixelSize: 14
|
||||
color: "#FFFFFF"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: triggerField
|
||||
width: 100
|
||||
height: 40
|
||||
text: loadSettings("trigger", "#")
|
||||
placeholderText: "#"
|
||||
backgroundColor: "#30FFFFFF"
|
||||
textColor: "#FFFFFF"
|
||||
|
||||
onTextEdited: {
|
||||
const newTrigger = text.trim()
|
||||
saveSettings("trigger", newTrigger || "#")
|
||||
saveSettings("noTrigger", newTrigger === "")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Examples: #, !, @, !ex, etc."
|
||||
font.pixelSize: 12
|
||||
color: "#AAFFFFFF"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - 32
|
||||
height: 1
|
||||
color: "#30FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 8
|
||||
width: parent.width - 32
|
||||
|
||||
Text {
|
||||
text: "Example Items:"
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 4
|
||||
leftPadding: 16
|
||||
|
||||
Text {
|
||||
text: "• Test Item 1, 2, 3 - Show toast notifications"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "• Example Copy Action - Copy text to clipboard"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "• Example Script Action - Demonstrate script execution"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - 32
|
||||
height: 1
|
||||
color: "#30FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 8
|
||||
width: parent.width - 32
|
||||
|
||||
Text {
|
||||
text: "Usage:"
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 4
|
||||
leftPadding: 16
|
||||
bottomPadding: 24
|
||||
|
||||
Text {
|
||||
text: "1. Open Launcher (Ctrl+Space or click launcher button)"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: noTriggerToggle.checked ? "2. Items are always visible in the launcher" : "2. Type your trigger (default: #) to filter to this plugin"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: noTriggerToggle.checked ? "3. Search works normally with plugin items included" : "3. Optionally add search terms: '# test' to find test items"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "4. Select an item and press Enter to execute its action"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings(key, value) {
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("launcherExample", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
function loadSettings(key, defaultValue) {
|
||||
if (pluginService) {
|
||||
return pluginService.loadPluginData("launcherExample", key, defaultValue)
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
206
PLUGINS/LauncherExample/README.md
Normal file
206
PLUGINS/LauncherExample/README.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# LauncherExample Plugin
|
||||
|
||||
A demonstration plugin that showcases the DMS launcher plugin system capabilities.
|
||||
|
||||
## Purpose
|
||||
|
||||
This plugin serves as a comprehensive example for developers creating launcher plugins for DMS. It demonstrates:
|
||||
|
||||
- **Plugin Structure**: Proper manifest, launcher, and settings components
|
||||
- **Trigger System**: Customizable trigger strings for plugin activation (including empty triggers)
|
||||
- **Item Management**: Providing searchable items to the launcher
|
||||
- **Action Execution**: Handling different types of actions (toast, copy, script)
|
||||
- **Settings Integration**: Configurable plugin settings with persistence
|
||||
|
||||
## Features
|
||||
|
||||
### Example Items
|
||||
- **Test Items 1-3**: Demonstrate toast notifications
|
||||
- **Copy Action**: Shows clipboard integration
|
||||
- **Script Action**: Demonstrates command execution
|
||||
|
||||
### Trigger System
|
||||
- **Default Trigger**: `#` (configurable in settings)
|
||||
- **Empty Trigger Option**: Items can always be visible without needing a trigger
|
||||
- **Usage**: Type `#` in launcher to filter to this plugin (when trigger is set)
|
||||
- **Search**: Type `# test` to search within plugin items
|
||||
|
||||
### Action Types
|
||||
- `toast:message` - Shows toast notification
|
||||
- `copy:text` - Copies text to clipboard
|
||||
- `script:command` - Executes shell command (demo only)
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
PLUGINS/LauncherExample/
|
||||
├── plugin.json # Plugin manifest
|
||||
├── LauncherExampleLauncher.qml # Main launcher component
|
||||
├── LauncherExampleSettings.qml # Settings interface
|
||||
└── README.md # This documentation
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Plugin Directory**: Copy to `~/.config/DankMaterialShell/plugins/LauncherExample`
|
||||
2. **Enable Plugin**: Settings → Plugins → Enable "LauncherExample"
|
||||
3. **Configure**: Set custom trigger in plugin settings if desired
|
||||
|
||||
## Usage
|
||||
|
||||
### With Trigger (Default)
|
||||
1. Open launcher (Ctrl+Space or launcher button)
|
||||
2. Type `#` to activate plugin trigger
|
||||
3. Browse available items or add search terms
|
||||
4. Press Enter to execute selected item
|
||||
|
||||
### Without Trigger (Empty Trigger Mode)
|
||||
1. Enable "No trigger (always show)" in plugin settings
|
||||
2. Open launcher - plugin items are always visible
|
||||
3. Search works normally with plugin items included
|
||||
4. Press Enter to execute selected item
|
||||
|
||||
### Search Examples
|
||||
- `#` - Show all plugin items (with trigger enabled)
|
||||
- `# test` - Show items matching "test"
|
||||
- `# copy` - Show items matching "copy"
|
||||
- `test` - Show all items matching "test" (with empty trigger enabled)
|
||||
|
||||
## Developer Guide
|
||||
|
||||
### Plugin Contract
|
||||
|
||||
**Launcher Component Requirements**:
|
||||
```qml
|
||||
// Required properties
|
||||
property var pluginService: null
|
||||
property string trigger: "#"
|
||||
|
||||
// Required signals
|
||||
signal itemsChanged()
|
||||
|
||||
// Required functions
|
||||
function getItems(query): array
|
||||
function executeItem(item): void
|
||||
```
|
||||
|
||||
**Item Structure**:
|
||||
```javascript
|
||||
{
|
||||
name: "Item Name", // Display name
|
||||
icon: "icon_name", // Material icon
|
||||
comment: "Description", // Subtitle text
|
||||
action: "type:data", // Action to execute
|
||||
categories: ["PluginName"] // Category array
|
||||
}
|
||||
```
|
||||
|
||||
**Action Format**: `type:data` where:
|
||||
- `type` - Action handler (toast, copy, script, etc.)
|
||||
- `data` - Action-specific data
|
||||
|
||||
### Settings Integration
|
||||
```qml
|
||||
// Save setting
|
||||
pluginService.savePluginData("pluginId", "key", value)
|
||||
|
||||
// Load setting
|
||||
pluginService.loadPluginData("pluginId", "key", defaultValue)
|
||||
```
|
||||
|
||||
### Trigger Configuration
|
||||
|
||||
The trigger can be configured in two ways:
|
||||
|
||||
1. **Empty Trigger** (No Trigger Mode):
|
||||
- Check "No trigger (always show)" in settings
|
||||
- Saves `trigger: ""` and `noTrigger: true`
|
||||
- Items always appear in launcher alongside regular apps
|
||||
|
||||
2. **Custom Trigger**:
|
||||
- Enter any string (e.g., `#`, `!`, `@`, `!ex`)
|
||||
- Uncheck "No trigger" checkbox
|
||||
- Items only appear when trigger is typed
|
||||
|
||||
### Manifest Structure
|
||||
```json
|
||||
{
|
||||
"id": "launcherExample",
|
||||
"name": "LauncherExample",
|
||||
"type": "launcher",
|
||||
"capabilities": ["launcher"],
|
||||
"component": "./LauncherExampleLauncher.qml",
|
||||
"settings": "./LauncherExampleSettings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
```
|
||||
|
||||
Note: The `trigger` field in the manifest is optional and serves as the default trigger value.
|
||||
|
||||
## Extending This Plugin
|
||||
|
||||
### Adding New Items
|
||||
```qml
|
||||
function getItems(query) {
|
||||
return [
|
||||
{
|
||||
name: "My Item",
|
||||
icon: "custom_icon",
|
||||
comment: "Does something cool",
|
||||
action: "custom:action_data",
|
||||
categories: ["LauncherExample"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Adding New Actions
|
||||
```qml
|
||||
function executeItem(item) {
|
||||
const actionParts = item.action.split(":")
|
||||
const actionType = actionParts[0]
|
||||
const actionData = actionParts.slice(1).join(":")
|
||||
|
||||
switch (actionType) {
|
||||
case "custom":
|
||||
handleCustomAction(actionData)
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Trigger Logic
|
||||
```qml
|
||||
Component.onCompleted: {
|
||||
if (pluginService) {
|
||||
trigger = pluginService.loadPluginData("launcherExample", "trigger", "#")
|
||||
}
|
||||
}
|
||||
|
||||
onTriggerChanged: {
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("launcherExample", "trigger", trigger)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Unique Triggers**: Choose triggers that don't conflict with other plugins
|
||||
2. **Clear Descriptions**: Write helpful item comments
|
||||
3. **Error Handling**: Gracefully handle action failures
|
||||
4. **Performance**: Return results quickly in getItems()
|
||||
5. **Cleanup**: Destroy temporary objects in executeItem()
|
||||
6. **Empty Trigger Support**: Consider if your plugin should support empty trigger mode
|
||||
|
||||
## Testing
|
||||
|
||||
Test the plugin by:
|
||||
1. Installing and enabling in DMS
|
||||
2. Testing with trigger enabled
|
||||
3. Testing with empty trigger (no trigger mode)
|
||||
4. Trying each action type
|
||||
5. Testing search functionality
|
||||
6. Verifying settings persistence
|
||||
|
||||
This plugin provides a solid foundation for building more sophisticated launcher plugins with custom functionality!
|
||||
17
PLUGINS/LauncherExample/plugin.json
Normal file
17
PLUGINS/LauncherExample/plugin.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "launcherExample",
|
||||
"name": "LauncherExample",
|
||||
"description": "Example launcher plugin demonstrating the launcher plugin system",
|
||||
"version": "1.0.0",
|
||||
"author": "DMS Team",
|
||||
"icon": "extension",
|
||||
"type": "launcher",
|
||||
"capabilities": ["launcher"],
|
||||
"component": "./LauncherExampleLauncher.qml",
|
||||
"settings": "./LauncherExampleSettings.qml",
|
||||
"trigger": "#",
|
||||
"permissions": [
|
||||
"settings_read",
|
||||
"settings_write"
|
||||
]
|
||||
}
|
||||
@@ -771,12 +771,266 @@ The plugin API is currently **experimental**. Breaking changes may occur in mino
|
||||
- Plugin update notifications
|
||||
- Inter-plugin communication
|
||||
|
||||
## Launcher Plugins
|
||||
|
||||
Launcher plugins extend the DMS application launcher by adding custom searchable items with trigger-based filtering.
|
||||
|
||||
### Overview
|
||||
|
||||
Launcher plugins enable you to:
|
||||
- Add custom items to the launcher/app drawer
|
||||
- Use trigger strings for quick filtering (e.g., `!`, `#`, `@`)
|
||||
- Execute custom actions when items are selected
|
||||
- Provide searchable, categorized content
|
||||
- Integrate seamlessly with the existing launcher
|
||||
|
||||
### Plugin Type Configuration
|
||||
|
||||
To create a launcher plugin, set the plugin type in `plugin.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "myLauncher",
|
||||
"name": "My Launcher Plugin",
|
||||
"type": "launcher",
|
||||
"capabilities": ["launcher"],
|
||||
"component": "./MyLauncher.qml",
|
||||
"settings": "./MySettings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
```
|
||||
|
||||
### Launcher Component Contract
|
||||
|
||||
Create `MyLauncher.qml` with the following interface:
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Required properties
|
||||
property var pluginService: null
|
||||
property string trigger: "#"
|
||||
|
||||
// Required signals
|
||||
signal itemsChanged()
|
||||
|
||||
// Required: Return array of launcher items
|
||||
function getItems(query) {
|
||||
return [
|
||||
{
|
||||
name: "Item Name",
|
||||
icon: "icon_name",
|
||||
comment: "Description",
|
||||
action: "type:data",
|
||||
categories: ["MyLauncher"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Required: Execute item action
|
||||
function executeItem(item) {
|
||||
const [type, data] = item.action.split(":", 2)
|
||||
// Handle action based on type
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (pluginService) {
|
||||
trigger = pluginService.loadPluginData("myLauncher", "trigger", "#")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Item Structure
|
||||
|
||||
Each item returned by `getItems()` must include:
|
||||
|
||||
- `name` (string): Display name shown in launcher
|
||||
- `icon` (string): Material Design icon name
|
||||
- `comment` (string): Description/subtitle text
|
||||
- `action` (string): Action identifier in `type:data` format
|
||||
- `categories` (array): Array containing your plugin name
|
||||
|
||||
### Trigger System
|
||||
|
||||
Triggers control when your plugin's items appear in the launcher:
|
||||
|
||||
**Empty Trigger Mode** (No trigger):
|
||||
- Items always visible alongside regular apps
|
||||
- Search includes your items automatically
|
||||
- Configure by saving empty trigger: `trigger: ""`
|
||||
|
||||
**Custom Trigger Mode**:
|
||||
- Items only appear when trigger is typed
|
||||
- Example: Type `#` to show only your plugin's items
|
||||
- Type `# query` to search within your plugin
|
||||
- Configure any string: `#`, `!`, `@`, `!custom`, etc.
|
||||
|
||||
### Trigger Configuration in Settings
|
||||
|
||||
Provide a settings component with trigger configuration:
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
property var pluginService: null
|
||||
|
||||
Column {
|
||||
spacing: 12
|
||||
|
||||
CheckBox {
|
||||
id: noTriggerToggle
|
||||
text: "No trigger (always show)"
|
||||
checked: loadSettings("noTrigger", false)
|
||||
|
||||
onCheckedChanged: {
|
||||
saveSettings("noTrigger", checked)
|
||||
if (checked) {
|
||||
saveSettings("trigger", "")
|
||||
} else {
|
||||
saveSettings("trigger", triggerField.text || "#")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: triggerField
|
||||
visible: !noTriggerToggle.checked
|
||||
text: loadSettings("trigger", "#")
|
||||
placeholderText: "#"
|
||||
|
||||
onTextEdited: {
|
||||
saveSettings("trigger", text || "#")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings(key, value) {
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("myLauncher", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
function loadSettings(key, defaultValue) {
|
||||
if (pluginService) {
|
||||
return pluginService.loadPluginData("myLauncher", key, defaultValue)
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Action Execution
|
||||
|
||||
Handle different action types in `executeItem()`:
|
||||
|
||||
```qml
|
||||
function executeItem(item) {
|
||||
const actionParts = item.action.split(":")
|
||||
const actionType = actionParts[0]
|
||||
const actionData = actionParts.slice(1).join(":")
|
||||
|
||||
switch (actionType) {
|
||||
case "toast":
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showInfo("Plugin", actionData)
|
||||
}
|
||||
break
|
||||
case "copy":
|
||||
// Copy to clipboard
|
||||
break
|
||||
case "script":
|
||||
// Execute command
|
||||
break
|
||||
default:
|
||||
console.warn("Unknown action:", actionType)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Search and Filtering
|
||||
|
||||
The launcher automatically handles search when:
|
||||
|
||||
**With empty trigger**:
|
||||
- Your items appear in all searches
|
||||
- No prefix needed
|
||||
|
||||
**With custom trigger**:
|
||||
- Type trigger alone: Shows all your items
|
||||
- Type trigger + query: Filters your items by query
|
||||
- The query parameter is passed to your `getItems(query)` function
|
||||
|
||||
Example `getItems()` implementation:
|
||||
|
||||
```qml
|
||||
function getItems(query) {
|
||||
const allItems = [
|
||||
{name: "Item 1", ...},
|
||||
{name: "Item 2", ...},
|
||||
{name: "Test Item", ...}
|
||||
]
|
||||
|
||||
if (!query || query.length === 0) {
|
||||
return allItems
|
||||
}
|
||||
|
||||
const lowerQuery = query.toLowerCase()
|
||||
return allItems.filter(item => {
|
||||
return item.name.toLowerCase().includes(lowerQuery) ||
|
||||
item.comment.toLowerCase().includes(lowerQuery)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Flow
|
||||
|
||||
1. User opens launcher
|
||||
2. If empty trigger: Your items appear alongside apps
|
||||
3. If custom trigger: User types trigger (e.g., `#`)
|
||||
4. Launcher calls `getItems(query)` on your plugin
|
||||
5. Your items displayed with your plugin's category
|
||||
6. User selects item and presses Enter
|
||||
7. Launcher calls `executeItem(item)` on your plugin
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Unique Triggers**: Choose non-conflicting trigger strings
|
||||
2. **Fast Response**: Return results quickly from `getItems()`
|
||||
3. **Clear Names**: Use descriptive item names and comments
|
||||
4. **Error Handling**: Gracefully handle failures in `executeItem()`
|
||||
5. **Cleanup**: Destroy temporary objects after use
|
||||
6. **Empty Trigger Support**: Consider if your plugin benefits from always being visible
|
||||
|
||||
### Example Plugin
|
||||
|
||||
See `PLUGINS/LauncherExample/` for a complete working example demonstrating:
|
||||
- Trigger configuration (including empty trigger mode)
|
||||
- Multiple action types (toast, copy, script)
|
||||
- Search/filtering implementation
|
||||
- Settings integration
|
||||
- Proper error handling
|
||||
|
||||
## Resources
|
||||
|
||||
- **Example Plugins**: [Emoji Picker](./ExampleEmojiPlugin/) [WorldClock](https://github.com/rochacbruno/WorldClock)
|
||||
- **Example Plugins**:
|
||||
- [Emoji Picker](./ExampleEmojiPlugin/)
|
||||
- [WorldClock](https://github.com/rochacbruno/WorldClock)
|
||||
- [LauncherExample](./LauncherExample/)
|
||||
- [Calculator](https://github.com/rochacbruno/DankCalculator)
|
||||
- **PluginService**: `Services/PluginService.qml`
|
||||
- **Settings UI**: `Modules/Settings/PluginsTab.qml`
|
||||
- **DankBar Integration**: `Modules/DankBar/DankBar.qml`
|
||||
- **Launcher Integration**: `Modules/AppDrawer/AppLauncher.qml`
|
||||
- **Theme Reference**: `Common/Theme.qml`
|
||||
- **Widget Library**: `Widgets/`
|
||||
|
||||
|
||||
19
README.md
19
README.md
@@ -12,7 +12,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/) and optimized for the [niri](https://github.com/YaLTeR/niri) and [Hyprland](https://hyprland.org/) compositors.
|
||||
A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/) and [Go](https://go.dev/). Optimized for the [niri](https://github.com/YaLTeR/niri) and [Hyprland](https://hyprland.org/) compositors.
|
||||
|
||||
Features notifications, app launcher, wallpaper customization, and fully customizable with [plugins](https://github.com/AvengeMedia/dms-plugin-registry).
|
||||
|
||||
@@ -21,7 +21,7 @@ Features notifications, app launcher, wallpaper customization, and fully customi
|
||||
<div align="center">
|
||||
<div style="max-width: 700px; margin: 0 auto;">
|
||||
|
||||
https://github.com/user-attachments/assets/9b99dbbf-42d3-44ab-83b6-fae6c2aa3cc0
|
||||
https://github.com/user-attachments/assets/40d2c56e-c1c9-4671-b04f-8f8b7b83b9ec
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,7 +134,7 @@ curl -fsSL https://install.danklinux.com | sh
|
||||
|
||||
### Compositor Setup
|
||||
|
||||
DankMaterialShell supports both **niri** and **Hyprland** compositors:
|
||||
DankMaterialShell particularly aims at supporting the **niri** and **Hyprland** compositors, but it does support more wayland compositors with a diminished feature set (no monitor off, workspace switcher, overview integration, etc.):
|
||||
|
||||
**Niri**:
|
||||
```bash
|
||||
@@ -171,6 +171,9 @@ For detailed Hyprland installation instructions, see the [Hyprland wiki](https:/
|
||||
#### Arch Linux - via AUR
|
||||
|
||||
```bash
|
||||
# Stable release
|
||||
paru -S dms-shell-bin
|
||||
# Latest -git
|
||||
paru -S dms-shell-git
|
||||
```
|
||||
|
||||
@@ -661,6 +664,16 @@ cp -R ./PLUGINS/ExampleEmojiPlugin ~/.config/DankMaterialShell/plugins
|
||||
|
||||
**Only install plugins from TRUSTED sources.** Plugins execute QML and javascript at runtime, plugins from third parties should be reviewed before enabling them in dms.
|
||||
|
||||
### nixOS - via home-manager
|
||||
|
||||
Add the following to your home-manager config to install a plugin:
|
||||
|
||||
```nix
|
||||
programs.dankMaterialShell.plugins = {
|
||||
ExampleEmojiPlugin.src = "${inputs.dankMaterialShell}/PLUGINS/ExampleEmojiPlugin";
|
||||
};
|
||||
```
|
||||
|
||||
### Calendar Setup
|
||||
|
||||
Sync your caldev compatible calendar (Google, Office365, etc.) for dashboard integration:
|
||||
|
||||
@@ -11,10 +11,13 @@ Singleton {
|
||||
id: root
|
||||
|
||||
property var applications: DesktopEntries.applications.values.filter(app => !app.noDisplay && !app.runInTerminal)
|
||||
|
||||
|
||||
|
||||
function searchApplications(query) {
|
||||
if (!query || query.length === 0)
|
||||
if (!query || query.length === 0) {
|
||||
return applications
|
||||
}
|
||||
if (applications.length === 0)
|
||||
return []
|
||||
|
||||
@@ -202,6 +205,11 @@ Singleton {
|
||||
})
|
||||
|
||||
function getCategoryIcon(category) {
|
||||
// Check if it's a plugin category
|
||||
const pluginIcon = getPluginCategoryIcon(category)
|
||||
if (pluginIcon) {
|
||||
return pluginIcon
|
||||
}
|
||||
return categoryIcons[category] || "folder"
|
||||
}
|
||||
|
||||
@@ -213,7 +221,12 @@ Singleton {
|
||||
appCategories.forEach(cat => categories.add(cat))
|
||||
}
|
||||
|
||||
return Array.from(categories).sort()
|
||||
// Add plugin categories
|
||||
const pluginCategories = getPluginCategories()
|
||||
pluginCategories.forEach(cat => categories.add(cat))
|
||||
|
||||
const result = Array.from(categories).sort()
|
||||
return result
|
||||
}
|
||||
|
||||
function getAppsInCategory(category) {
|
||||
@@ -221,9 +234,146 @@ Singleton {
|
||||
return applications
|
||||
}
|
||||
|
||||
// Check if it's a plugin category
|
||||
const pluginItems = getPluginItems(category, "")
|
||||
if (pluginItems.length > 0) {
|
||||
return pluginItems
|
||||
}
|
||||
|
||||
return applications.filter(app => {
|
||||
const appCategories = getCategoriesForApp(app)
|
||||
return appCategories.includes(category)
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin launcher support functions
|
||||
function getPluginCategories() {
|
||||
if (typeof PluginService === "undefined") {
|
||||
return []
|
||||
}
|
||||
|
||||
const categories = []
|
||||
const launchers = PluginService.getLauncherPlugins()
|
||||
|
||||
for (const pluginId in launchers) {
|
||||
const plugin = launchers[pluginId]
|
||||
const categoryName = plugin.name || pluginId
|
||||
categories.push(categoryName)
|
||||
}
|
||||
|
||||
return categories
|
||||
}
|
||||
|
||||
function getPluginCategoryIcon(category) {
|
||||
if (typeof PluginService === "undefined") return null
|
||||
|
||||
const launchers = PluginService.getLauncherPlugins()
|
||||
for (const pluginId in launchers) {
|
||||
const plugin = launchers[pluginId]
|
||||
if ((plugin.name || pluginId) === category) {
|
||||
return plugin.icon || "extension"
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getAllPluginItems() {
|
||||
if (typeof PluginService === "undefined") {
|
||||
return []
|
||||
}
|
||||
|
||||
let allItems = []
|
||||
const launchers = PluginService.getLauncherPlugins()
|
||||
|
||||
for (const pluginId in launchers) {
|
||||
const categoryName = launchers[pluginId].name || pluginId
|
||||
const items = getPluginItems(categoryName, "")
|
||||
allItems = allItems.concat(items)
|
||||
}
|
||||
|
||||
return allItems
|
||||
}
|
||||
|
||||
function getPluginItems(category, query) {
|
||||
if (typeof PluginService === "undefined") return []
|
||||
|
||||
const launchers = PluginService.getLauncherPlugins()
|
||||
for (const pluginId in launchers) {
|
||||
const plugin = launchers[pluginId]
|
||||
if ((plugin.name || pluginId) === category) {
|
||||
return getPluginItemsForPlugin(pluginId, query)
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function getPluginItemsForPlugin(pluginId, query) {
|
||||
if (typeof PluginService === "undefined") {
|
||||
return []
|
||||
}
|
||||
|
||||
const component = PluginService.pluginLauncherComponents[pluginId]
|
||||
if (!component) return []
|
||||
|
||||
try {
|
||||
const instance = component.createObject(root, {
|
||||
"pluginService": PluginService
|
||||
})
|
||||
|
||||
if (instance && typeof instance.getItems === "function") {
|
||||
const items = instance.getItems(query || "")
|
||||
instance.destroy()
|
||||
return items || []
|
||||
}
|
||||
|
||||
if (instance) {
|
||||
instance.destroy()
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("AppSearchService: Error getting items from plugin", pluginId, ":", e)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function executePluginItem(item, pluginId) {
|
||||
if (typeof PluginService === "undefined") return false
|
||||
|
||||
const component = PluginService.pluginLauncherComponents[pluginId]
|
||||
if (!component) return false
|
||||
|
||||
try {
|
||||
const instance = component.createObject(root, {
|
||||
"pluginService": PluginService
|
||||
})
|
||||
|
||||
if (instance && typeof instance.executeItem === "function") {
|
||||
instance.executeItem(item)
|
||||
instance.destroy()
|
||||
return true
|
||||
}
|
||||
|
||||
if (instance) {
|
||||
instance.destroy()
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("AppSearchService: Error executing item from plugin", pluginId, ":", e)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function searchPluginItems(query) {
|
||||
if (typeof PluginService === "undefined") return []
|
||||
|
||||
let allItems = []
|
||||
const launchers = PluginService.getLauncherPlugins()
|
||||
|
||||
for (const pluginId in launchers) {
|
||||
const items = getPluginItemsForPlugin(pluginId, query)
|
||||
allItems = allItems.concat(items)
|
||||
}
|
||||
|
||||
return allItems
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,22 @@ Singleton {
|
||||
|
||||
property bool dmsAvailable: false
|
||||
property var capabilities: []
|
||||
property int apiVersion: 0
|
||||
readonly property int expectedApiVersion: 1
|
||||
property var availablePlugins: []
|
||||
property var installedPlugins: []
|
||||
property bool isConnected: false
|
||||
property bool isConnecting: false
|
||||
property bool subscribeConnected: false
|
||||
|
||||
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
|
||||
readonly property bool verboseLogs: Quickshell.env("DMS_VERBOSE_LOGS") === "1"
|
||||
|
||||
property var pendingRequests: ({})
|
||||
property int requestIdCounter: 0
|
||||
property bool shownOutdatedError: false
|
||||
property string updateCommand: "dms update"
|
||||
property bool checkingUpdateCommand: false
|
||||
|
||||
signal pluginsListReceived(var plugins)
|
||||
signal installedPluginsReceived(var plugins)
|
||||
@@ -30,12 +37,87 @@ Singleton {
|
||||
signal operationError(string error)
|
||||
signal connectionStateChanged()
|
||||
|
||||
signal networkStateUpdate(var data)
|
||||
signal loginctlStateUpdate(var data)
|
||||
signal loginctlEvent(var event)
|
||||
|
||||
Component.onCompleted: {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
detectUpdateCommand()
|
||||
}
|
||||
}
|
||||
|
||||
function detectUpdateCommand() {
|
||||
checkingUpdateCommand = true
|
||||
checkAurHelper.running = true
|
||||
}
|
||||
|
||||
function startSocketConnection() {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
testProcess.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: checkAurHelper
|
||||
command: ["sh", "-c", "command -v paru || command -v yay"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const helper = text.trim()
|
||||
if (helper.includes("paru")) {
|
||||
checkDmsPackage.helper = "paru"
|
||||
checkDmsPackage.running = true
|
||||
} else if (helper.includes("yay")) {
|
||||
checkDmsPackage.helper = "yay"
|
||||
checkDmsPackage.running = true
|
||||
} else {
|
||||
updateCommand = "dms update"
|
||||
checkingUpdateCommand = false
|
||||
startSocketConnection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0) {
|
||||
updateCommand = "dms update"
|
||||
checkingUpdateCommand = false
|
||||
startSocketConnection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: checkDmsPackage
|
||||
property string helper: ""
|
||||
command: ["sh", "-c", "pacman -Qi dms-shell-git 2>/dev/null || pacman -Qi dms-shell-bin 2>/dev/null"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.includes("dms-shell-git")) {
|
||||
updateCommand = checkDmsPackage.helper + " -S dms-shell-git"
|
||||
} else if (text.includes("dms-shell-bin")) {
|
||||
updateCommand = checkDmsPackage.helper + " -S dms-shell-bin"
|
||||
} else {
|
||||
updateCommand = "dms update"
|
||||
}
|
||||
checkingUpdateCommand = false
|
||||
startSocketConnection()
|
||||
}
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0) {
|
||||
updateCommand = "dms update"
|
||||
checkingUpdateCommand = false
|
||||
startSocketConnection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: testProcess
|
||||
command: ["test", "-S", root.socketPath]
|
||||
@@ -56,11 +138,11 @@ Singleton {
|
||||
}
|
||||
|
||||
isConnecting = true
|
||||
socket.connected = true
|
||||
requestSocket.connected = true
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: socket
|
||||
id: requestSocket
|
||||
path: root.socketPath
|
||||
connected: false
|
||||
|
||||
@@ -69,9 +151,12 @@ Singleton {
|
||||
root.isConnected = true
|
||||
root.isConnecting = false
|
||||
root.connectionStateChanged()
|
||||
subscribeSocket.connected = true
|
||||
} else {
|
||||
root.isConnected = false
|
||||
root.isConnecting = false
|
||||
root.apiVersion = 0
|
||||
root.capabilities = []
|
||||
root.connectionStateChanged()
|
||||
}
|
||||
}
|
||||
@@ -82,28 +167,112 @@ Singleton {
|
||||
return
|
||||
}
|
||||
|
||||
if (root.verboseLogs) {
|
||||
console.log("DMSService: Request socket <<", line)
|
||||
}
|
||||
|
||||
try {
|
||||
const response = JSON.parse(line)
|
||||
|
||||
if (response.capabilities) {
|
||||
root.capabilities = response.capabilities
|
||||
return
|
||||
}
|
||||
|
||||
handleResponse(response)
|
||||
} catch (e) {
|
||||
console.warn("DMSService: Failed to parse response:", line, e)
|
||||
console.warn("DMSService: Failed to parse request response:", line, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: subscribeSocket
|
||||
path: root.socketPath
|
||||
connected: false
|
||||
|
||||
onConnectionStateChanged: {
|
||||
root.subscribeConnected = connected
|
||||
if (connected) {
|
||||
sendSubscribeRequest()
|
||||
}
|
||||
}
|
||||
|
||||
parser: SplitParser {
|
||||
onRead: line => {
|
||||
if (!line || line.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (root.verboseLogs) {
|
||||
console.log("DMSService: Subscribe socket <<", line)
|
||||
}
|
||||
|
||||
try {
|
||||
const response = JSON.parse(line)
|
||||
handleSubscriptionEvent(response)
|
||||
} catch (e) {
|
||||
console.warn("DMSService: Failed to parse subscription event:", line, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendSubscribeRequest() {
|
||||
const request = {
|
||||
"method": "subscribe"
|
||||
}
|
||||
|
||||
if (verboseLogs) {
|
||||
console.log("DMSService: Subscribing to all services")
|
||||
}
|
||||
subscribeSocket.send(request)
|
||||
}
|
||||
|
||||
function handleSubscriptionEvent(response) {
|
||||
if (response.error) {
|
||||
if (response.error.includes("unknown method") && response.error.includes("subscribe")) {
|
||||
if (!shownOutdatedError) {
|
||||
console.error("DMSService: Server does not support subscribe method")
|
||||
ToastService.showError(
|
||||
I18n.tr("DMS out of date"),
|
||||
I18n.tr("To update, run the following command:"),
|
||||
updateCommand
|
||||
)
|
||||
shownOutdatedError = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!response.result) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = response.result.service
|
||||
const data = response.result.data
|
||||
|
||||
if (service === "server") {
|
||||
apiVersion = data.apiVersion || 0
|
||||
capabilities = data.capabilities || []
|
||||
|
||||
console.log("DMSService: Connected (API v" + apiVersion + ") -", JSON.stringify(capabilities))
|
||||
|
||||
if (apiVersion < expectedApiVersion) {
|
||||
ToastService.showError("DMS server is outdated (API v" + apiVersion + ", expected v" + expectedApiVersion + ")")
|
||||
}
|
||||
} else if (service === "network") {
|
||||
networkStateUpdate(data)
|
||||
} else if (service === "loginctl") {
|
||||
if (data.event) {
|
||||
loginctlEvent(data)
|
||||
} else {
|
||||
loginctlStateUpdate(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendRequest(method, params, callback) {
|
||||
if (!isConnected) {
|
||||
if (callback) {
|
||||
callback({
|
||||
"error": "not connected to DMS socket"
|
||||
})
|
||||
"error": "not connected to DMS socket"
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -123,21 +292,10 @@ Singleton {
|
||||
pendingRequests[id] = callback
|
||||
}
|
||||
|
||||
socket.send(request)
|
||||
requestSocket.send(request)
|
||||
}
|
||||
|
||||
property var networkUpdateCallback: null
|
||||
|
||||
function handleResponse(response) {
|
||||
if (response.id === undefined && response.result) {
|
||||
if (response.result.type === "state_changed" && response.result.data) {
|
||||
if (networkUpdateCallback) {
|
||||
networkUpdateCallback(response.result.data)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const callback = pendingRequests[response.id]
|
||||
|
||||
if (callback) {
|
||||
|
||||
@@ -136,6 +136,15 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionService
|
||||
function onPrepareForSleep() {
|
||||
if (SessionData.lockBeforeSuspend) {
|
||||
root.lockRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!idleMonitorAvailable) {
|
||||
console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.")
|
||||
|
||||
@@ -76,8 +76,6 @@ Singleton {
|
||||
signal networksUpdated
|
||||
signal connectionChanged
|
||||
|
||||
property bool subscriptionConnected: false
|
||||
|
||||
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -87,51 +85,16 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: subscriptionSocket
|
||||
path: root.socketPath
|
||||
connected: networkAvailable
|
||||
Connections {
|
||||
target: DMSService
|
||||
|
||||
onConnectionStateChanged: {
|
||||
root.subscriptionConnected = connected
|
||||
if (connected) {
|
||||
console.log("NetworkManagerService: Subscription socket connected")
|
||||
function onNetworkStateUpdate(data) {
|
||||
if (DMSService.verboseLogs) {
|
||||
const networksCount = data.wifiNetworks?.length ?? "null"
|
||||
console.log("NetworkManagerService: Subscription update received, networks:", networksCount)
|
||||
}
|
||||
updateState(data)
|
||||
}
|
||||
|
||||
parser: SplitParser {
|
||||
onRead: line => {
|
||||
if (!line || line.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = JSON.parse(line)
|
||||
|
||||
if (response.capabilities) {
|
||||
console.log("NetworkManagerService: Subscription socket received capabilities")
|
||||
Qt.callLater(() => sendSubscribeRequest())
|
||||
return
|
||||
}
|
||||
|
||||
if (response.result && response.result.type === "state_changed" && response.result.data) {
|
||||
const networksCount = response.result.data.wifiNetworks?.length ?? "null"
|
||||
console.log("NetworkManagerService: Subscription update received, networks:", networksCount)
|
||||
updateState(response.result.data)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("NetworkManagerService: Failed to parse subscription response:", line, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendSubscribeRequest() {
|
||||
subscriptionSocket.send({
|
||||
"id": 1,
|
||||
"method": "network.subscribe"
|
||||
})
|
||||
console.log("NetworkManagerService: Sent network.subscribe request")
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -162,15 +125,15 @@ Singleton {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("NetworkManagerService: Capabilities:", JSON.stringify(DMSService.capabilities))
|
||||
networkAvailable = DMSService.capabilities.includes("network")
|
||||
console.log("NetworkManagerService: Network available:", networkAvailable)
|
||||
|
||||
if (DMSService.verboseLogs) {
|
||||
console.log("NetworkManagerService: Network available:", networkAvailable)
|
||||
}
|
||||
|
||||
if (networkAvailable && !stateInitialized) {
|
||||
console.log("NetworkManagerService: Requesting network state and starting subscription socket...")
|
||||
stateInitialized = true
|
||||
getState()
|
||||
subscriptionSocket.connected = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +160,9 @@ Singleton {
|
||||
if (response.result) {
|
||||
updateState(response.result)
|
||||
if (!initialStateFetched && response.result.wifiEnabled && (!response.result.wifiNetworks || response.result.wifiNetworks.length === 0)) {
|
||||
console.log("NetworkManagerService: Initial state has no networks, triggering scan")
|
||||
if (DMSService.verboseLogs) {
|
||||
console.log("NetworkManagerService: Initial state has no networks, triggering scan")
|
||||
}
|
||||
initialStateFetched = true
|
||||
Qt.callLater(() => scanWifi())
|
||||
}
|
||||
@@ -258,14 +223,18 @@ Singleton {
|
||||
function scanWifi() {
|
||||
if (!networkAvailable || isScanning || !wifiEnabled) return
|
||||
|
||||
console.log("NetworkManagerService: Starting WiFi scan...")
|
||||
if (DMSService.verboseLogs) {
|
||||
console.log("NetworkManagerService: Starting WiFi scan...")
|
||||
}
|
||||
isScanning = true
|
||||
DMSService.sendRequest("network.wifi.scan", null, response => {
|
||||
isScanning = false
|
||||
if (response.error) {
|
||||
console.warn("NetworkManagerService: WiFi scan failed:", response.error)
|
||||
} else {
|
||||
console.log("NetworkManagerService: Scan completed, requesting fresh state...")
|
||||
if (DMSService.verboseLogs) {
|
||||
console.log("NetworkManagerService: Scan completed")
|
||||
}
|
||||
Qt.callLater(() => getState())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -23,6 +23,7 @@ Singleton {
|
||||
|
||||
property var outputs: ({})
|
||||
property var windows: []
|
||||
property var displayScales: ({})
|
||||
|
||||
property bool inOverview: false
|
||||
|
||||
@@ -68,6 +69,7 @@ Singleton {
|
||||
const outputsData = JSON.parse(text)
|
||||
outputs = outputsData
|
||||
console.log("NiriService: Loaded", Object.keys(outputsData).length, "outputs")
|
||||
updateDisplayScales()
|
||||
if (windows.length > 0) {
|
||||
windows = sortWindowsByLayout(windows)
|
||||
}
|
||||
@@ -169,6 +171,20 @@ Singleton {
|
||||
outputsProcess.running = true
|
||||
}
|
||||
|
||||
function updateDisplayScales() {
|
||||
if (!outputs || Object.keys(outputs).length === 0) return
|
||||
|
||||
const scales = {}
|
||||
for (const outputName in outputs) {
|
||||
const output = outputs[outputName]
|
||||
if (output.logical && output.logical.scale !== undefined) {
|
||||
scales[outputName] = output.logical.scale
|
||||
}
|
||||
}
|
||||
|
||||
displayScales = scales
|
||||
}
|
||||
|
||||
function sortWindowsByLayout(windowList) {
|
||||
return [...windowList].sort((a, b) => {
|
||||
const aWorkspace = workspaces[a.workspace_id]
|
||||
@@ -395,6 +411,7 @@ Singleton {
|
||||
function handleOutputsChanged(data) {
|
||||
if (!data.outputs) return
|
||||
outputs = data.outputs
|
||||
updateDisplayScales()
|
||||
windows = sortWindowsByLayout(windows)
|
||||
}
|
||||
|
||||
@@ -407,9 +424,10 @@ Singleton {
|
||||
validateProcess.running = true
|
||||
} else {
|
||||
configValidationOutput = ""
|
||||
if (ToastService.toastVisible && ToastService.currentLevel === ToastService.levelError) {
|
||||
if (ToastService.toastVisible && ToastService.currentLevel === ToastService.levelError && ToastService.currentMessage.startsWith("niri:")) {
|
||||
ToastService.hideToast()
|
||||
}
|
||||
fetchOutputs()
|
||||
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression) {
|
||||
ToastService.showInfo("niri: config reloaded")
|
||||
} else if (suppressNextConfigToast) {
|
||||
@@ -467,6 +485,10 @@ Singleton {
|
||||
return send({"Action": {"DoScreenTransition": {"delay_ms": 0}}})
|
||||
}
|
||||
|
||||
function toggleOverview() {
|
||||
return send({"Action": {"ToggleOverview": {}}})
|
||||
}
|
||||
|
||||
function switchToWorkspace(workspaceIndex) {
|
||||
return send({"Action": {"FocusWorkspace": {"reference": {"Index": workspaceIndex}}}})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import Qt.labs.folderlistmodel
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
@@ -15,6 +16,7 @@ Singleton {
|
||||
property var loadedPlugins: ({})
|
||||
property var pluginWidgetComponents: ({})
|
||||
property var pluginDaemonComponents: ({})
|
||||
property var pluginLauncherComponents: ({})
|
||||
property string pluginDirectory: {
|
||||
var configDir = StandardPaths.writableLocation(StandardPaths.ConfigLocation)
|
||||
var configDirStr = configDir.toString()
|
||||
@@ -24,197 +26,202 @@ Singleton {
|
||||
return configDirStr + "/DankMaterialShell/plugins"
|
||||
}
|
||||
property string systemPluginDirectory: "/etc/xdg/quickshell/dms-plugins"
|
||||
property var pluginDirectories: [pluginDirectory, systemPluginDirectory]
|
||||
|
||||
property var knownManifests: ({})
|
||||
property var pathToPluginId: ({})
|
||||
property var pluginInstances: ({})
|
||||
|
||||
signal pluginLoaded(string pluginId)
|
||||
signal pluginUnloaded(string pluginId)
|
||||
signal pluginLoadFailed(string pluginId, string error)
|
||||
signal pluginDataChanged(string pluginId)
|
||||
signal pluginListUpdated()
|
||||
|
||||
Timer {
|
||||
id: resyncDebounce
|
||||
interval: 120
|
||||
repeat: false
|
||||
onTriggered: resyncAll()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(initializePlugins)
|
||||
userWatcher.folder = Paths.toFileUrl(root.pluginDirectory)
|
||||
systemWatcher.folder = Paths.toFileUrl(root.systemPluginDirectory)
|
||||
Qt.callLater(resyncAll)
|
||||
}
|
||||
|
||||
function initializePlugins() {
|
||||
scanPlugins()
|
||||
FolderListModel {
|
||||
id: userWatcher
|
||||
showDirs: true
|
||||
showFiles: false
|
||||
showDotAndDotDot: false
|
||||
nameFilters: ["plugin.json"]
|
||||
|
||||
onCountChanged: resyncDebounce.restart()
|
||||
onStatusChanged: if (status === FolderListModel.Ready) resyncDebounce.restart()
|
||||
}
|
||||
|
||||
property int currentScanIndex: 0
|
||||
property var scanResults: []
|
||||
property var foundPlugins: ({})
|
||||
FolderListModel {
|
||||
id: systemWatcher
|
||||
showDirs: true
|
||||
showFiles: false
|
||||
showDotAndDotDot: false
|
||||
nameFilters: ["plugin.json"]
|
||||
|
||||
property var lsProcess: Process {
|
||||
id: dirScanner
|
||||
onCountChanged: resyncDebounce.restart()
|
||||
onStatusChanged: if (status === FolderListModel.Ready) resyncDebounce.restart()
|
||||
}
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
var output = text.trim()
|
||||
var currentDir = pluginDirectories[currentScanIndex]
|
||||
if (output) {
|
||||
var directories = output.split('\n')
|
||||
for (var i = 0; i < directories.length; i++) {
|
||||
var dir = directories[i].trim()
|
||||
if (dir) {
|
||||
var manifestPath = currentDir + "/" + dir + "/plugin.json"
|
||||
loadPluginManifest(manifestPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
function snapshotModel(model, sourceTag) {
|
||||
const out = []
|
||||
const n = model.count
|
||||
const baseDir = sourceTag === "user" ? pluginDirectory : systemPluginDirectory
|
||||
for (let i = 0; i < n; i++) {
|
||||
let dirPath = model.get(i, "filePath")
|
||||
if (dirPath.startsWith("file://")) {
|
||||
dirPath = dirPath.substring(7)
|
||||
}
|
||||
if (!dirPath.startsWith(baseDir)) {
|
||||
continue
|
||||
}
|
||||
const manifestPath = dirPath + "/plugin.json"
|
||||
out.push({ path: manifestPath, source: sourceTag })
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function resyncAll() {
|
||||
const userList = snapshotModel(userWatcher, "user")
|
||||
const sysList = snapshotModel(systemWatcher, "system")
|
||||
const seenPaths = {}
|
||||
|
||||
function consider(entry) {
|
||||
const key = entry.path
|
||||
seenPaths[key] = true
|
||||
const prev = knownManifests[key]
|
||||
if (!prev) {
|
||||
loadPluginManifestFile(entry.path, entry.source, Date.now())
|
||||
}
|
||||
}
|
||||
for (let i=0;i<userList.length;i++) consider(userList[i])
|
||||
for (let i=0;i<sysList.length;i++) consider(sysList[i])
|
||||
|
||||
onExited: function(exitCode) {
|
||||
currentScanIndex++
|
||||
if (currentScanIndex < pluginDirectories.length) {
|
||||
scanNextDirectory()
|
||||
} else {
|
||||
currentScanIndex = 0
|
||||
cleanupRemovedPlugins()
|
||||
}
|
||||
const removed = []
|
||||
for (const path in knownManifests) {
|
||||
if (!seenPaths[path]) removed.push(path)
|
||||
}
|
||||
}
|
||||
|
||||
function scanPlugins() {
|
||||
currentScanIndex = 0
|
||||
foundPlugins = {}
|
||||
scanNextDirectory()
|
||||
}
|
||||
|
||||
function scanNextDirectory() {
|
||||
var dir = pluginDirectories[currentScanIndex]
|
||||
lsProcess.command = ["find", "-L", dir, "-maxdepth", "1", "-type", "d", "-not", "-path", dir, "-exec", "basename", "{}", ";"]
|
||||
lsProcess.running = true
|
||||
}
|
||||
|
||||
property var manifestReaders: ({})
|
||||
|
||||
function loadPluginManifest(manifestPath) {
|
||||
var readerId = "reader_" + Date.now() + "_" + Math.random()
|
||||
|
||||
var checkProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { stdout: StdioCollector { } }")
|
||||
if (checkProcess.status === Component.Ready) {
|
||||
var checker = checkProcess.createObject(root)
|
||||
checker.command = ["test", "-f", manifestPath]
|
||||
checker.exited.connect(function(exitCode) {
|
||||
if (exitCode !== 0) {
|
||||
checker.destroy()
|
||||
delete manifestReaders[readerId]
|
||||
return
|
||||
if (removed.length) {
|
||||
removed.forEach(function(path) {
|
||||
const pid = pathToPluginId[path]
|
||||
if (pid) {
|
||||
unregisterPluginByPath(path, pid)
|
||||
}
|
||||
|
||||
var catProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { stdout: StdioCollector { } }")
|
||||
if (catProcess.status === Component.Ready) {
|
||||
var process = catProcess.createObject(root)
|
||||
process.command = ["cat", manifestPath]
|
||||
process.stdout.streamFinished.connect(function() {
|
||||
try {
|
||||
var manifest = JSON.parse(process.stdout.text.trim())
|
||||
processManifest(manifest, manifestPath)
|
||||
} catch (e) {
|
||||
console.error("PluginService: Failed to parse manifest", manifestPath, ":", e.message)
|
||||
}
|
||||
process.destroy()
|
||||
delete manifestReaders[readerId]
|
||||
})
|
||||
process.exited.connect(function(exitCode) {
|
||||
if (exitCode !== 0) {
|
||||
console.error("PluginService: Failed to read manifest file:", manifestPath, "exit code:", exitCode)
|
||||
process.destroy()
|
||||
delete manifestReaders[readerId]
|
||||
}
|
||||
})
|
||||
manifestReaders[readerId] = process
|
||||
process.running = true
|
||||
} else {
|
||||
console.error("PluginService: Failed to create manifest reader process")
|
||||
}
|
||||
|
||||
checker.destroy()
|
||||
delete knownManifests[path]
|
||||
delete pathToPluginId[path]
|
||||
})
|
||||
manifestReaders[readerId] = checker
|
||||
checker.running = true
|
||||
} else {
|
||||
console.error("PluginService: Failed to create file check process")
|
||||
pluginListUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
function processManifest(manifest, manifestPath) {
|
||||
registerPlugin(manifest, manifestPath)
|
||||
|
||||
var enabled = SettingsData.getPluginSetting(manifest.id, "enabled", false)
|
||||
if (enabled) {
|
||||
loadPlugin(manifest.id)
|
||||
}
|
||||
function loadPluginManifestFile(manifestPathNoScheme, sourceTag, mtimeEpochMs) {
|
||||
const manifestId = "m_" + Math.random().toString(36).slice(2)
|
||||
const qml = `
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
FileView {
|
||||
id: fv
|
||||
property string absPath: ""
|
||||
onLoaded: {
|
||||
try {
|
||||
let raw = text()
|
||||
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1)
|
||||
const manifest = JSON.parse(raw)
|
||||
root._onManifestParsed(absPath, manifest, "${sourceTag}", ${mtimeEpochMs})
|
||||
} catch (e) {
|
||||
console.error("PluginService: bad manifest", absPath, e.message)
|
||||
knownManifests[absPath] = { mtime: ${mtimeEpochMs}, source: "${sourceTag}", bad: true }
|
||||
}
|
||||
fv.destroy()
|
||||
}
|
||||
onLoadFailed: (err) => {
|
||||
console.warn("PluginService: manifest load failed", absPath, err)
|
||||
fv.destroy()
|
||||
}
|
||||
}
|
||||
`
|
||||
const loader = Qt.createQmlObject(qml, root, "mf_" + manifestId)
|
||||
loader.absPath = manifestPathNoScheme
|
||||
loader.path = manifestPathNoScheme
|
||||
}
|
||||
|
||||
function registerPlugin(manifest, manifestPath) {
|
||||
if (!manifest.id || !manifest.name || !manifest.component) {
|
||||
console.error("PluginService: Invalid manifest, missing required fields:", manifestPath)
|
||||
function _onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs) {
|
||||
if (!manifest || !manifest.id || !manifest.name || !manifest.component) {
|
||||
console.error("PluginService: invalid manifest fields:", absPath)
|
||||
knownManifests[absPath] = { mtime: mtimeEpochMs, source: sourceTag, bad: true }
|
||||
return
|
||||
}
|
||||
|
||||
var pluginDir = manifestPath.substring(0, manifestPath.lastIndexOf('/'))
|
||||
const dir = absPath.substring(0, absPath.lastIndexOf('/'))
|
||||
let comp = manifest.component
|
||||
if (comp.startsWith("./")) comp = comp.slice(2)
|
||||
let settings = manifest.settings
|
||||
if (settings && settings.startsWith("./")) settings = settings.slice(2)
|
||||
|
||||
// Clean up relative paths by removing './' prefix
|
||||
var componentFile = manifest.component
|
||||
if (componentFile.startsWith('./')) {
|
||||
componentFile = componentFile.substring(2)
|
||||
const info = {}
|
||||
for (const k in manifest) info[k] = manifest[k]
|
||||
|
||||
let perms = manifest.permissions
|
||||
if (typeof perms === "string") {
|
||||
perms = perms.split(/\s*,\s*/)
|
||||
}
|
||||
|
||||
var settingsFile = manifest.settings
|
||||
if (settingsFile && settingsFile.startsWith('./')) {
|
||||
settingsFile = settingsFile.substring(2)
|
||||
if (!Array.isArray(perms)) {
|
||||
perms = []
|
||||
}
|
||||
info.permissions = perms.map(p => String(p).trim())
|
||||
|
||||
var pluginInfo = {}
|
||||
for (var key in manifest) {
|
||||
pluginInfo[key] = manifest[key]
|
||||
info.manifestPath = absPath
|
||||
info.pluginDirectory = dir
|
||||
info.componentPath = dir + "/" + comp
|
||||
info.settingsPath = settings ? (dir + "/" + settings) : null
|
||||
info.loaded = isPluginLoaded(manifest.id)
|
||||
info.type = manifest.type || "widget"
|
||||
info.source = sourceTag
|
||||
|
||||
const existing = availablePlugins[manifest.id]
|
||||
const shouldReplace =
|
||||
(!existing) ||
|
||||
(existing && existing.source === "system" && sourceTag === "user")
|
||||
|
||||
if (shouldReplace) {
|
||||
if (existing && existing.loaded && existing.source !== sourceTag) {
|
||||
unloadPlugin(manifest.id)
|
||||
}
|
||||
const newMap = Object.assign({}, availablePlugins)
|
||||
newMap[manifest.id] = info
|
||||
availablePlugins = newMap
|
||||
pathToPluginId[absPath] = manifest.id
|
||||
knownManifests[absPath] = { mtime: mtimeEpochMs, source: sourceTag }
|
||||
pluginListUpdated()
|
||||
const enabled = SettingsData.getPluginSetting(manifest.id, "enabled", false)
|
||||
if (enabled && !info.loaded) loadPlugin(manifest.id)
|
||||
} else {
|
||||
knownManifests[absPath] = { mtime: mtimeEpochMs, source: sourceTag, shadowedBy: existing.source }
|
||||
pathToPluginId[absPath] = manifest.id
|
||||
}
|
||||
pluginInfo.manifestPath = manifestPath
|
||||
pluginInfo.pluginDirectory = pluginDir
|
||||
pluginInfo.componentPath = pluginDir + '/' + componentFile
|
||||
pluginInfo.settingsPath = settingsFile ? pluginDir + '/' + settingsFile : null
|
||||
pluginInfo.loaded = false
|
||||
pluginInfo.type = manifest.type || "widget"
|
||||
|
||||
var newPlugins = Object.assign({}, availablePlugins)
|
||||
newPlugins[manifest.id] = pluginInfo
|
||||
availablePlugins = newPlugins
|
||||
foundPlugins[manifest.id] = true
|
||||
}
|
||||
|
||||
function hasPermission(pluginId, permission) {
|
||||
var plugin = availablePlugins[pluginId]
|
||||
if (!plugin) {
|
||||
return false
|
||||
}
|
||||
var permissions = plugin.permissions || []
|
||||
return permissions.indexOf(permission) !== -1
|
||||
}
|
||||
|
||||
function cleanupRemovedPlugins() {
|
||||
var pluginsToRemove = []
|
||||
for (var pluginId in availablePlugins) {
|
||||
if (!foundPlugins[pluginId]) {
|
||||
pluginsToRemove.push(pluginId)
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginsToRemove.length > 0) {
|
||||
var newPlugins = Object.assign({}, availablePlugins)
|
||||
for (var i = 0; i < pluginsToRemove.length; i++) {
|
||||
var pluginId = pluginsToRemove[i]
|
||||
if (isPluginLoaded(pluginId)) {
|
||||
unloadPlugin(pluginId)
|
||||
}
|
||||
delete newPlugins[pluginId]
|
||||
}
|
||||
availablePlugins = newPlugins
|
||||
function unregisterPluginByPath(absPath, pluginId) {
|
||||
const current = availablePlugins[pluginId]
|
||||
if (current && current.manifestPath === absPath) {
|
||||
if (current.loaded) unloadPlugin(pluginId)
|
||||
const newMap = Object.assign({}, availablePlugins)
|
||||
delete newMap[pluginId]
|
||||
availablePlugins = newMap
|
||||
}
|
||||
}
|
||||
|
||||
function loadPlugin(pluginId) {
|
||||
var plugin = availablePlugins[pluginId]
|
||||
const plugin = availablePlugins[pluginId]
|
||||
if (!plugin) {
|
||||
console.error("PluginService: Plugin not found:", pluginId)
|
||||
pluginLoadFailed(pluginId, "Plugin not found")
|
||||
@@ -225,48 +232,48 @@ Singleton {
|
||||
return true
|
||||
}
|
||||
|
||||
var isDaemon = plugin.type === "daemon"
|
||||
var componentMap = isDaemon ? pluginDaemonComponents : pluginWidgetComponents
|
||||
const isDaemon = plugin.type === "daemon"
|
||||
const isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"))
|
||||
const map = isDaemon ? pluginDaemonComponents : isLauncher ? pluginLauncherComponents : pluginWidgetComponents
|
||||
|
||||
if (componentMap[pluginId]) {
|
||||
componentMap[pluginId]?.destroy()
|
||||
if (isDaemon) {
|
||||
var newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
delete newDaemons[pluginId]
|
||||
pluginDaemonComponents = newDaemons
|
||||
} else {
|
||||
var newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
delete newComponents[pluginId]
|
||||
pluginWidgetComponents = newComponents
|
||||
}
|
||||
const prevInstance = pluginInstances[pluginId]
|
||||
if (prevInstance) {
|
||||
prevInstance.destroy()
|
||||
const newInstances = Object.assign({}, pluginInstances)
|
||||
delete newInstances[pluginId]
|
||||
pluginInstances = newInstances
|
||||
}
|
||||
|
||||
try {
|
||||
var componentUrl = "file://" + plugin.componentPath
|
||||
var component = Qt.createComponent(componentUrl, Component.PreferSynchronous)
|
||||
|
||||
if (component.status === Component.Loading) {
|
||||
component.statusChanged.connect(function() {
|
||||
if (component.status === Component.Error) {
|
||||
console.error("PluginService: Failed to create component for plugin:", pluginId, "Error:", component.errorString())
|
||||
pluginLoadFailed(pluginId, component.errorString())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (component.status === Component.Error) {
|
||||
console.error("PluginService: Failed to create component for plugin:", pluginId, "Error:", component.errorString())
|
||||
pluginLoadFailed(pluginId, component.errorString())
|
||||
const url = "file://" + plugin.componentPath
|
||||
const comp = Qt.createComponent(url, Component.PreferSynchronous)
|
||||
if (comp.status === Component.Error) {
|
||||
console.error("PluginService: component error", pluginId, comp.errorString())
|
||||
pluginLoadFailed(pluginId, comp.errorString())
|
||||
return false
|
||||
}
|
||||
|
||||
if (isDaemon) {
|
||||
var newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
newDaemons[pluginId] = component
|
||||
const instance = comp.createObject(root, { "pluginId": pluginId })
|
||||
if (!instance) {
|
||||
console.error("PluginService: failed to instantiate daemon:", pluginId, comp.errorString())
|
||||
pluginLoadFailed(pluginId, comp.errorString())
|
||||
return false
|
||||
}
|
||||
const newInstances = Object.assign({}, pluginInstances)
|
||||
newInstances[pluginId] = instance
|
||||
pluginInstances = newInstances
|
||||
|
||||
const newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
newDaemons[pluginId] = comp
|
||||
pluginDaemonComponents = newDaemons
|
||||
} else if (isLauncher) {
|
||||
const newLaunchers = Object.assign({}, pluginLauncherComponents)
|
||||
newLaunchers[pluginId] = comp
|
||||
pluginLauncherComponents = newLaunchers
|
||||
} else {
|
||||
var newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
newComponents[pluginId] = component
|
||||
const newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
newComponents[pluginId] = comp
|
||||
pluginWidgetComponents = newComponents
|
||||
}
|
||||
|
||||
@@ -276,31 +283,42 @@ Singleton {
|
||||
pluginLoaded(pluginId)
|
||||
return true
|
||||
|
||||
} catch (error) {
|
||||
console.error("PluginService: Error loading plugin:", pluginId, "Error:", error.message)
|
||||
pluginLoadFailed(pluginId, error.message)
|
||||
} catch (e) {
|
||||
console.error("PluginService: Error loading plugin:", pluginId, e.message)
|
||||
pluginLoadFailed(pluginId, e.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function unloadPlugin(pluginId) {
|
||||
var plugin = loadedPlugins[pluginId]
|
||||
const plugin = loadedPlugins[pluginId]
|
||||
if (!plugin) {
|
||||
console.warn("PluginService: Plugin not loaded:", pluginId)
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
var isDaemon = plugin.type === "daemon"
|
||||
const isDaemon = plugin.type === "daemon"
|
||||
const isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"))
|
||||
|
||||
const instance = pluginInstances[pluginId]
|
||||
if (instance) {
|
||||
instance.destroy()
|
||||
const newInstances = Object.assign({}, pluginInstances)
|
||||
delete newInstances[pluginId]
|
||||
pluginInstances = newInstances
|
||||
}
|
||||
|
||||
if (isDaemon && pluginDaemonComponents[pluginId]) {
|
||||
pluginDaemonComponents[pluginId]?.destroy()
|
||||
var newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
const newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
delete newDaemons[pluginId]
|
||||
pluginDaemonComponents = newDaemons
|
||||
} else if (isLauncher && pluginLauncherComponents[pluginId]) {
|
||||
const newLaunchers = Object.assign({}, pluginLauncherComponents)
|
||||
delete newLaunchers[pluginId]
|
||||
pluginLauncherComponents = newLaunchers
|
||||
} else if (pluginWidgetComponents[pluginId]) {
|
||||
pluginWidgetComponents[pluginId]?.destroy()
|
||||
var newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
const newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
delete newComponents[pluginId]
|
||||
pluginWidgetComponents = newComponents
|
||||
}
|
||||
@@ -326,30 +344,30 @@ Singleton {
|
||||
}
|
||||
|
||||
function getAvailablePlugins() {
|
||||
var result = []
|
||||
for (var key in availablePlugins) {
|
||||
const result = []
|
||||
for (const key in availablePlugins) {
|
||||
result.push(availablePlugins[key])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getPluginVariants(pluginId) {
|
||||
var plugin = availablePlugins[pluginId]
|
||||
const plugin = availablePlugins[pluginId]
|
||||
if (!plugin) {
|
||||
return []
|
||||
}
|
||||
var variants = SettingsData.getPluginSetting(pluginId, "variants", [])
|
||||
const variants = SettingsData.getPluginSetting(pluginId, "variants", [])
|
||||
return variants
|
||||
}
|
||||
|
||||
function getAllPluginVariants() {
|
||||
var result = []
|
||||
for (var pluginId in availablePlugins) {
|
||||
var plugin = availablePlugins[pluginId]
|
||||
const result = []
|
||||
for (const pluginId in availablePlugins) {
|
||||
const plugin = availablePlugins[pluginId]
|
||||
if (plugin.type !== "widget") {
|
||||
continue
|
||||
}
|
||||
var variants = getPluginVariants(pluginId)
|
||||
const variants = getPluginVariants(pluginId)
|
||||
if (variants.length === 0) {
|
||||
result.push({
|
||||
pluginId: pluginId,
|
||||
@@ -361,8 +379,8 @@ Singleton {
|
||||
loaded: plugin.loaded
|
||||
})
|
||||
} else {
|
||||
for (var i = 0; i < variants.length; i++) {
|
||||
var variant = variants[i]
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
const variant = variants[i]
|
||||
result.push({
|
||||
pluginId: pluginId,
|
||||
variantId: variant.id,
|
||||
@@ -379,9 +397,9 @@ Singleton {
|
||||
}
|
||||
|
||||
function createPluginVariant(pluginId, variantName, variantConfig) {
|
||||
var variants = getPluginVariants(pluginId)
|
||||
var variantId = "variant_" + Date.now()
|
||||
var newVariant = Object.assign({}, variantConfig, {
|
||||
const variants = getPluginVariants(pluginId)
|
||||
const variantId = "variant_" + Date.now()
|
||||
const newVariant = Object.assign({}, variantConfig, {
|
||||
id: variantId,
|
||||
name: variantName
|
||||
})
|
||||
@@ -392,15 +410,44 @@ Singleton {
|
||||
}
|
||||
|
||||
function removePluginVariant(pluginId, variantId) {
|
||||
var variants = getPluginVariants(pluginId)
|
||||
var newVariants = variants.filter(function(v) { return v.id !== variantId })
|
||||
const variants = getPluginVariants(pluginId)
|
||||
const newVariants = variants.filter(function(v) { return v.id !== variantId })
|
||||
SettingsData.setPluginSetting(pluginId, "variants", newVariants)
|
||||
|
||||
const fullId = pluginId + ":" + variantId
|
||||
removeWidgetFromDankBar(fullId)
|
||||
|
||||
pluginDataChanged(pluginId)
|
||||
}
|
||||
|
||||
function removeWidgetFromDankBar(widgetId) {
|
||||
function filterWidget(widget) {
|
||||
const id = typeof widget === "string" ? widget : widget.id
|
||||
return id !== widgetId
|
||||
}
|
||||
|
||||
const leftWidgets = SettingsData.dankBarLeftWidgets
|
||||
const centerWidgets = SettingsData.dankBarCenterWidgets
|
||||
const rightWidgets = SettingsData.dankBarRightWidgets
|
||||
|
||||
const newLeft = leftWidgets.filter(filterWidget)
|
||||
const newCenter = centerWidgets.filter(filterWidget)
|
||||
const newRight = rightWidgets.filter(filterWidget)
|
||||
|
||||
if (newLeft.length !== leftWidgets.length) {
|
||||
SettingsData.setDankBarLeftWidgets(newLeft)
|
||||
}
|
||||
if (newCenter.length !== centerWidgets.length) {
|
||||
SettingsData.setDankBarCenterWidgets(newCenter)
|
||||
}
|
||||
if (newRight.length !== rightWidgets.length) {
|
||||
SettingsData.setDankBarRightWidgets(newRight)
|
||||
}
|
||||
}
|
||||
|
||||
function updatePluginVariant(pluginId, variantId, variantConfig) {
|
||||
var variants = getPluginVariants(pluginId)
|
||||
for (var i = 0; i < variants.length; i++) {
|
||||
const variants = getPluginVariants(pluginId)
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
if (variants[i].id === variantId) {
|
||||
variants[i] = Object.assign({}, variants[i], variantConfig)
|
||||
break
|
||||
@@ -411,8 +458,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function getPluginVariantData(pluginId, variantId) {
|
||||
var variants = getPluginVariants(pluginId)
|
||||
for (var i = 0; i < variants.length; i++) {
|
||||
const variants = getPluginVariants(pluginId)
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
if (variants[i].id === variantId) {
|
||||
return variants[i]
|
||||
}
|
||||
@@ -421,8 +468,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function getLoadedPlugins() {
|
||||
var result = []
|
||||
for (var key in loadedPlugins) {
|
||||
const result = []
|
||||
for (const key in loadedPlugins) {
|
||||
result.push(loadedPlugins[key])
|
||||
}
|
||||
return result
|
||||
@@ -463,10 +510,14 @@ Singleton {
|
||||
SettingsData.savePluginSettings()
|
||||
}
|
||||
|
||||
function scanPlugins() {
|
||||
resyncDebounce.restart()
|
||||
}
|
||||
|
||||
function createPluginDirectory() {
|
||||
var mkdirProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { }")
|
||||
const mkdirProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { }")
|
||||
if (mkdirProcess.status === Component.Ready) {
|
||||
var process = mkdirProcess.createObject(root)
|
||||
const process = mkdirProcess.createObject(root)
|
||||
process.command = ["mkdir", "-p", pluginDirectory]
|
||||
process.exited.connect(function(exitCode) {
|
||||
if (exitCode !== 0) {
|
||||
@@ -481,4 +532,61 @@ Singleton {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Launcher plugin helper functions
|
||||
function getLauncherPlugins() {
|
||||
const launchers = {}
|
||||
|
||||
// Check plugins that have launcher components
|
||||
for (const pluginId in pluginLauncherComponents) {
|
||||
const plugin = availablePlugins[pluginId]
|
||||
if (plugin && plugin.loaded) {
|
||||
launchers[pluginId] = plugin
|
||||
}
|
||||
}
|
||||
return launchers
|
||||
}
|
||||
|
||||
function getLauncherPlugin(pluginId) {
|
||||
const plugin = availablePlugins[pluginId]
|
||||
if (plugin && plugin.loaded && pluginLauncherComponents[pluginId]) {
|
||||
return plugin
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getPluginTrigger(pluginId) {
|
||||
const plugin = getLauncherPlugin(pluginId)
|
||||
if (plugin) {
|
||||
const customTrigger = SettingsData.getPluginSetting(pluginId, "trigger", plugin.trigger || "!")
|
||||
return customTrigger
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getAllPluginTriggers() {
|
||||
const triggers = {}
|
||||
const launchers = getLauncherPlugins()
|
||||
|
||||
for (const pluginId in launchers) {
|
||||
const trigger = getPluginTrigger(pluginId)
|
||||
if (trigger && trigger.trim() !== "") {
|
||||
triggers[trigger] = pluginId
|
||||
}
|
||||
}
|
||||
return triggers
|
||||
}
|
||||
|
||||
function getPluginsWithEmptyTrigger() {
|
||||
const plugins = []
|
||||
const launchers = getLauncherPlugins()
|
||||
|
||||
for (const pluginId in launchers) {
|
||||
const trigger = getPluginTrigger(pluginId)
|
||||
if (!trigger || trigger.trim() === "") {
|
||||
plugins.push(pluginId)
|
||||
}
|
||||
}
|
||||
return plugins
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,24 +22,22 @@ Singleton {
|
||||
function init() {}
|
||||
|
||||
function getSystemProfileImage() {
|
||||
if (freedeskAvailable) {
|
||||
const username = Quickshell.env("USER")
|
||||
if (!username) return
|
||||
if (!freedeskAvailable) return
|
||||
|
||||
DMSService.sendRequest("freedesktop.accounts.getUserIconFile", { username: username }, response => {
|
||||
if (response.result && response.result.success) {
|
||||
const iconFile = response.result.value || ""
|
||||
if (iconFile && iconFile !== "" && iconFile !== "/var/lib/AccountsService/icons/") {
|
||||
systemProfileImage = iconFile
|
||||
if (!profileImage || profileImage === "") {
|
||||
profileImage = iconFile
|
||||
}
|
||||
const username = Quickshell.env("USER")
|
||||
if (!username) return
|
||||
|
||||
DMSService.sendRequest("freedesktop.accounts.getUserIconFile", { username: username }, response => {
|
||||
if (response.result && response.result.success) {
|
||||
const iconFile = response.result.value || ""
|
||||
if (iconFile && iconFile !== "" && iconFile !== "/var/lib/AccountsService/icons/") {
|
||||
systemProfileImage = iconFile
|
||||
if (!profileImage || profileImage === "") {
|
||||
profileImage = iconFile
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
systemProfileCheckProcess.running = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getUserProfileImage(username) {
|
||||
@@ -52,26 +50,23 @@ Singleton {
|
||||
return
|
||||
}
|
||||
|
||||
if (freedeskAvailable) {
|
||||
DMSService.sendRequest("freedesktop.accounts.getUserIconFile", { username: username }, response => {
|
||||
if (response.result && response.result.success) {
|
||||
const icon = response.result.value || ""
|
||||
if (icon && icon !== "" && icon !== "/var/lib/AccountsService/icons/") {
|
||||
profileImage = icon
|
||||
} else {
|
||||
profileImage = ""
|
||||
}
|
||||
if (!freedeskAvailable) {
|
||||
profileImage = ""
|
||||
return
|
||||
}
|
||||
|
||||
DMSService.sendRequest("freedesktop.accounts.getUserIconFile", { username: username }, response => {
|
||||
if (response.result && response.result.success) {
|
||||
const icon = response.result.value || ""
|
||||
if (icon && icon !== "" && icon !== "/var/lib/AccountsService/icons/") {
|
||||
profileImage = icon
|
||||
} else {
|
||||
profileImage = ""
|
||||
}
|
||||
})
|
||||
} else {
|
||||
userProfileCheckProcess.command = [
|
||||
"bash", "-c",
|
||||
`uid=$(id -u ${username} 2>/dev/null) && [ -n "$uid" ] && dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$uid org.freedesktop.DBus.Properties.Get string:org.freedesktop.Accounts.User string:IconFile 2>/dev/null | grep -oP 'string "\\K[^"]+' || echo ""`
|
||||
]
|
||||
userProfileCheckProcess.running = true
|
||||
}
|
||||
} else {
|
||||
profileImage = ""
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setProfileImage(imagePath) {
|
||||
@@ -86,25 +81,23 @@ Singleton {
|
||||
}
|
||||
|
||||
function getSystemColorScheme() {
|
||||
if (freedeskAvailable) {
|
||||
DMSService.sendRequest("freedesktop.settings.getColorScheme", null, response => {
|
||||
if (response.result) {
|
||||
systemColorScheme = response.result.value || 0
|
||||
if (!freedeskAvailable) return
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
const shouldBeLightMode = (systemColorScheme === 2)
|
||||
if (Theme.isLightMode !== shouldBeLightMode) {
|
||||
Theme.isLightMode = shouldBeLightMode
|
||||
if (typeof SessionData !== "undefined") {
|
||||
SessionData.setLightMode(shouldBeLightMode)
|
||||
}
|
||||
DMSService.sendRequest("freedesktop.settings.getColorScheme", null, response => {
|
||||
if (response.result) {
|
||||
systemColorScheme = response.result.value || 0
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
const shouldBeLightMode = (systemColorScheme === 2)
|
||||
if (Theme.isLightMode !== shouldBeLightMode) {
|
||||
Theme.isLightMode = shouldBeLightMode
|
||||
if (typeof SessionData !== "undefined") {
|
||||
SessionData.setLightMode(shouldBeLightMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
systemColorSchemeCheckProcess.running = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setLightMode(isLightMode) {
|
||||
@@ -114,49 +107,42 @@ Singleton {
|
||||
}
|
||||
|
||||
function setSystemColorScheme(isLightMode) {
|
||||
if (!settingsPortalAvailable) return
|
||||
if (!settingsPortalAvailable || !freedeskAvailable) return
|
||||
|
||||
const colorScheme = isLightMode ? "default" : "prefer-dark"
|
||||
colorSchemeSetProcess.command = ["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", colorScheme]
|
||||
colorSchemeSetProcess.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: colorSchemeSetProcess
|
||||
running: false
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
DMSService.sendRequest("freedesktop.settings.setColorScheme", { preferDark: !isLightMode }, response => {
|
||||
if (!response.error) {
|
||||
Qt.callLater(() => getSystemColorScheme())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setSystemIconTheme(themeName) {
|
||||
if (!settingsPortalAvailable || !freedeskAvailable) return
|
||||
|
||||
DMSService.sendRequest("freedesktop.settings.setIconTheme", { iconTheme: themeName }, response => {
|
||||
if (response.error) {
|
||||
console.warn("PortalService: Failed to set icon theme:", response.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setSystemProfileImage(imagePath) {
|
||||
if (!accountsServiceAvailable) return
|
||||
if (!accountsServiceAvailable || !freedeskAvailable) return
|
||||
|
||||
if (freedeskAvailable) {
|
||||
DMSService.sendRequest("freedesktop.accounts.setIconFile", { path: imagePath || "" }, response => {
|
||||
if (response.error) {
|
||||
console.warn("PortalService: Failed to set icon file:", response.error)
|
||||
} else {
|
||||
Qt.callLater(() => getSystemProfileImage())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const path = imagePath || ""
|
||||
systemProfileSetProcess.command = ["bash", "-c", `dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$(id -u) org.freedesktop.Accounts.User.SetIconFile string:'${path}'`]
|
||||
systemProfileSetProcess.running = true
|
||||
}
|
||||
DMSService.sendRequest("freedesktop.accounts.setIconFile", { path: imagePath || "" }, response => {
|
||||
if (response.error) {
|
||||
console.warn("PortalService: Failed to set icon file:", response.error)
|
||||
} else {
|
||||
Qt.callLater(() => getSystemProfileImage())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
checkDMSCapabilities()
|
||||
} else {
|
||||
console.log("PortalService: DMS_SOCKET not set, using fallback methods")
|
||||
checkAccountsServiceFallback()
|
||||
checkSettingsPortalFallback()
|
||||
console.log("PortalService: DMS_SOCKET not set")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,9 +179,7 @@ Singleton {
|
||||
checkAccountsService()
|
||||
checkSettingsPortal()
|
||||
} else {
|
||||
console.log("PortalService: freedesktop capability not available in DMS, using fallback methods")
|
||||
checkAccountsServiceFallback()
|
||||
checkSettingsPortalFallback()
|
||||
console.log("PortalService: freedesktop capability not available in DMS")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,14 +209,6 @@ Singleton {
|
||||
})
|
||||
}
|
||||
|
||||
function checkAccountsServiceFallback() {
|
||||
accountsServiceCheckProcess.running = true
|
||||
}
|
||||
|
||||
function checkSettingsPortalFallback() {
|
||||
settingsPortalCheckProcess.running = true
|
||||
}
|
||||
|
||||
function getGreeterUserProfileImage(username) {
|
||||
if (!username) {
|
||||
profileImage = ""
|
||||
@@ -245,54 +221,6 @@ Singleton {
|
||||
userProfileCheckProcess.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: accountsServiceCheckProcess
|
||||
command: ["bash", "-c", "dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts org.freedesktop.Accounts.FindUserByName string:\"$USER\""]
|
||||
running: false
|
||||
|
||||
onExited: exitCode => {
|
||||
accountsServiceAvailable = (exitCode === 0)
|
||||
if (accountsServiceAvailable) {
|
||||
getSystemProfileImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: systemProfileCheckProcess
|
||||
command: ["bash", "-c", "dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$(id -u) org.freedesktop.DBus.Properties.Get string:org.freedesktop.Accounts.User string:IconFile"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const match = text.match(/string\s+"([^"]+)"/)
|
||||
if (match && match[1] && match[1] !== "" && match[1] !== "/var/lib/AccountsService/icons/") {
|
||||
systemProfileImage = match[1]
|
||||
if (!profileImage || profileImage === "") {
|
||||
profileImage = systemProfileImage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0) {
|
||||
systemProfileImage = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: systemProfileSetProcess
|
||||
running: false
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
getSystemProfileImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: userProfileCheckProcess
|
||||
command: []
|
||||
@@ -316,50 +244,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: settingsPortalCheckProcess
|
||||
command: ["gdbus", "call", "--session", "--dest", "org.freedesktop.portal.Desktop", "--object-path", "/org/freedesktop/portal/desktop", "--method", "org.freedesktop.portal.Settings.ReadOne", "org.freedesktop.appearance", "color-scheme"]
|
||||
running: false
|
||||
|
||||
onExited: exitCode => {
|
||||
settingsPortalAvailable = (exitCode === 0)
|
||||
if (settingsPortalAvailable) {
|
||||
getSystemColorScheme()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: systemColorSchemeCheckProcess
|
||||
command: ["gdbus", "call", "--session", "--dest", "org.freedesktop.portal.Desktop", "--object-path", "/org/freedesktop/portal/desktop", "--method", "org.freedesktop.portal.Settings.ReadOne", "org.freedesktop.appearance", "color-scheme"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const match = text.match(/uint32 (\d+)/)
|
||||
if (match && match[1]) {
|
||||
systemColorScheme = parseInt(match[1])
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
const shouldBeLightMode = (systemColorScheme === 2)
|
||||
if (Theme.isLightMode !== shouldBeLightMode) {
|
||||
Theme.isLightMode = shouldBeLightMode
|
||||
if (typeof SessionData !== "undefined") {
|
||||
SessionData.setLightMode(shouldBeLightMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0) {
|
||||
systemColorScheme = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "profile"
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ Singleton {
|
||||
signal prepareForSleep()
|
||||
signal loginctlStateChanged()
|
||||
|
||||
property bool subscriptionConnected: false
|
||||
property bool stateInitialized: false
|
||||
|
||||
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
|
||||
@@ -61,11 +60,14 @@ Singleton {
|
||||
detectHibernateProcess.running = true
|
||||
detectPrimeRunProcess.running = true
|
||||
console.log("SessionService: Native inhibitor available:", nativeInhibitorAvailable)
|
||||
if (!SessionData.loginctlLockIntegration) {
|
||||
console.log("SessionService: loginctl lock integration disabled by user")
|
||||
return
|
||||
}
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
checkDMSCapabilities()
|
||||
} else {
|
||||
console.log("SessionService: DMS_SOCKET not set, using fallback")
|
||||
initFallbackLoginctl()
|
||||
console.log("SessionService: DMS_SOCKET not set")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,46 +293,41 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: subscriptionSocket
|
||||
path: root.socketPath
|
||||
connected: loginctlAvailable
|
||||
Connections {
|
||||
target: SessionData
|
||||
|
||||
onConnectionStateChanged: {
|
||||
root.subscriptionConnected = connected
|
||||
function onLoginctlLockIntegrationChanged() {
|
||||
if (SessionData.loginctlLockIntegration) {
|
||||
if (socketPath && socketPath.length > 0 && loginctlAvailable) {
|
||||
if (!stateInitialized) {
|
||||
stateInitialized = true
|
||||
getLoginctlState()
|
||||
syncLockBeforeSuspend()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stateInitialized = false
|
||||
}
|
||||
}
|
||||
|
||||
parser: SplitParser {
|
||||
onRead: line => {
|
||||
if (!line || line.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = JSON.parse(line)
|
||||
|
||||
if (response.capabilities) {
|
||||
Qt.callLater(() => sendSubscribeRequest())
|
||||
return
|
||||
}
|
||||
|
||||
if (response.result && response.result.type === "loginctl_event") {
|
||||
handleLoginctlEvent(response.result)
|
||||
} else if (response.result && response.result.type === "state_changed" && response.result.data) {
|
||||
updateLoginctlState(response.result.data)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("SessionService: Failed to parse subscription response:", line, e)
|
||||
}
|
||||
function onLockBeforeSuspendChanged() {
|
||||
if (SessionData.loginctlLockIntegration) {
|
||||
syncLockBeforeSuspend()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendSubscribeRequest() {
|
||||
subscriptionSocket.send({
|
||||
"id": 2,
|
||||
"method": "loginctl.subscribe"
|
||||
})
|
||||
Connections {
|
||||
target: DMSService
|
||||
enabled: SessionData.loginctlLockIntegration
|
||||
|
||||
function onLoginctlStateUpdate(data) {
|
||||
updateLoginctlState(data)
|
||||
}
|
||||
|
||||
function onLoginctlEvent(event) {
|
||||
handleLoginctlEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
function checkDMSCapabilities() {
|
||||
@@ -344,14 +341,14 @@ Singleton {
|
||||
|
||||
if (DMSService.capabilities.includes("loginctl")) {
|
||||
loginctlAvailable = true
|
||||
if (!stateInitialized) {
|
||||
if (SessionData.loginctlLockIntegration && !stateInitialized) {
|
||||
stateInitialized = true
|
||||
getLoginctlState()
|
||||
subscriptionSocket.connected = true
|
||||
syncLockBeforeSuspend()
|
||||
}
|
||||
} else {
|
||||
console.log("SessionService: loginctl capability not available in DMS, using fallback")
|
||||
initFallbackLoginctl()
|
||||
loginctlAvailable = false
|
||||
console.log("SessionService: loginctl capability not available in DMS")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +362,23 @@ Singleton {
|
||||
})
|
||||
}
|
||||
|
||||
function syncLockBeforeSuspend() {
|
||||
if (!loginctlAvailable) return
|
||||
|
||||
DMSService.sendRequest("loginctl.setLockBeforeSuspend", {
|
||||
enabled: SessionData.lockBeforeSuspend
|
||||
}, response => {
|
||||
if (response.error) {
|
||||
console.warn("SessionService: Failed to sync lock before suspend:", response.error)
|
||||
} else {
|
||||
console.log("SessionService: Synced lock before suspend:", SessionData.lockBeforeSuspend)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function updateLoginctlState(state) {
|
||||
const wasLocked = locked
|
||||
|
||||
sessionId = state.sessionId || ""
|
||||
sessionPath = state.sessionPath || ""
|
||||
locked = state.locked || false
|
||||
@@ -377,11 +390,10 @@ Singleton {
|
||||
seat = state.seat || ""
|
||||
display = state.display || ""
|
||||
|
||||
const wasPreparing = preparingForSleep
|
||||
preparingForSleep = state.preparingForSleep || false
|
||||
|
||||
if (preparingForSleep && !wasPreparing) {
|
||||
prepareForSleep()
|
||||
if (locked && !wasLocked) {
|
||||
sessionLocked()
|
||||
} else if (!locked && wasLocked) {
|
||||
sessionUnlocked()
|
||||
}
|
||||
|
||||
loginctlStateChanged()
|
||||
@@ -396,97 +408,7 @@ Singleton {
|
||||
locked = false
|
||||
lockedHint = false
|
||||
sessionUnlocked()
|
||||
} else if (event.event === "PrepareForSleep") {
|
||||
preparingForSleep = event.data?.sleeping || false
|
||||
if (preparingForSleep) {
|
||||
prepareForSleep()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initFallbackLoginctl() {
|
||||
getSessionPathFallback.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getSessionPathFallback
|
||||
command: ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", "/org/freedesktop/login1", "--method", "org.freedesktop.login1.Manager.GetSession", Quickshell.env("XDG_SESSION_ID") || "self"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const match = text.match(/objectpath '([^']+)'/)
|
||||
if (match) {
|
||||
sessionPath = match[1]
|
||||
console.log("SessionService: Found session path (fallback):", sessionPath)
|
||||
checkCurrentLockStateFallback.running = true
|
||||
lockStateMonitorFallback.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: checkCurrentLockStateFallback
|
||||
command: sessionPath ? ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", sessionPath, "--method", "org.freedesktop.DBus.Properties.Get", "org.freedesktop.login1.Session", "LockedHint"] : []
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.includes("true")) {
|
||||
locked = true
|
||||
lockedHint = true
|
||||
sessionLocked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: lockStateMonitorFallback
|
||||
command: sessionPath ? ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.login1"] : []
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: line => {
|
||||
if (sessionPath && line.includes(sessionPath)) {
|
||||
if (line.includes("org.freedesktop.login1.Session.Lock")) {
|
||||
locked = true
|
||||
lockedHint = true
|
||||
sessionLocked()
|
||||
} else if (line.includes("org.freedesktop.login1.Session.Unlock")) {
|
||||
locked = false
|
||||
lockedHint = false
|
||||
sessionUnlocked()
|
||||
} else if (line.includes("LockedHint") && line.includes("true")) {
|
||||
locked = true
|
||||
lockedHint = true
|
||||
loginctlStateChanged()
|
||||
} else if (line.includes("LockedHint") && line.includes("false")) {
|
||||
locked = false
|
||||
lockedHint = false
|
||||
loginctlStateChanged()
|
||||
}
|
||||
}
|
||||
if (line.includes("PrepareForSleep") && line.includes("true") && SessionData.lockBeforeSuspend) {
|
||||
preparingForSleep = true
|
||||
prepareForSleep()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("SessionService: gdbus monitor fallback failed, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: lockSessionFallback
|
||||
command: ["loginctl", "lock-session"]
|
||||
running: false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -176,11 +176,27 @@ Singleton {
|
||||
if (!distributionSupported || !pkgManager || updateCount === 0) return
|
||||
|
||||
const terminal = Quickshell.env("TERMINAL") || "xterm"
|
||||
const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ")
|
||||
const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : ""
|
||||
const updateCommand = `${sudo} ${pkgManager} ${params} && echo "Updates complete! Press Enter to close..." && read`
|
||||
|
||||
updater.command = [terminal, "-e", "sh", "-c", updateCommand]
|
||||
if (SettingsData.updaterUseCustomCommand && SettingsData.updaterCustomCommand.length > 0) {
|
||||
const updateCommand = `${SettingsData.updaterCustomCommand} && echo "Updates complete! Press Enter to close..." && read`
|
||||
const termClass = SettingsData.updaterTerminalAdditionalParams
|
||||
|
||||
var finalCommand = [terminal]
|
||||
if (termClass.length > 0) {
|
||||
finalCommand = finalCommand.concat(termClass.split(" "))
|
||||
}
|
||||
finalCommand.push("-e")
|
||||
finalCommand.push("sh")
|
||||
finalCommand.push("-c")
|
||||
finalCommand.push(updateCommand)
|
||||
updater.command = finalCommand
|
||||
} else {
|
||||
const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ")
|
||||
const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : ""
|
||||
const updateCommand = `${sudo} ${pkgManager} ${params} && echo "Updates complete! Press Enter to close..." && read`
|
||||
|
||||
updater.command = [terminal, "-e", "sh", "-c", updateCommand]
|
||||
}
|
||||
updater.running = true
|
||||
}
|
||||
|
||||
|
||||
@@ -16,36 +16,39 @@ Singleton {
|
||||
property bool toastVisible: false
|
||||
property var toastQueue: []
|
||||
property string currentDetails: ""
|
||||
property string currentCommand: ""
|
||||
property bool hasDetails: false
|
||||
property string wallpaperErrorStatus: ""
|
||||
|
||||
function showToast(message, level = levelInfo, details = "") {
|
||||
function showToast(message, level = levelInfo, details = "", command = "") {
|
||||
toastQueue.push({
|
||||
"message": message,
|
||||
"level": level,
|
||||
"details": details
|
||||
"details": details,
|
||||
"command": command
|
||||
})
|
||||
if (!toastVisible) {
|
||||
processQueue()
|
||||
}
|
||||
}
|
||||
|
||||
function showInfo(message, details = "") {
|
||||
showToast(message, levelInfo, details)
|
||||
function showInfo(message, details = "", command = "") {
|
||||
showToast(message, levelInfo, details, command)
|
||||
}
|
||||
|
||||
function showWarning(message, details = "") {
|
||||
showToast(message, levelWarn, details)
|
||||
function showWarning(message, details = "", command = "") {
|
||||
showToast(message, levelWarn, details, command)
|
||||
}
|
||||
|
||||
function showError(message, details = "") {
|
||||
showToast(message, levelError, details)
|
||||
function showError(message, details = "", command = "") {
|
||||
showToast(message, levelError, details, command)
|
||||
}
|
||||
|
||||
function hideToast() {
|
||||
toastVisible = false
|
||||
currentMessage = ""
|
||||
currentDetails = ""
|
||||
currentCommand = ""
|
||||
hasDetails = false
|
||||
currentLevel = levelInfo
|
||||
toastTimer.stop()
|
||||
@@ -64,7 +67,8 @@ Singleton {
|
||||
currentMessage = toast.message
|
||||
currentLevel = toast.level
|
||||
currentDetails = toast.details || ""
|
||||
hasDetails = currentDetails.length > 0
|
||||
currentCommand = toast.command || ""
|
||||
hasDetails = currentDetails.length > 0 || currentCommand.length > 0
|
||||
toastVisible = true
|
||||
resetToastState()
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: contentRow.width - (contentRow.children[0].visible ? contentRow.children[0].width + contentRow.spacing : 0)
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
@@ -69,33 +71,40 @@ PanelWindow {
|
||||
|
||||
readonly property real screenWidth: root.screen.width
|
||||
readonly property real screenHeight: root.screen.height
|
||||
readonly property real dpr: root.screen.devicePixelRatio
|
||||
readonly property real dpr: {
|
||||
if (CompositorService.isNiri && root.screen) {
|
||||
const niriScale = NiriService.displayScales[root.screen.name]
|
||||
if (niriScale !== undefined) return niriScale
|
||||
}
|
||||
if (CompositorService.isHyprland && root.screen) {
|
||||
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === root.screen.name)
|
||||
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
|
||||
}
|
||||
return root.screen?.devicePixelRatio || 1
|
||||
}
|
||||
|
||||
readonly property real calculatedX: {
|
||||
readonly property real alignedWidth: Theme.px(popupWidth, dpr)
|
||||
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
|
||||
readonly property real alignedX: Theme.snap((() => {
|
||||
if (SettingsData.dankBarPosition === SettingsData.Position.Left) {
|
||||
return triggerY
|
||||
return triggerY + SettingsData.dankBarBottomGap
|
||||
} else if (SettingsData.dankBarPosition === SettingsData.Position.Right) {
|
||||
return screenWidth - triggerY - popupWidth
|
||||
return screenWidth - triggerY - SettingsData.dankBarBottomGap - popupWidth
|
||||
} else {
|
||||
const centerX = triggerX + (triggerWidth / 2) - (popupWidth / 2)
|
||||
return Math.max(Theme.popupDistance, Math.min(screenWidth - popupWidth - Theme.popupDistance, centerX))
|
||||
}
|
||||
}
|
||||
readonly property real calculatedY: {
|
||||
})(), dpr)
|
||||
readonly property real alignedY: Theme.snap((() => {
|
||||
if (SettingsData.dankBarPosition === SettingsData.Position.Left || SettingsData.dankBarPosition === SettingsData.Position.Right) {
|
||||
const centerY = triggerX + (triggerWidth / 2) - (popupHeight / 2)
|
||||
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, centerY))
|
||||
} else if (SettingsData.dankBarPosition === SettingsData.Position.Bottom) {
|
||||
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, screenHeight - triggerY - popupHeight + Theme.popupDistance))
|
||||
return Math.max(Theme.popupDistance, screenHeight - triggerY - popupHeight)
|
||||
} else {
|
||||
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, triggerY + Theme.popupDistance))
|
||||
return Math.min(screenHeight - popupHeight - Theme.popupDistance, triggerY)
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real alignedWidth: Theme.snap(popupWidth, dpr)
|
||||
readonly property real alignedHeight: Theme.snap(popupHeight, dpr)
|
||||
readonly property real alignedX: Theme.snap(calculatedX, dpr)
|
||||
readonly property real alignedY: Theme.snap(calculatedY, dpr)
|
||||
})(), dpr)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -117,6 +126,7 @@ PanelWindow {
|
||||
height: alignedHeight
|
||||
active: root.visible
|
||||
asynchronous: false
|
||||
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true"
|
||||
opacity: shouldBeVisible ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
|
||||
@@ -42,6 +42,7 @@ StyledRect {
|
||||
property real topPadding: Theme.spacingM
|
||||
property real bottomPadding: Theme.spacingM
|
||||
property bool ignoreLeftRightKeys: false
|
||||
property bool ignoreTabKeys: false
|
||||
property var keyForwardTargets: []
|
||||
property Item keyNavigationTab: null
|
||||
property Item keyNavigationBacktab: null
|
||||
@@ -109,7 +110,7 @@ StyledRect {
|
||||
onEditingFinished: root.editingFinished()
|
||||
onAccepted: root.accepted()
|
||||
onActiveFocusChanged: root.focusStateChanged(activeFocus)
|
||||
Keys.forwardTo: root.ignoreLeftRightKeys ? root.keyForwardTargets : []
|
||||
Keys.forwardTo: root.keyForwardTargets
|
||||
Keys.onLeftPressed: event => {
|
||||
if (root.ignoreLeftRightKeys) {
|
||||
event.accepted = true
|
||||
@@ -122,10 +123,19 @@ StyledRect {
|
||||
if (root.ignoreLeftRightKeys) {
|
||||
event.accepted = true
|
||||
} else {
|
||||
// Allow normal TextInput cursor movement
|
||||
event.accepted = false
|
||||
}
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
|
||||
event.accepted = false
|
||||
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
||||
if (root.keyForwardTargets[i]) {
|
||||
root.keyForwardTargets[i].Keys.pressed(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -48,13 +48,13 @@ PanelWindow {
|
||||
|
||||
margins {
|
||||
left: {
|
||||
if (alignLeft) return Math.round(targetX)
|
||||
if (alignRight) return Math.round(targetX - implicitWidth)
|
||||
return Math.round(targetX - implicitWidth / 2)
|
||||
if (alignLeft) return Math.round(Math.max(Theme.spacingS, Math.min((targetScreen?.width ?? Screen.width) - implicitWidth - Theme.spacingS, targetX)))
|
||||
if (alignRight) return Math.round(Math.max(Theme.spacingS, Math.min((targetScreen?.width ?? Screen.width) - implicitWidth - Theme.spacingS, targetX - implicitWidth)))
|
||||
return Math.round(Math.max(Theme.spacingS, Math.min((targetScreen?.width ?? Screen.width) - implicitWidth - Theme.spacingS, targetX - implicitWidth / 2)))
|
||||
}
|
||||
top: {
|
||||
if (alignLeft || alignRight) return Math.round(targetY - implicitHeight / 2)
|
||||
return Math.round(targetY)
|
||||
if (alignLeft || alignRight) return Math.round(Math.max(Theme.spacingS, Math.min((targetScreen?.height ?? Screen.height) - implicitHeight - Theme.spacingS, targetY - implicitHeight / 2)))
|
||||
return Math.round(Math.max(Theme.spacingS, Math.min((targetScreen?.height ?? Screen.height) - implicitHeight - Theme.spacingS, targetY)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
111
dms.spec
Normal file
111
dms.spec
Normal file
@@ -0,0 +1,111 @@
|
||||
# Spec for DMS - uses rpkg macros for both stable and git builds
|
||||
|
||||
%global debug_package %{nil}
|
||||
%global version {{{ git_dir_version }}}
|
||||
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
Name: dms
|
||||
Version: %{version}
|
||||
Release: 1%{?dist}
|
||||
Summary: %{pkg_summary}
|
||||
|
||||
License: GPL-3.0-only
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
VCS: {{{ git_dir_vcs }}}
|
||||
Source0: {{{ git_dir_pack }}}
|
||||
|
||||
# DMS CLI tool sources - compiled from danklinux
|
||||
Source1: https://github.com/AvengeMedia/danklinux/archive/refs/heads/master.tar.gz#/danklinux-master.tar.gz
|
||||
|
||||
BuildRequires: git-core
|
||||
BuildRequires: golang >= 1.21
|
||||
BuildRequires: rpkg
|
||||
|
||||
# Core requirements - Shell and fonts
|
||||
# Requires: (quickshell or quickshell-git)
|
||||
Requires: dms-cli = %{version}-%{release}
|
||||
Requires: dgop
|
||||
Requires: fira-code-fonts
|
||||
Requires: material-symbols-fonts
|
||||
Requires: rsms-inter-fonts
|
||||
Requires: quickshell-git
|
||||
|
||||
# Core utilities (Highly recommended for DMS functionality)
|
||||
Recommends: brightnessctl
|
||||
Recommends: cava
|
||||
Recommends: cliphist
|
||||
Recommends: hyprpicker
|
||||
Recommends: matugen
|
||||
Recommends: wl-clipboard
|
||||
|
||||
# Recommended system packages
|
||||
Recommends: gammastep
|
||||
Recommends: NetworkManager
|
||||
Suggests: qt6ct
|
||||
|
||||
%description
|
||||
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
and optimized for the niri and hyprland compositors. Features notifications,
|
||||
app launcher, wallpaper customization, and fully customizable with plugins.
|
||||
|
||||
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||
process monitoring, notification center, clipboard history, dock, control center,
|
||||
lock screen, and comprehensive plugin system.
|
||||
|
||||
%package -n dms-cli
|
||||
Summary: DankMaterialShell CLI tool
|
||||
License: GPL-3.0-only
|
||||
URL: https://github.com/AvengeMedia/danklinux
|
||||
|
||||
%description -n dms-cli
|
||||
Command-line interface for DankMaterialShell configuration and management.
|
||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||
|
||||
%prep
|
||||
{{{ git_dir_setup_macro }}}
|
||||
|
||||
# Extract danklinux for building dms CLI
|
||||
tar -xzf %{SOURCE1} -C %{_builddir}
|
||||
|
||||
%build
|
||||
# Compile dms CLI from danklinux source
|
||||
pushd %{_builddir}/danklinux-master
|
||||
|
||||
# Use RPM version and build info
|
||||
BUILD_TIME=$(date -u '+%%Y-%%m-%%d_%%H:%%M:%%S')
|
||||
|
||||
# Build with CGO disabled and version info
|
||||
export CGO_ENABLED=0
|
||||
export GOFLAGS="-trimpath -mod=readonly -modcacherw"
|
||||
|
||||
go build \
|
||||
-tags distro_binary \
|
||||
-ldflags="-s -w -X main.Version=%{version}-%{release} -X main.buildTime=${BUILD_TIME} -X main.commit=%{version}" \
|
||||
-o dms \
|
||||
./cmd/dms
|
||||
|
||||
popd
|
||||
|
||||
%install
|
||||
# Install dms-cli binary
|
||||
install -Dm755 %{_builddir}/danklinux-master/dms %{buildroot}%{_bindir}/dms
|
||||
|
||||
# Install shell files to XDG config location
|
||||
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms
|
||||
cp -r ./* %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/
|
||||
|
||||
# Remove git-related files
|
||||
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.git*
|
||||
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.gitignore
|
||||
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.github
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc README.md CONTRIBUTING.md
|
||||
%{_sysconfdir}/xdg/quickshell/dms/
|
||||
|
||||
%files -n dms-cli
|
||||
%{_bindir}/dms
|
||||
|
||||
%changelog
|
||||
{{{ git_dir_changelog }}}
|
||||
26
flake.lock
generated
26
flake.lock
generated
@@ -7,11 +7,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1759769087,
|
||||
"narHash": "sha256-b4dEAjvIfIkw2/C47aZGDnwhTBEjqptDo8J5PizeTCo=",
|
||||
"lastModified": 1760238269,
|
||||
"narHash": "sha256-7CeGZM/Z/5Qt3AYByCRohGYGR1MRuXYzTTbkV/JxyAs=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "dgop",
|
||||
"rev": "ad6ad285e8b882c41eb8994ef7c91e151afb9a97",
|
||||
"rev": "95acdfce2d323e28fa8f5a4f345160962034f2b5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -27,11 +27,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1759946376,
|
||||
"narHash": "sha256-/kQpJPH1y+U6V7N3bbGzvNRGfk9VuxdZev9Os4bS5ZQ=",
|
||||
"lastModified": 1760241259,
|
||||
"narHash": "sha256-DlLGn+4M6tIafoDsHr2WhHG2hrHrC24S2IL3+KAvjEU=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "danklinux",
|
||||
"rev": "98db89ffba290265bc4a886d13b8a27a53fdaca1",
|
||||
"rev": "dae4c3ff4ce0feb930361c399747edb29d081775",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -42,11 +42,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1759826507,
|
||||
"narHash": "sha256-vwXL9H5zDHEQA0oFpww2one0/hkwnPAjc47LRph6d0I=",
|
||||
"lastModified": 1760164275,
|
||||
"narHash": "sha256-gKl2Gtro/LNf8P+4L3S2RsZ0G390ccd5MyXYrTdMCFE=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "bce5fe2bb998488d8e7e7856315f90496723793c",
|
||||
"rev": "362791944032cb532aabbeed7887a441496d5e6e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -63,11 +63,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1759610621,
|
||||
"narHash": "sha256-P3UPFd95mS/3aNgy40nCXAmyfR2bEEBd+tX6xfkYFb0=",
|
||||
"lastModified": 1760228179,
|
||||
"narHash": "sha256-4Z6k7lv3Zcgk3K+4h60LpqB9wCkR+utkYERU735U068=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "c5c438f1cd1a76660a8658ef929a3d19e968e2ce",
|
||||
"revCount": 689,
|
||||
"rev": "c9d3ffb6043c5bf3f3009202bad7e0e5132c4a25",
|
||||
"revCount": 693,
|
||||
"type": "git",
|
||||
"url": "https://git.outfoxxed.me/quickshell/quickshell"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
...
|
||||
}: let
|
||||
cfg = config.programs.dankMaterialShell;
|
||||
jsonFormat = pkgs.formats.json { };
|
||||
in {
|
||||
options.programs.dankMaterialShell = with lib.types; {
|
||||
enable = lib.mkEnableOption "DankMaterialShell";
|
||||
@@ -54,6 +55,37 @@ in {
|
||||
quickshell = {
|
||||
package = lib.mkPackageOption pkgs "quickshell" {};
|
||||
};
|
||||
|
||||
default = {
|
||||
settings = lib.mkOption {
|
||||
type = jsonFormat.type;
|
||||
default = { };
|
||||
description = "The default settings are only read if the settings.json file don't exist";
|
||||
};
|
||||
session = lib.mkOption {
|
||||
type = jsonFormat.type;
|
||||
default = { };
|
||||
description = "The default session are only read if the session.json file don't exist";
|
||||
};
|
||||
};
|
||||
|
||||
plugins = lib.mkOption {
|
||||
type = attrsOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
enable = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to link this plugin";
|
||||
};
|
||||
src = lib.mkOption {
|
||||
type = types.path;
|
||||
description = "Source to link to DMS plugins directory";
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = {};
|
||||
description = "DMS Plugins to install";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable
|
||||
@@ -65,14 +97,39 @@ in {
|
||||
configs.dms = "${
|
||||
dmsPkgs.dankMaterialShell
|
||||
}/etc/xdg/quickshell/DankMaterialShell";
|
||||
activeConfig = lib.mkIf cfg.enableSystemd "dms";
|
||||
|
||||
systemd = lib.mkIf cfg.enableSystemd {
|
||||
enable = true;
|
||||
target = "graphical-session.target";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.user.services.dms = lib.mkIf cfg.enableSystemd {
|
||||
Unit = {
|
||||
Description = "DankMaterialShell";
|
||||
PartOf = [ config.wayland.systemd.target ];
|
||||
After = [ config.wayland.systemd.target ];
|
||||
};
|
||||
|
||||
Service = {
|
||||
ExecStart = lib.getExe dmsPkgs.dmsCli + " run";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
|
||||
Install.WantedBy = [ config.wayland.systemd.target ];
|
||||
};
|
||||
|
||||
xdg.stateFile."DankMaterialShell/default-session.json" = lib.mkIf (cfg.default.session != { }) {
|
||||
source = jsonFormat.generate "default-session.json" cfg.default.session;
|
||||
};
|
||||
|
||||
xdg.configFile = lib.mkMerge [
|
||||
(lib.mapAttrs' (name: plugin: {
|
||||
name = "DankMaterialShell/plugins/${name}";
|
||||
value.source = plugin.src;
|
||||
}) (lib.filterAttrs (n: v: v.enable) cfg.plugins))
|
||||
{
|
||||
"DankMaterialShell/default-settings.json" = lib.mkIf (cfg.default.settings != { }) {
|
||||
source = jsonFormat.generate "default-settings.json" cfg.default.settings;
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
home.packages =
|
||||
[
|
||||
pkgs.material-symbols
|
||||
|
||||
@@ -10,42 +10,40 @@
|
||||
|
||||
user = config.services.greetd.settings.default_session.user;
|
||||
|
||||
buildCompositorConfig = conf: pkgs.writeText "dmsgreeter-compositor-config" ''
|
||||
${(lib.replaceString "_DMS_PATH_" "${dmsPkgs.dankMaterialShell}/etc/xdg/quickshell/dms" (lib.fileContents conf))}
|
||||
${cfg.compositor.extraConfig}
|
||||
'';
|
||||
|
||||
sessionCommands = {
|
||||
niri = ''
|
||||
export PATH=$PATH:${lib.makeBinPath [ config.programs.niri.package ]}
|
||||
niri -c ${buildCompositorConfig ../Modules/Greetd/assets/dms-niri.kdl} \
|
||||
'';
|
||||
hyprland = ''
|
||||
export PATH=$PATH:${lib.makeBinPath [ config.programs.hyprland.package ]}
|
||||
hyprland -c ${buildCompositorConfig ../Modules/Greetd/assets/dms-hypr.conf} \
|
||||
'';
|
||||
};
|
||||
|
||||
greeterScript = pkgs.writeShellScriptBin "dms-greeter" ''
|
||||
export QT_QPA_PLATFORM=wayland
|
||||
export XDG_SESSION_TYPE=wayland
|
||||
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
||||
export EGL_PLATFORM=gbm
|
||||
export DMS_GREET_CFG_DIR="/var/lib/dmsgreeter/"
|
||||
export PATH=$PATH:${lib.makeBinPath [ cfg.quickshell.package ]}
|
||||
${sessionCommands.${cfg.compositor.name}} ${lib.optionalString cfg.logs.save "> ${cfg.logs.path} 2>&1"}
|
||||
export PATH=$PATH:${lib.makeBinPath [ cfg.quickshell.package config.programs.${cfg.compositor.name}.package ]}
|
||||
${lib.escapeShellArgs ([
|
||||
"sh"
|
||||
"${../Modules/Greetd/assets/dms-greeter}"
|
||||
"--cache-dir"
|
||||
"/var/lib/dmsgreeter"
|
||||
"--command"
|
||||
cfg.compositor.name
|
||||
"-p"
|
||||
"${dmsPkgs.dankMaterialShell}/etc/xdg/quickshell/dms"
|
||||
]
|
||||
++ lib.optionals (cfg.compositor.customConfig != "") [
|
||||
"-C"
|
||||
"${pkgs.writeText "dmsgreeter-compositor-config" cfg.compositor.customConfig}"
|
||||
])} ${lib.optionalString cfg.logs.save "> ${cfg.logs.path} 2>&1"}
|
||||
'';
|
||||
in {
|
||||
imports =
|
||||
let
|
||||
msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead.";
|
||||
in
|
||||
[ (lib.mkRemovedOptionModule [ "programs" "dankMaterialShell" "greeter" "compositor" "extraConfig" ] msg) ];
|
||||
|
||||
options.programs.dankMaterialShell.greeter = {
|
||||
enable = lib.mkEnableOption "DankMaterialShell greeter";
|
||||
compositor.name = lib.mkOption {
|
||||
type = types.enum ["niri" "hyprland"];
|
||||
type = types.enum ["niri" "hyprland" "sway"];
|
||||
description = "Compositor to run greeter in";
|
||||
};
|
||||
compositor.extraConfig = lib.mkOption {
|
||||
compositor.customConfig = lib.mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Exra compositor config to include";
|
||||
description = "Custom compositor config";
|
||||
};
|
||||
configFiles = lib.mkOption {
|
||||
type = types.listOf types.path;
|
||||
|
||||
@@ -54,34 +54,45 @@ key_of() {
|
||||
local icon=$(echo "$json" | sed 's/.*"iconTheme": *"\([^"]*\)".*/\1/')
|
||||
local matugen_type=$(echo "$json" | sed 's/.*"matugenType": *"\([^"]*\)".*/\1/')
|
||||
local surface_base=$(echo "$json" | sed 's/.*"surfaceBase": *"\([^"]*\)".*/\1/')
|
||||
local run_user_templates=$(echo "$json" | sed 's/.*"runUserTemplates": *\([^,}]*\).*/\1/')
|
||||
[[ -z "$icon" ]] && icon="System Default"
|
||||
[[ -z "$matugen_type" ]] && matugen_type="scheme-tonal-spot"
|
||||
[[ -z "$surface_base" ]] && surface_base="sc"
|
||||
echo "${kind}|${value}|${mode}|${icon}|${matugen_type}|${surface_base}" | sha256sum | cut -d' ' -f1
|
||||
[[ -z "$run_user_templates" ]] && run_user_templates="true"
|
||||
echo "${kind}|${value}|${mode}|${icon}|${matugen_type}|${surface_base}|${run_user_templates}" | sha256sum | cut -d' ' -f1
|
||||
}
|
||||
|
||||
build_once() {
|
||||
local json="$1"
|
||||
local kind value mode icon matugen_type surface_base
|
||||
local kind value mode icon matugen_type surface_base run_user_templates
|
||||
kind=$(echo "$json" | sed 's/.*"kind": *"\([^"]*\)".*/\1/')
|
||||
value=$(echo "$json" | sed 's/.*"value": *"\([^"]*\)".*/\1/')
|
||||
mode=$(echo "$json" | sed 's/.*"mode": *"\([^"]*\)".*/\1/')
|
||||
icon=$(echo "$json" | sed 's/.*"iconTheme": *"\([^"]*\)".*/\1/')
|
||||
matugen_type=$(echo "$json" | sed 's/.*"matugenType": *"\([^"]*\)".*/\1/')
|
||||
surface_base=$(echo "$json" | sed 's/.*"surfaceBase": *"\([^"]*\)".*/\1/')
|
||||
run_user_templates=$(echo "$json" | sed 's/.*"runUserTemplates": *\([^,}]*\).*/\1/')
|
||||
[[ -z "$icon" ]] && icon="System Default"
|
||||
[[ -z "$matugen_type" ]] && matugen_type="scheme-tonal-spot"
|
||||
[[ -z "$surface_base" ]] && surface_base="sc"
|
||||
[[ -z "$run_user_templates" ]] && run_user_templates="true"
|
||||
|
||||
USER_MATUGEN_DIR="$CONFIG_DIR/matugen/dms"
|
||||
|
||||
TMP_CFG="$(mktemp)"
|
||||
trap 'rm -f "$TMP_CFG"' RETURN
|
||||
|
||||
cat "$SHELL_DIR/matugen/configs/base.toml" > "$TMP_CFG"
|
||||
if [[ "$run_user_templates" == "true" ]] && [[ -f "$CONFIG_DIR/matugen/config.toml" ]]; then
|
||||
awk '/^\[config/{p=1} /^\[templates/{p=0} p' "$CONFIG_DIR/matugen/config.toml" >> "$TMP_CFG"
|
||||
echo "" >> "$TMP_CFG"
|
||||
else
|
||||
echo "[config]" >> "$TMP_CFG"
|
||||
echo "" >> "$TMP_CFG"
|
||||
fi
|
||||
|
||||
grep -v '^\[config\]' "$SHELL_DIR/matugen/configs/base.toml" >> "$TMP_CFG"
|
||||
echo "" >> "$TMP_CFG"
|
||||
|
||||
# Generate dank config dynamically with correct state directory
|
||||
cat >> "$TMP_CFG" << EOF
|
||||
[templates.dank]
|
||||
input_path = '$SHELL_DIR/matugen/templates/dank.json'
|
||||
@@ -118,8 +129,12 @@ EOF
|
||||
cat "$SHELL_DIR/matugen/configs/vesktop.toml" >> "$TMP_CFG"
|
||||
echo "" >> "$TMP_CFG"
|
||||
fi
|
||||
|
||||
# Load user's matugen configurations
|
||||
|
||||
if [[ "$run_user_templates" == "true" ]] && [[ -f "$CONFIG_DIR/matugen/config.toml" ]]; then
|
||||
awk '/^\[templates/{p=1} p' "$CONFIG_DIR/matugen/config.toml" >> "$TMP_CFG"
|
||||
echo "" >> "$TMP_CFG"
|
||||
fi
|
||||
|
||||
for config in "$USER_MATUGEN_DIR/configs"/*.toml; do
|
||||
[[ -f "$config" ]] || continue
|
||||
cat "$config" >> "$TMP_CFG"
|
||||
@@ -258,14 +273,6 @@ EOF
|
||||
mv "$TMP" "$CONFIG_DIR/kitty/dank-theme.conf"
|
||||
fi
|
||||
fi
|
||||
COLOR_SCHEME=$([[ "$mode" == "light" ]] && echo default || echo prefer-dark)
|
||||
if command -v dconf >/dev/null 2>&1; then
|
||||
dconf write /org/gnome/desktop/interface/color-scheme "\"$COLOR_SCHEME\"" 2>/dev/null || true
|
||||
[[ "$icon" != "System Default" && -n "$icon" ]] && dconf write /org/gnome/desktop/interface/icon-theme "\"$icon\"" 2>/dev/null || true
|
||||
elif command -v gsettings >/dev/null 2>&1; then
|
||||
gsettings set org.gnome.desktop.interface color-scheme "$COLOR_SCHEME" 2>/dev/null || true
|
||||
[[ "$icon" != "System Default" && -n "$icon" ]] && gsettings set org.gnome.desktop.interface icon-theme "$icon" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
if command -v pywalfox >/dev/null 2>&1 && [[ -f "$HOME/.cache/wal/colors.json" ]]; then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ from collections import defaultdict
|
||||
def extract_qstr_strings(root_dir):
|
||||
translations = defaultdict(list)
|
||||
qstr_pattern = re.compile(r'qsTr\(["\']([^"\']+)["\']\)')
|
||||
i18n_pattern = re.compile(r'I18n\.tr\(["\']([^"\']+)["\'],\s*["\']([^"\']+)["\']\)')
|
||||
i18n_pattern = re.compile(r'I18n\.tr\(["\']([^"\']+)["\']\)')
|
||||
|
||||
for qml_file in Path(root_dir).rglob('*.qml'):
|
||||
relative_path = qml_file.relative_to(root_dir)
|
||||
@@ -24,8 +24,7 @@ def extract_qstr_strings(root_dir):
|
||||
|
||||
i18n_matches = i18n_pattern.findall(line)
|
||||
for match in i18n_matches:
|
||||
term = match[0]
|
||||
translations[term].append({
|
||||
translations[match].append({
|
||||
'file': str(relative_path),
|
||||
'line': line_num
|
||||
})
|
||||
|
||||
@@ -30,16 +30,16 @@
|
||||
"A file with this name already exists. Do you want to overwrite it?": "この名前のファイルは既に存在します。上書きしてもよろしいですか?"
|
||||
},
|
||||
"About": {
|
||||
"About": ""
|
||||
"About": "詳細"
|
||||
},
|
||||
"Access clipboard history": {
|
||||
"Access clipboard history": ""
|
||||
"Access clipboard history": "クリップボードの履歴へのアクセス"
|
||||
},
|
||||
"Access to notifications and do not disturb": {
|
||||
"Access to notifications and do not disturb": ""
|
||||
"Access to notifications and do not disturb": "通知へのアクセスおよびサイレントモード"
|
||||
},
|
||||
"Access to system controls and settings": {
|
||||
"Access to system controls and settings": ""
|
||||
"Access to system controls and settings": "システム制御へのアクセスおよび設定"
|
||||
},
|
||||
"Actions": {
|
||||
"Actions": "アクション"
|
||||
@@ -63,7 +63,7 @@
|
||||
"All displays": "すべてのディスプレイ"
|
||||
},
|
||||
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": {
|
||||
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": ""
|
||||
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": "Alt+←/Backspace: 戻る • F1/I: ファイル情報 • F10: ヘルプ • Esc: 閉じる"
|
||||
},
|
||||
"Always Show OSD Percentage": {
|
||||
"Always Show OSD Percentage": "常に OSD パーセンテージを表示"
|
||||
@@ -75,10 +75,10 @@
|
||||
"Animations": "アニメーション"
|
||||
},
|
||||
"App Launcher": {
|
||||
"App Launcher": ""
|
||||
"App Launcher": "アプリランチャー"
|
||||
},
|
||||
"Applications": {
|
||||
"Applications": ""
|
||||
"Applications": "アプリ"
|
||||
},
|
||||
"Apply": {
|
||||
"Apply": "適用"
|
||||
@@ -93,7 +93,7 @@
|
||||
"Apps Icon": "アプリアイコン"
|
||||
},
|
||||
"Apps are ordered by usage frequency, then last used, then alphabetically.": {
|
||||
"Apps are ordered by usage frequency, then last used, then alphabetically.": ""
|
||||
"Apps are ordered by usage frequency, then last used, then alphabetically.": "アプリは使用頻度、最終使用日、アルファベット順の優先順位で並べられています。"
|
||||
},
|
||||
"Are you sure you want to hibernate the system?": {
|
||||
"Are you sure you want to hibernate the system?": "システムを休止状態にしますか?"
|
||||
@@ -171,13 +171,13 @@
|
||||
"Available Screens (": "利用可能なスクリーン("
|
||||
},
|
||||
"Back": {
|
||||
"Back": ""
|
||||
"Back": "戻る"
|
||||
},
|
||||
"Battery": {
|
||||
"Battery": ""
|
||||
"Battery": "バッテリー"
|
||||
},
|
||||
"Battery level and power management": {
|
||||
"Battery level and power management": ""
|
||||
"Battery level and power management": "バッテリーレベルおよび電源管理"
|
||||
},
|
||||
"Battery not detected - only AC power settings available": {
|
||||
"Battery not detected - only AC power settings available": "バッテリーが検出されません - AC電源設定のみ利用可能です"
|
||||
@@ -192,7 +192,7 @@
|
||||
"Border": "ボーダー"
|
||||
},
|
||||
"Brightness": {
|
||||
"Brightness": ""
|
||||
"Brightness": "明るさ"
|
||||
},
|
||||
"Browse": {
|
||||
"Browse": "ブラウズ"
|
||||
@@ -204,16 +204,16 @@
|
||||
"CPU": "CPU"
|
||||
},
|
||||
"CPU Temperature": {
|
||||
"CPU Temperature": ""
|
||||
"CPU Temperature": "CPU温度"
|
||||
},
|
||||
"CPU Usage": {
|
||||
"CPU Usage": ""
|
||||
"CPU Usage": "CPU使用率"
|
||||
},
|
||||
"CPU temperature display": {
|
||||
"CPU temperature display": ""
|
||||
"CPU temperature display": "CPU温度表示"
|
||||
},
|
||||
"CPU usage indicator": {
|
||||
"CPU usage indicator": ""
|
||||
"CPU usage indicator": "CPU使用率インジケーター"
|
||||
},
|
||||
"Cancel": {
|
||||
"Cancel": "キャンセル"
|
||||
@@ -225,37 +225,37 @@
|
||||
"Center Section": "センターセクション"
|
||||
},
|
||||
"Check for system updates": {
|
||||
"Check for system updates": ""
|
||||
"Check for system updates": "システムアップデートを検査"
|
||||
},
|
||||
"Choose Launcher Logo Color": {
|
||||
"Choose Launcher Logo Color": ""
|
||||
"Choose Launcher Logo Color": "ランチャーロゴの色を選ぶ"
|
||||
},
|
||||
"Choose icon": {
|
||||
"Choose icon": "アイコンを選択"
|
||||
"Choose icon": "アイコンを選ぶ"
|
||||
},
|
||||
"Choose the logo displayed on the launcher button in DankBar": {
|
||||
"Choose the logo displayed on the launcher button in DankBar": "Dank Barのランチャーボタンに表示されるロゴを選択"
|
||||
"Choose the logo displayed on the launcher button in DankBar": "Dank Barのランチャーボタンに表示されるロゴを選ぶ"
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": ""
|
||||
"Choose where notification popups appear on screen": "通知ポップアップが画面に表示される場所を選ぶ"
|
||||
},
|
||||
"Clear": {
|
||||
"Clear": "クリア"
|
||||
},
|
||||
"Clear All": {
|
||||
"Clear All": ""
|
||||
"Clear All": "すべてクリア"
|
||||
},
|
||||
"Clear All History?": {
|
||||
"Clear All History?": ""
|
||||
"Clear All History?": "すべての履歴をクリアしますか?"
|
||||
},
|
||||
"Clipboard History": {
|
||||
"Clipboard History": ""
|
||||
"Clipboard History": "クリップボード履歴"
|
||||
},
|
||||
"Clipboard Manager": {
|
||||
"Clipboard Manager": ""
|
||||
"Clipboard Manager": "クリップボード管理"
|
||||
},
|
||||
"Clock": {
|
||||
"Clock": ""
|
||||
"Clock": "時計"
|
||||
},
|
||||
"Close": {
|
||||
"Close": "閉じる"
|
||||
@@ -264,10 +264,10 @@
|
||||
"Color Override": "色のオーバーライド"
|
||||
},
|
||||
"Color Picker": {
|
||||
"Color Picker": ""
|
||||
"Color Picker": "カラーピッカー"
|
||||
},
|
||||
"Color temperature for night mode": {
|
||||
"Color temperature for night mode": ""
|
||||
"Color temperature for night mode": "ナイトモードの色温度"
|
||||
},
|
||||
"Communication": {
|
||||
"Communication": "コミュニケーション"
|
||||
@@ -294,19 +294,19 @@
|
||||
"Connected Displays": "接続されたディスプレイ"
|
||||
},
|
||||
"Contrast": {
|
||||
"Contrast": ""
|
||||
"Contrast": "コントラスト"
|
||||
},
|
||||
"Control Center": {
|
||||
"Control Center": ""
|
||||
"Control Center": "コントロールセンター"
|
||||
},
|
||||
"Control currently playing media": {
|
||||
"Control currently playing media": ""
|
||||
"Control currently playing media": "現在再生中のメディアを制御"
|
||||
},
|
||||
"Control the speed of animations throughout the interface": {
|
||||
"Control the speed of animations throughout the interface": "インターフェース全体のアニメーションの速度を制御する"
|
||||
"Control the speed of animations throughout the interface": "インターフェース全体のアニメーションの速度を制御"
|
||||
},
|
||||
"Copied to clipboard": {
|
||||
"Copied to clipboard": ""
|
||||
"Copied to clipboard": "クリップボードにコピーしました"
|
||||
},
|
||||
"Copied!": {
|
||||
"Copied!": "コピーしました!"
|
||||
@@ -321,7 +321,7 @@
|
||||
"Copy Process Name": "プロセス名をコピー"
|
||||
},
|
||||
"Corner Radius (0 = square corners)": {
|
||||
"Corner Radius (0 = square corners)": ""
|
||||
"Corner Radius (0 = square corners)": "コーナー半径(0 = 角丸なし)"
|
||||
},
|
||||
"Create Dir": {
|
||||
"Create Dir": "ディレクトリを作成"
|
||||
@@ -333,10 +333,10 @@
|
||||
"Current Items": "現在のアイテム"
|
||||
},
|
||||
"Current time and date display": {
|
||||
"Current time and date display": ""
|
||||
"Current time and date display": "現在の日時を表示"
|
||||
},
|
||||
"Current weather conditions and temperature": {
|
||||
"Current weather conditions and temperature": ""
|
||||
"Current weather conditions and temperature": "現在の天気状況と気温"
|
||||
},
|
||||
"Custom": {
|
||||
"Custom": "カスタム"
|
||||
@@ -351,7 +351,7 @@
|
||||
"Custom: ": "カスタム: "
|
||||
},
|
||||
"Customizable empty space": {
|
||||
"Customizable empty space": ""
|
||||
"Customizable empty space": "カスタマイズ可能な空きスペース"
|
||||
},
|
||||
"DEMO MODE - Click anywhere to exit": {
|
||||
"DEMO MODE - Click anywhere to exit": "デモモード -任意の場所をクリックして 終了"
|
||||
@@ -360,13 +360,13 @@
|
||||
"DMS Plugin Manager Unavailable": "DMS プラグイン マネージャーが利用できません"
|
||||
},
|
||||
"DMS_SOCKET not available": {
|
||||
"DMS_SOCKET not available": ""
|
||||
"DMS_SOCKET not available": "DMS_SOCKETが利用できません"
|
||||
},
|
||||
"Daily at:": {
|
||||
"Daily at:": "毎日:"
|
||||
},
|
||||
"Dank Bar": {
|
||||
"Dank Bar": ""
|
||||
"Dank Bar": "Dank Bar"
|
||||
},
|
||||
"Dank Bar Transparency": {
|
||||
"Dank Bar Transparency": "Dank Barの透明性"
|
||||
@@ -384,7 +384,7 @@
|
||||
"Date Format": "日付形式"
|
||||
},
|
||||
"Default": {
|
||||
"Default": ""
|
||||
"Default": "デフォルト"
|
||||
},
|
||||
"Defaults": {
|
||||
"Defaults": "デフォルト"
|
||||
@@ -405,7 +405,7 @@
|
||||
"Disk": "ディスク"
|
||||
},
|
||||
"Disk Usage": {
|
||||
"Disk Usage": ""
|
||||
"Disk Usage": "ディスク使用率"
|
||||
},
|
||||
"Display Settings": {
|
||||
"Display Settings": "表示設定"
|
||||
@@ -417,22 +417,22 @@
|
||||
"Display all priorities over fullscreen apps": "フルスクリーンアプリよりもすべての優先度を表示する"
|
||||
},
|
||||
"Display currently focused application title": {
|
||||
"Display currently focused application title": ""
|
||||
"Display currently focused application title": "現在フォーカスされているアプリケーションのタイトルを表示"
|
||||
},
|
||||
"Display volume and brightness percentage values by default in OSD popups": {
|
||||
"Display volume and brightness percentage values by default in OSD popups": ""
|
||||
"Display volume and brightness percentage values by default in OSD popups": "OSDポップアップに音量と輝度のパーセンテージ値をデフォルトで表示"
|
||||
},
|
||||
"Displays": {
|
||||
"Displays": ""
|
||||
"Displays": "表示"
|
||||
},
|
||||
"Displays the active keyboard layout and allows switching": {
|
||||
"Displays the active keyboard layout and allows switching": ""
|
||||
"Displays the active keyboard layout and allows switching": "アクティブなキーボードレイアウトを表示、切り替えを可能に"
|
||||
},
|
||||
"Do Not Disturb": {
|
||||
"Do Not Disturb": "サイレントモード"
|
||||
},
|
||||
"Dock": {
|
||||
"Dock": ""
|
||||
"Dock": "ドック"
|
||||
},
|
||||
"Dock Position": {
|
||||
"Dock Position": "ドック位置"
|
||||
@@ -441,13 +441,13 @@
|
||||
"Dock Transparency": "ドックの透明度"
|
||||
},
|
||||
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
|
||||
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": ""
|
||||
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "ウィジェットをドラッグしてセクション内で順序を変更できます。目のアイコンでウィジェットを表示/非表示に(スペースは維持)、Xで完全に削除できます。"
|
||||
},
|
||||
"Dynamic Theming": {
|
||||
"Dynamic Theming": "ダイナミックテーマ"
|
||||
},
|
||||
"Edge Spacing (0 = edge-to-edge)": {
|
||||
"Edge Spacing (0 = edge-to-edge)": ""
|
||||
"Edge Spacing (0 = edge-to-edge)": "エッジ間隔(0 = エッジ・トゥ・エッジ)"
|
||||
},
|
||||
"Education": {
|
||||
"Education": "教育"
|
||||
@@ -480,7 +480,7 @@
|
||||
"Exclusive Zone Offset": "排他ゾーンオフセット"
|
||||
},
|
||||
"F1/I: Toggle • F10: Help": {
|
||||
"F1/I: Toggle • F10: Help": ""
|
||||
"F1/I: Toggle • F10: Help": "F1/I: 切り替え • F10: ヘルプ"
|
||||
},
|
||||
"Feels Like": {
|
||||
"Feels Like": "どうやら"
|
||||
@@ -498,7 +498,7 @@
|
||||
"Find in note...": "メモで検索..."
|
||||
},
|
||||
"Focused Window": {
|
||||
"Focused Window": ""
|
||||
"Focused Window": "フォーカスされたウィンドウ"
|
||||
},
|
||||
"Font Family": {
|
||||
"Font Family": "フォントファミリー"
|
||||
@@ -534,13 +534,13 @@
|
||||
"Fun": "娯楽"
|
||||
},
|
||||
"GPU": {
|
||||
"GPU": "グラフィックプロセッサ"
|
||||
"GPU": "GPU"
|
||||
},
|
||||
"GPU Temperature": {
|
||||
"GPU Temperature": ""
|
||||
"GPU Temperature": "GPU温度"
|
||||
},
|
||||
"GPU temperature display": {
|
||||
"GPU temperature display": ""
|
||||
"GPU temperature display": "GPU温度表示"
|
||||
},
|
||||
"Games": {
|
||||
"Games": "ゲーム"
|
||||
@@ -585,7 +585,7 @@
|
||||
"Hour": "時間"
|
||||
},
|
||||
"How often to change wallpaper": {
|
||||
"How often to change wallpaper": ""
|
||||
"How often to change wallpaper": "壁紙を切り替える間隔"
|
||||
},
|
||||
"Humidity": {
|
||||
"Humidity": "湿度"
|
||||
@@ -597,7 +597,7 @@
|
||||
"Icon Theme": "アイコンテーマ"
|
||||
},
|
||||
"Idle Inhibitor": {
|
||||
"Idle Inhibitor": ""
|
||||
"Idle Inhibitor": "アイドルインヒビター"
|
||||
},
|
||||
"Idle Settings": {
|
||||
"Idle Settings": "アイドル設定"
|
||||
@@ -606,7 +606,7 @@
|
||||
"Idle monitoring not supported - requires newer Quickshell version": "アイドル監視はサポートされていません - 新しい Quickshell バージョンが必要です"
|
||||
},
|
||||
"Image": {
|
||||
"Image": ""
|
||||
"Image": "画像"
|
||||
},
|
||||
"Include Transitions": {
|
||||
"Include Transitions": "トランジションを含める"
|
||||
@@ -624,10 +624,10 @@
|
||||
"Interval": "間隔"
|
||||
},
|
||||
"Invert on mode change": {
|
||||
"Invert on mode change": ""
|
||||
"Invert on mode change": "モード変更時に反転"
|
||||
},
|
||||
"Keyboard Layout Name": {
|
||||
"Keyboard Layout Name": ""
|
||||
"Keyboard Layout Name": "キーボードレイアウト名"
|
||||
},
|
||||
"Kill Process": {
|
||||
"Kill Process": "プロセスを強制終了"
|
||||
@@ -642,13 +642,13 @@
|
||||
"Launch": "起動"
|
||||
},
|
||||
"Launch Prefix": {
|
||||
"Launch Prefix": ""
|
||||
"Launch Prefix": "起動プリフィックス"
|
||||
},
|
||||
"Launch on dGPU": {
|
||||
"Launch on dGPU": "dGPUで起動"
|
||||
},
|
||||
"Launcher": {
|
||||
"Launcher": ""
|
||||
"Launcher": "ランチャー"
|
||||
},
|
||||
"Launcher Button Logo": {
|
||||
"Launcher Button Logo": "ランチャーのボタンロゴ"
|
||||
@@ -678,7 +678,7 @@
|
||||
"Log Out": "ログアウト"
|
||||
},
|
||||
"Long Text": {
|
||||
"Long Text": ""
|
||||
"Long Text": "長文"
|
||||
},
|
||||
"Longitude": {
|
||||
"Longitude": "経度"
|
||||
@@ -693,7 +693,7 @@
|
||||
"Manual Coordinates": "手動座標"
|
||||
},
|
||||
"Manual Show/Hide": {
|
||||
"Manual Show/Hide": ""
|
||||
"Manual Show/Hide": "手動で表示/非表示"
|
||||
},
|
||||
"Material Colors": {
|
||||
"Material Colors": "Material Colors"
|
||||
@@ -708,7 +708,7 @@
|
||||
"Media": "メディア"
|
||||
},
|
||||
"Media Controls": {
|
||||
"Media Controls": ""
|
||||
"Media Controls": "メディアコントロール"
|
||||
},
|
||||
"Media Player Settings": {
|
||||
"Media Player Settings": "メディアプレーヤーの設定"
|
||||
@@ -720,10 +720,10 @@
|
||||
"Memory": "メモリ"
|
||||
},
|
||||
"Memory Usage": {
|
||||
"Memory Usage": ""
|
||||
"Memory Usage": "メモリ使用率"
|
||||
},
|
||||
"Memory usage indicator": {
|
||||
"Memory usage indicator": ""
|
||||
"Memory usage indicator": "メモリ使用率インジケーター"
|
||||
},
|
||||
"Minute": {
|
||||
"Minute": "分"
|
||||
@@ -744,7 +744,7 @@
|
||||
"Mount": "マウント"
|
||||
},
|
||||
"NM not supported": {
|
||||
"NM not supported": ""
|
||||
"NM not supported": "NMが利用できません"
|
||||
},
|
||||
"Named Workspace Icons": {
|
||||
"Named Workspace Icons": "名前付きワークスペースアイコン"
|
||||
@@ -768,10 +768,10 @@
|
||||
"Network Settings": "ネットワーク設定"
|
||||
},
|
||||
"Network Speed Monitor": {
|
||||
"Network Speed Monitor": ""
|
||||
"Network Speed Monitor": "ネットワーク速度モニター"
|
||||
},
|
||||
"Network download and upload speed display": {
|
||||
"Network download and upload speed display": ""
|
||||
"Network download and upload speed display": "ネットワークのダウンロードおよびアップロード速度を表示"
|
||||
},
|
||||
"New": {
|
||||
"New": "新しい"
|
||||
@@ -813,7 +813,7 @@
|
||||
"No plugins found": "プラグインが見つかりませんでした"
|
||||
},
|
||||
"No plugins found.": {
|
||||
"No plugins found.": ""
|
||||
"No plugins found.": "プラグインが見つかりませんでした。"
|
||||
},
|
||||
"Normal Priority": {
|
||||
"Normal Priority": "通常の優先度"
|
||||
@@ -828,7 +828,7 @@
|
||||
"Nothing to see here": "ここには何もありません"
|
||||
},
|
||||
"Notification Center": {
|
||||
"Notification Center": ""
|
||||
"Notification Center": "通知センター"
|
||||
},
|
||||
"Notification Overlay": {
|
||||
"Notification Overlay": "通知オーバーレイ"
|
||||
@@ -885,19 +885,19 @@
|
||||
"Per-Monitor Workspaces": "モニターごとのワークスペース"
|
||||
},
|
||||
"Percentage": {
|
||||
"Percentage": ""
|
||||
"Percentage": "百分率"
|
||||
},
|
||||
"Personalization": {
|
||||
"Personalization": ""
|
||||
"Personalization": "パーソナライゼーション"
|
||||
},
|
||||
"Pin to Dock": {
|
||||
"Pin to Dock": ""
|
||||
"Pin to Dock": "ドックにピン留め"
|
||||
},
|
||||
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": {
|
||||
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": "プラグインディレクトリをここに配置します。各プラグインには plugin.json マニフェストファイルが必要です。"
|
||||
},
|
||||
"Place plugins in": {
|
||||
"Place plugins in": ""
|
||||
"Place plugins in": "プラグインを配置する場所"
|
||||
},
|
||||
"Plugin Directory": {
|
||||
"Plugin Directory": "プラグインディレクトリ"
|
||||
@@ -906,10 +906,10 @@
|
||||
"Plugin Management": "プラグイン管理"
|
||||
},
|
||||
"Plugin is disabled - enable in Plugins settings to use": {
|
||||
"Plugin is disabled - enable in Plugins settings to use": ""
|
||||
"Plugin is disabled - enable in Plugins settings to use": "プラグインは無効です - 使用するにはプラグイン設定で有効にしてください"
|
||||
},
|
||||
"Plugins": {
|
||||
"Plugins": ""
|
||||
"Plugins": "プラグイン"
|
||||
},
|
||||
"Popup Position": {
|
||||
"Popup Position": "ポップアップの位置"
|
||||
@@ -921,7 +921,7 @@
|
||||
"Position": "位置"
|
||||
},
|
||||
"Power": {
|
||||
"Power": ""
|
||||
"Power": "電源"
|
||||
},
|
||||
"Power Off": {
|
||||
"Power Off": "電源オフ"
|
||||
@@ -936,28 +936,25 @@
|
||||
"Pressure": "プレッシャー"
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": ""
|
||||
"Prevent screen timeout": "画面のタイムアウトを防止"
|
||||
},
|
||||
"Primary": {
|
||||
"Primary": ""
|
||||
"Primary": "プライマリー"
|
||||
},
|
||||
"Privacy Indicator": {
|
||||
"Privacy Indicator": ""
|
||||
"Privacy Indicator": "プライバシーインジケーター"
|
||||
},
|
||||
"Process": {
|
||||
"Process": "プロセス"
|
||||
},
|
||||
"QML (Qt Modeling Language)": {
|
||||
"QML (Qt Modeling Language)": "QML (Qt モデリング言語)"
|
||||
},
|
||||
"Quick access to application launcher": {
|
||||
"Quick access to application launcher": ""
|
||||
"Quick access to application launcher": "アプリケーションランチャーへのクイックアクセス"
|
||||
},
|
||||
"Quick access to color picker": {
|
||||
"Quick access to color picker": ""
|
||||
"Quick access to color picker": "カラーピッカーへのクイックアクセス"
|
||||
},
|
||||
"Quick access to notepad": {
|
||||
"Quick access to notepad": ""
|
||||
"Quick access to notepad": "メモ帳へのクイックアクセス"
|
||||
},
|
||||
"Rain Chance": {
|
||||
"Rain Chance": "降水確率"
|
||||
@@ -969,7 +966,7 @@
|
||||
"Recent Colors": "最近の色"
|
||||
},
|
||||
"Recently Used Apps": {
|
||||
"Recently Used Apps": ""
|
||||
"Recently Used Apps": "最近使用したアプリ"
|
||||
},
|
||||
"Refresh": {
|
||||
"Refresh": "リフレッシュ"
|
||||
@@ -981,7 +978,7 @@
|
||||
"Reset": "リセット"
|
||||
},
|
||||
"Running Apps": {
|
||||
"Running Apps": ""
|
||||
"Running Apps": "実行中のアプリ"
|
||||
},
|
||||
"Running Apps Only In Current Workspace": {
|
||||
"Running Apps Only In Current Workspace": "現在のワークスペースでのみアプリを実行する"
|
||||
@@ -1020,34 +1017,34 @@
|
||||
"Search...": "検索..."
|
||||
},
|
||||
"Select Launcher Logo": {
|
||||
"Select Launcher Logo": "ランチャーロゴを選択"
|
||||
"Select Launcher Logo": "ランチャーロゴを選ぶ"
|
||||
},
|
||||
"Select a color from the palette or use custom sliders": {
|
||||
"Select a color from the palette or use custom sliders": "パレットから色を選択するか、カスタムスライダーを使用します"
|
||||
"Select a color from the palette or use custom sliders": "パレットから色を選ぶか、カスタムスライダーを使用します"
|
||||
},
|
||||
"Select a widget to add to the ": {
|
||||
"Select a widget to add to the ": "追加するウィジェットを選択してください "
|
||||
"Select a widget to add to the ": "追加するウィジェットを選ぶ "
|
||||
},
|
||||
"Select an image file...": {
|
||||
"Select an image file...": "画像ファイルを選択..."
|
||||
"Select an image file...": "画像ファイルを選ぶ..."
|
||||
},
|
||||
"Select font weight": {
|
||||
"Select font weight": ""
|
||||
"Select font weight": "フォントの太さを選ぶ"
|
||||
},
|
||||
"Select monitor to configure wallpaper": {
|
||||
"Select monitor to configure wallpaper": ""
|
||||
"Select monitor to configure wallpaper": "壁紙を設定するモニターを選ぶ"
|
||||
},
|
||||
"Select monospace font for process list and technical displays": {
|
||||
"Select monospace font for process list and technical displays": ""
|
||||
"Select monospace font for process list and technical displays": "プロセスリストと技術表示用の等幅フォントを選ぶ"
|
||||
},
|
||||
"Select system font family": {
|
||||
"Select system font family": ""
|
||||
"Select system font family": "システムフォントファミリーを選ぶ"
|
||||
},
|
||||
"Select which transitions to include in randomization": {
|
||||
"Select which transitions to include in randomization": "含めたいトランジションをランダム化に選択"
|
||||
},
|
||||
"Separator": {
|
||||
"Separator": ""
|
||||
"Separator": "区切り"
|
||||
},
|
||||
"Set different wallpapers for each connected monitor": {
|
||||
"Set different wallpapers for each connected monitor": "接続されているモニターごとに異なる壁紙を設定する"
|
||||
@@ -1077,7 +1074,7 @@
|
||||
"Show on Overview": "概要に表示"
|
||||
},
|
||||
"Show on all connected displays": {
|
||||
"Show on all connected displays": ""
|
||||
"Show on all connected displays": "すべての接続されたディスプレイに表示"
|
||||
},
|
||||
"Show on screens:": {
|
||||
"Show on screens:": "画面に表示:"
|
||||
@@ -1089,28 +1086,28 @@
|
||||
"Show weather information in top bar and control center": "トップバーとコントロールセンターに天気情報を表示"
|
||||
},
|
||||
"Shows all running applications with focus indication": {
|
||||
"Shows all running applications with focus indication": ""
|
||||
"Shows all running applications with focus indication": "実行中のすべてのアプリケーションをフォーカス状態で表示"
|
||||
},
|
||||
"Shows current workspace and allows switching": {
|
||||
"Shows current workspace and allows switching": ""
|
||||
"Shows current workspace and allows switching": "現在のワークスペースを表示、切り替えを可能に"
|
||||
},
|
||||
"Shows when microphone, camera, or screen sharing is active": {
|
||||
"Shows when microphone, camera, or screen sharing is active": ""
|
||||
"Shows when microphone, camera, or screen sharing is active": "マイク、カメラ、または画面共有がアクティブなときに表示"
|
||||
},
|
||||
"Size": {
|
||||
"Size": "サイズ"
|
||||
},
|
||||
"Size Offset": {
|
||||
"Size Offset": ""
|
||||
"Size Offset": "サイズオフセット"
|
||||
},
|
||||
"Spacer": {
|
||||
"Spacer": ""
|
||||
"Spacer": "間隔"
|
||||
},
|
||||
"Spacing": {
|
||||
"Spacing": "間隔"
|
||||
},
|
||||
"Square Corners": {
|
||||
"Square Corners": "コーナーを四角くする"
|
||||
"Square Corners": "四角コーナー"
|
||||
},
|
||||
"Start": {
|
||||
"Start": "始める"
|
||||
@@ -1125,7 +1122,7 @@
|
||||
"Storage & Disks": "ストレージとディスク"
|
||||
},
|
||||
"Surface": {
|
||||
"Surface": ""
|
||||
"Surface": "表面"
|
||||
},
|
||||
"Suspend": {
|
||||
"Suspend": "一時停止"
|
||||
@@ -1155,19 +1152,19 @@
|
||||
"System Monitoring:": "システム監視:"
|
||||
},
|
||||
"System Tray": {
|
||||
"System Tray": ""
|
||||
"System Tray": "システムトレイ"
|
||||
},
|
||||
"System Update": {
|
||||
"System Update": ""
|
||||
"System Update": "システムアップデート"
|
||||
},
|
||||
"System Updates": {
|
||||
"System Updates": "システムアップデート"
|
||||
},
|
||||
"System notification area icons": {
|
||||
"System notification area icons": ""
|
||||
"System notification area icons": "システム通知エリアアイコン"
|
||||
},
|
||||
"Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select": {
|
||||
"Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select": ""
|
||||
"Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select": "Tab/Shift+Tab: ナビゲーション • ←→↑↓: グリッドナビゲーション • Enter/Space: 選択"
|
||||
},
|
||||
"Technical Details": {
|
||||
"Technical Details": "技術的な詳細"
|
||||
@@ -1176,16 +1173,16 @@
|
||||
"Temperature": "温度"
|
||||
},
|
||||
"Text": {
|
||||
"Text": ""
|
||||
"Text": "テキスト"
|
||||
},
|
||||
"The DMS_SOCKET environment variable is not set or the socket is unavailable. Automated plugin management requires the DMS_SOCKET.": {
|
||||
"The DMS_SOCKET environment variable is not set or the socket is unavailable. Automated plugin management requires the DMS_SOCKET.": "DMS_SOCKET環境変数が設定されていないか、ソケットが利用できません。自動プラグイン管理にはDMS_SOCKETが必要です。"
|
||||
},
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": {
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": ""
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": "以下の設定は、GTKとQtの設定を変更します。現在の設定を維持したい場合は、バックアップを作成してください(qt5ct.conf|qt6ct.conf および ~/.config/gtk-3.0|gtk-4.0)。"
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": ""
|
||||
"Theme & Colors": "テーマおよびカラー"
|
||||
},
|
||||
"Theme Color": {
|
||||
"Theme Color": "テーマカラー"
|
||||
@@ -1194,16 +1191,16 @@
|
||||
"Third-Party Plugin Warning": "サードパーティ製プラグインの警告"
|
||||
},
|
||||
"Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\\n\\nThese plugins may pose security and privacy risks - install at your own risk.": {
|
||||
"Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\\n\\nThese plugins may pose security and privacy risks - install at your own risk.": ""
|
||||
"Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\\n\\nThese plugins may pose security and privacy risks - install at your own risk.": "サードパーティプラグインはコミュニティによって作成されており、DankMaterialShellによる公式サポートはありません。\\n\\nこれらのプラグインはセキュリティやプライバシーのリスクをもたらす可能性があります - 自己責任でインストールしてください。"
|
||||
},
|
||||
"This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics.": {
|
||||
"This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics.": ""
|
||||
"This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics.": "このウィジェットはGPUの省電力状態を防ぎ、ノートパソコンのバッテリー寿命に大きな影響を与える可能性があります。ハイブリッドグラフィックス搭載のノートパソコンでの使用は推奨されません。"
|
||||
},
|
||||
"This will permanently delete all clipboard history.": {
|
||||
"This will permanently delete all clipboard history.": ""
|
||||
"This will permanently delete all clipboard history.": "これはすべてのクリップボード履歴を完全に削除します。"
|
||||
},
|
||||
"Time & Date": {
|
||||
"Time & Date": ""
|
||||
"Time & Date": "時間と日付"
|
||||
},
|
||||
"Today": {
|
||||
"Today": "今日"
|
||||
@@ -1224,7 +1221,7 @@
|
||||
"Turn off monitors after": "後にモニターの電源を切る"
|
||||
},
|
||||
"Unpin from Dock": {
|
||||
"Unpin from Dock": ""
|
||||
"Unpin from Dock": "ドックから固定を解除"
|
||||
},
|
||||
"Unsaved Changes": {
|
||||
"Unsaved Changes": "保存されていない変更"
|
||||
@@ -1242,7 +1239,7 @@
|
||||
"Update All": "すべて更新"
|
||||
},
|
||||
"Use 24-hour time format instead of 12-hour AM/PM": {
|
||||
"Use 24-hour time format instead of 12-hour AM/PM": ""
|
||||
"Use 24-hour time format instead of 12-hour AM/PM": "12時間制のAM/PMではなく、24時間表記を使用"
|
||||
},
|
||||
"Use Fahrenheit": {
|
||||
"Use Fahrenheit": "華氏を使用する"
|
||||
@@ -1254,7 +1251,7 @@
|
||||
"Use Monospace Font": "等幅フォントを使用"
|
||||
},
|
||||
"Use light theme instead of dark theme": {
|
||||
"Use light theme instead of dark theme": ""
|
||||
"Use light theme instead of dark theme": "ダークテーマではなく、ライトテーマを使用"
|
||||
},
|
||||
"Use%": {
|
||||
"Use%": "使用%"
|
||||
@@ -1263,28 +1260,28 @@
|
||||
"Used": "使用済み"
|
||||
},
|
||||
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": {
|
||||
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": ""
|
||||
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": "あなたの位置情報に基づいて、日の出と日の入りの時間を使ってナイトモードを自動調整します。"
|
||||
},
|
||||
"Utilities": {
|
||||
"Utilities": "ユーティリティ"
|
||||
},
|
||||
"VPN": {
|
||||
"VPN": ""
|
||||
"VPN": "VPN"
|
||||
},
|
||||
"VPN Connections": {
|
||||
"VPN Connections": "VPN接続"
|
||||
},
|
||||
"VPN status and quick connect": {
|
||||
"VPN status and quick connect": ""
|
||||
"VPN status and quick connect": "VPNステータスとクイック接続"
|
||||
},
|
||||
"Visibility": {
|
||||
"Visibility": "可視性"
|
||||
},
|
||||
"Visual divider between widgets": {
|
||||
"Visual divider between widgets": ""
|
||||
"Visual divider between widgets": "ウィジェット間の視覚的分離"
|
||||
},
|
||||
"Visual effect used when wallpaper changes": {
|
||||
"Visual effect used when wallpaper changes": ""
|
||||
"Visual effect used when wallpaper changes": "壁紙が変更される時に使用されるビジュアルエフェクト"
|
||||
},
|
||||
"Wallpaper": {
|
||||
"Wallpaper": "壁紙"
|
||||
@@ -1293,10 +1290,10 @@
|
||||
"Wave Progress Bars": "ウェーブプログレスバー"
|
||||
},
|
||||
"Weather": {
|
||||
"Weather": ""
|
||||
"Weather": "天気"
|
||||
},
|
||||
"Weather Widget": {
|
||||
"Weather Widget": ""
|
||||
"Weather Widget": "天気ウィジェット"
|
||||
},
|
||||
"WiFi is off": {
|
||||
"WiFi is off": "Wi-Fiはオフ中"
|
||||
@@ -1308,7 +1305,7 @@
|
||||
"Widget Styling": "ウィジェットのスタイル"
|
||||
},
|
||||
"Widgets": {
|
||||
"Widgets": ""
|
||||
"Widgets": "ウィジェット"
|
||||
},
|
||||
"Wind": {
|
||||
"Wind": "風"
|
||||
@@ -1326,7 +1323,7 @@
|
||||
"Workspace Settings": "ワークスペース設定"
|
||||
},
|
||||
"Workspace Switcher": {
|
||||
"Workspace Switcher": ""
|
||||
"Workspace Switcher": "ワークスペーススイッチャー"
|
||||
},
|
||||
"You have unsaved changes. Save before closing this tab?": {
|
||||
"You have unsaved changes. Save before closing this tab?": "保存されていない変更があります。このタブを閉じる前に保存しますか?"
|
||||
@@ -1347,7 +1344,7 @@
|
||||
"official": "公式"
|
||||
},
|
||||
"update dms for NM integration.": {
|
||||
"update dms for NM integration.": ""
|
||||
"update dms for NM integration.": "NM統合のためにDMSを更新します。"
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "• 信頼できるソースからのみインストールする"
|
||||
@@ -1387,5 +1384,77 @@
|
||||
},
|
||||
"• yyyy - Year (2024)": {
|
||||
"• yyyy - Year (2024)": "• yyyy - 年(2024年)"
|
||||
},
|
||||
"Border Color": {
|
||||
"Border Color": "ボーダーの色"
|
||||
},
|
||||
"Border Opacity": {
|
||||
"Border Opacity": "ボーダーの透明度"
|
||||
},
|
||||
"Border Thickness": {
|
||||
"Border Thickness": "ボーダーの太さ"
|
||||
},
|
||||
"Choose the border accent color": {
|
||||
"Choose the border accent color": "ボーダーの強調色を選ぶ"
|
||||
},
|
||||
"Enable loginctl lock integration": {
|
||||
"Enable loginctl lock integration": "ログインロックの統合を有効に"
|
||||
},
|
||||
"Execute templates from ~/.config/matugen/config.toml": {
|
||||
"Execute templates from ~/.config/matugen/config.toml": "~/.config/matugen/config.tomlからテンプレを実行"
|
||||
},
|
||||
"QML, JavaScript, Go": {
|
||||
"QML, JavaScript, Go": "QML, JavaScript, Go"
|
||||
},
|
||||
"Run User Templates": {
|
||||
"Run User Templates": "ユーザーのテンプレを実行"
|
||||
},
|
||||
"Current Weather": {
|
||||
"Current Weather": "現在の天気"
|
||||
},
|
||||
"DMS out of date": {
|
||||
"DMS out of date": "DMSに更新があります"
|
||||
},
|
||||
"Gamma Control": {
|
||||
"Gamma Control": "ガンマ設定"
|
||||
},
|
||||
"Idle & Lock Screen": {
|
||||
"Idle & Lock Screen": "アイドルおよびロックスクリーン"
|
||||
},
|
||||
"Matugen Settings": {
|
||||
"Matugen Settings": "Matugen設定"
|
||||
},
|
||||
"Time & Weather": {
|
||||
"Time & Weather": "時間および天気"
|
||||
},
|
||||
"To update, run the following command:": {
|
||||
"To update, run the following command:": "更新するには以下のコマンドを実行してください:"
|
||||
},
|
||||
"loginctl not available - lock integration requires DMS socket connection": {
|
||||
"loginctl not available - lock integration requires DMS socket connection": "loginctlが利用できません- ロック統合のためにDMS socketの接続が必要です。"
|
||||
},
|
||||
"Auto Popup Gaps": {
|
||||
"Auto Popup Gaps": ""
|
||||
},
|
||||
"Automatically calculate popup distance from bar edge.": {
|
||||
"Automatically calculate popup distance from bar edge.": ""
|
||||
},
|
||||
"Manual Gap Size": {
|
||||
"Manual Gap Size": ""
|
||||
},
|
||||
"System Updater": {
|
||||
"System Updater": ""
|
||||
},
|
||||
"System update custom command": {
|
||||
"System update custom command": ""
|
||||
},
|
||||
"Terminal custom additional parameters": {
|
||||
"Terminal custom additional parameters": ""
|
||||
},
|
||||
"Use Custom Command": {
|
||||
"Use Custom Command": ""
|
||||
},
|
||||
"Use custom command for update your system": {
|
||||
"Use custom command for update your system": ""
|
||||
}
|
||||
}
|
||||
1460
translations/poexports/zh_CN.json
Normal file
1460
translations/poexports/zh_CN.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -167,13 +167,6 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Animations",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "App Launcher",
|
||||
"translation": "",
|
||||
@@ -300,6 +293,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Auto Popup Gaps",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Auto-hide",
|
||||
"translation": "",
|
||||
@@ -342,6 +342,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Automatically calculate popup distance from bar edge.",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Automatically cycle through wallpapers in the same folder",
|
||||
"translation": "",
|
||||
@@ -447,6 +454,27 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Border Color",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Border Opacity",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Border Thickness",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Brightness",
|
||||
"translation": "",
|
||||
@@ -545,6 +573,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Choose the border accent color",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Choose the logo displayed on the launcher button in DankBar",
|
||||
"translation": "",
|
||||
@@ -706,13 +741,6 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Control the speed of animations throughout the interface",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Copied to clipboard",
|
||||
"translation": "",
|
||||
@@ -776,6 +804,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Current Weather",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Current time and date display",
|
||||
"translation": "",
|
||||
@@ -839,6 +874,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "DMS out of date",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "DMS_SOCKET not available",
|
||||
"translation": "",
|
||||
@@ -951,13 +993,6 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Display Settings",
|
||||
"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": "",
|
||||
@@ -1084,6 +1119,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Enable loginctl lock integration",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "End",
|
||||
"translation": "",
|
||||
@@ -1119,6 +1161,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Execute templates from ~/.config/matugen/config.toml",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "F1/I: Toggle • F10: Help",
|
||||
"translation": "",
|
||||
@@ -1273,6 +1322,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Gamma Control",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Github:",
|
||||
"translation": "",
|
||||
@@ -1392,6 +1448,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Idle & Lock Screen",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Idle Inhibitor",
|
||||
"translation": "",
|
||||
@@ -1616,6 +1679,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Manual Gap Size",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Manual Show/Hide",
|
||||
"translation": "",
|
||||
@@ -1637,6 +1707,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Matugen Settings",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Max apps to show",
|
||||
"translation": "",
|
||||
@@ -2148,13 +2225,6 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Power",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Power Off",
|
||||
"translation": "",
|
||||
@@ -2212,7 +2282,7 @@
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "QML (Qt Modeling Language)",
|
||||
"term": "QML, JavaScript, Go",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
@@ -2288,6 +2358,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Run User Templates",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Running Apps",
|
||||
"translation": "",
|
||||
@@ -2708,6 +2785,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "System Updater",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "System Updates",
|
||||
"translation": "",
|
||||
@@ -2722,6 +2806,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "System update custom command",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select",
|
||||
"translation": "",
|
||||
@@ -2743,6 +2834,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Terminal custom additional parameters",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Text",
|
||||
"translation": "",
|
||||
@@ -2807,7 +2905,14 @@
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Time & Date",
|
||||
"term": "Time & Weather",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "To update, run the following command:",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
@@ -2904,6 +3009,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Use Custom Command",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Use Fahrenheit",
|
||||
"translation": "",
|
||||
@@ -2925,6 +3037,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Use custom command for update your system",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Use light theme instead of dark theme",
|
||||
"translation": "",
|
||||
@@ -3128,6 +3247,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "loginctl not available - lock integration requires DMS socket connection",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "matugen not detected - dynamic theming unavailable",
|
||||
"translation": "",
|
||||
|
||||
Reference in New Issue
Block a user