mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-22 11:14:01 -04:00
Compare commits
20
Commits
v2.0.0
..
stable-2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ab57f14b8 | ||
|
|
ce81dfc95a | ||
|
|
a5511fe8d7 | ||
|
|
7d930f8f6c | ||
|
|
11ef76b9d0 | ||
|
|
e48de4cc2a | ||
|
|
1683fceac5 | ||
|
|
2a83c5c69b | ||
|
|
1acfae1755 | ||
|
|
1e1dbebb70 | ||
|
|
53412d1f22 | ||
|
|
9ece8f26fa | ||
|
|
fd1295f959 | ||
|
|
1e3f16ac9d | ||
|
|
4fa6f58720 | ||
|
|
ff45b3b4b2 | ||
|
|
12e54e0225 | ||
|
|
f63d8bf4fb | ||
|
|
1e8423992d | ||
|
|
c4ecb988b4 |
@@ -100,7 +100,7 @@ Notification center with grouping, rich text support, and keyboard navigation.
|
||||
MPRIS player controls, calendar sync, weather widgets, and clipboard history with image previews.
|
||||
|
||||
**Session Management**
|
||||
Lock screen, idle detection, auto-lock/suspend with separate AC/battery settings, and a settings front-end for [dank-greeter](https://github.com/duxlabs-dev/dux-greeter).
|
||||
Lock screen, idle detection, auto-lock/suspend with separate AC/battery settings, and a settings front-end for [dux-greeter](https://github.com/duxlabs-dev/dux-greeter).
|
||||
|
||||
**Plugin System**
|
||||
Extend functionality with the [plugin registry](https://plugins.duxlabs.dev). DMS keeps
|
||||
@@ -138,16 +138,16 @@ dms plugins restore ~/plugins.lock.json
|
||||
- **Plugins:** [Development guide](https://duxlabs.dev/docs/dms/plugins-overview)
|
||||
- **Support:** [Ko-fi](https://ko-fi.com/duxlabs)
|
||||
|
||||
## Dank Projects
|
||||
## Dux Projects
|
||||
|
||||
DMS is one piece of the suite. The rest lives in its own repos:
|
||||
|
||||
- [dank-greeter](https://github.com/duxlabs-dev/dux-greeter) - greetd login screen with the Dux Shell aesthetic. The Greeter tab in DMS settings is the front-end for it.
|
||||
- [dankcalendar](https://github.com/duxlabs-dev/dux-calendar) - Local, Google, Microsoft, and CalDAV calendars for the Dux desktop.
|
||||
- [dux-greeter](https://github.com/duxlabs-dev/dux-greeter) - greetd login screen with the Dux Shell aesthetic. The Greeter tab in DMS settings is the front-end for it.
|
||||
- [dux-calendar](https://github.com/duxlabs-dev/dux-calendar) - Local, Google, Microsoft, and CalDAV calendars for the Dux desktop.
|
||||
- [dgop](https://github.com/duxlabs-dev/dux-gop) - System monitoring TUI and Go library; its library powers the process list and dashboard widgets inside the dms daemon.
|
||||
- [dsearch](https://github.com/duxlabs-dev/dux-search) - Fast file search that powers file results in the launcher.
|
||||
- [dux-qml-common](https://github.com/duxlabs-dev/dux-qml-common) - Shared QML widgets and components used by DMS, dank-greeter, and dankcalendar.
|
||||
- [dankgo](https://github.com/duxlabs-dev/dux-go) - Common Go modules behind the single binary apps.
|
||||
- [dux-qml-common](https://github.com/duxlabs-dev/dux-qml-common) - Shared QML widgets and components used by DMS, dux-greeter, and dux-calendar.
|
||||
- [dux-go](https://github.com/duxlabs-dev/dux-go) - Common Go modules behind the single binary apps.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -193,7 +193,7 @@ Contributions welcome. Bug fixes, widgets, features, documentation, and plugins
|
||||
3. Test thoroughly
|
||||
4. Open a pull request
|
||||
|
||||
For documentation contributions, see [DankLinux-Docs](https://github.com/duxlabs-dev/dux-docs).
|
||||
For documentation contributions, see [dux-docs](https://github.com/duxlabs-dev/dux-docs).
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/duxlabs-dev/dux-go/shellapp"
|
||||
@@ -83,12 +85,36 @@ func preLaunch() {
|
||||
ensureFontCache()
|
||||
}
|
||||
|
||||
// The elapsed-timer driver keeps the scrolling wallpaper spring in step above 60 Hz,
|
||||
// but it makes every other animation jump after a dropped frame (#3837).
|
||||
func scrollingWallpaperConfigured() bool {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(configDir, "dms", "settings.json"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return wantsSimpleAnimationDriver(data)
|
||||
}
|
||||
|
||||
func wantsSimpleAnimationDriver(settings []byte) bool {
|
||||
var parsed struct {
|
||||
WallpaperFillMode string `json:"wallpaperFillMode"`
|
||||
}
|
||||
if json.Unmarshal(settings, &parsed) != nil {
|
||||
return false
|
||||
}
|
||||
return parsed.WallpaperFillMode == "Scrolling"
|
||||
}
|
||||
|
||||
func dmsExtraEnv(string) []string {
|
||||
var env []string
|
||||
if selfPath, err := os.Executable(); err == nil {
|
||||
env = append(env, "DMS_EXECUTABLE="+selfPath)
|
||||
}
|
||||
if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" {
|
||||
if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" && scrollingWallpaperConfigured() {
|
||||
env = append(env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1")
|
||||
}
|
||||
if _, set := os.LookupEnv("MALLOC_CONF"); !set {
|
||||
|
||||
@@ -965,6 +965,7 @@ var vscodeEditors = []vscodeEditor{
|
||||
{"cursor", ".cursor"},
|
||||
{"windsurf", ".windsurf"},
|
||||
{"positron", ".positron"},
|
||||
{"antigravity", ".antigravity-ide"},
|
||||
{"vscode-insiders", ".vscode-insiders"},
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ include_package() {
|
||||
[[ "$PACKAGE_FILTER" == "all" || "$PACKAGE_FILTER" == "$package" ]]
|
||||
}
|
||||
|
||||
CURRENT_COMMIT="$(git rev-parse --short=8 HEAD)"
|
||||
CURRENT_COMMIT="$(git rev-parse HEAD)"
|
||||
LATEST_TAG=""
|
||||
TARGETS=()
|
||||
|
||||
@@ -118,9 +118,9 @@ for pkg_info in "${PACKAGES[@]}"; do
|
||||
reason="missing from ${series}"
|
||||
elif [[ "$type" == "git" ]]; then
|
||||
ppa_commit="$(embedded_commit "$ppa_version")"
|
||||
if [[ "$ppa_commit" != "$CURRENT_COMMIT" ]]; then
|
||||
if [[ -z "$ppa_commit" || "${CURRENT_COMMIT:0:${#ppa_commit}}" != "$ppa_commit" ]]; then
|
||||
needs_update=true
|
||||
reason="commit ${ppa_commit:-none} -> ${CURRENT_COMMIT}"
|
||||
reason="commit ${ppa_commit:-none} -> ${CURRENT_COMMIT:0:9}"
|
||||
fi
|
||||
else
|
||||
if [[ -z "$LATEST_TAG" ]]; then
|
||||
|
||||
+1
-1
Submodule dux-qml-common updated: ebf5e6c002...5715e8ea86
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1792513666,
|
||||
"narHash": "sha256-RDqPdPrM5lnwRrfFGLz3IwgTjCV2Ie+nrTD3ldj1t/s=",
|
||||
"lastModified": 1792597131,
|
||||
"narHash": "sha256-2t9btOQ2owoRv/fgeonlr8cHVt2NUvUu9iXdZd9+QFc=",
|
||||
"owner": "duxlabs-dev",
|
||||
"repo": "dux-qml-common",
|
||||
"rev": "ebf5e6c002da1bdaa4aaa2f319fea7b8507abc42",
|
||||
"rev": "5715e8ea868f3a131a0f54c7a5875e9a95f2f253",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -13,8 +13,6 @@ Singleton {
|
||||
|
||||
readonly property int cacheConfigVersion: 3
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericCacheLocation)
|
||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||
|
||||
@@ -52,8 +50,6 @@ Singleton {
|
||||
property var fileBrowserSettings: ({})
|
||||
|
||||
Component.onCompleted: {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
loadCache();
|
||||
}
|
||||
|
||||
@@ -280,7 +276,7 @@ Singleton {
|
||||
FileView {
|
||||
id: launcherCacheFile
|
||||
|
||||
path: isGreeterMode ? "" : _stateDir + "/dms/launcher_cache.json"
|
||||
path: _stateDir + "/dms/launcher_cache.json"
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
@@ -290,19 +286,15 @@ Singleton {
|
||||
FileView {
|
||||
id: cacheFile
|
||||
|
||||
path: isGreeterMode ? "" : _stateDir + "/dms/cache.json"
|
||||
path: _stateDir + "/dms/cache.json"
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
watchChanges: !isGreeterMode
|
||||
watchChanges: true
|
||||
onLoaded: {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
parseCache(cacheFile.text());
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
log.info("No cache file found, starting fresh");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ Singleton {
|
||||
|
||||
readonly property int sessionConfigVersion: 7
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
signal loaded
|
||||
signal brightnessDisplayHintChanged(string deviceName)
|
||||
signal loadErrorOccurred(string file, string message)
|
||||
@@ -326,11 +324,6 @@ Singleton {
|
||||
_hasUnsavedChanges = false;
|
||||
_pendingMigration = null;
|
||||
|
||||
if (isGreeterMode) {
|
||||
parseSettings(greeterSessionFile.text());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const txt = settingsFile.text();
|
||||
let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
|
||||
@@ -369,7 +362,7 @@ Singleton {
|
||||
_loadedSessionSnapshot = getCurrentSessionJson();
|
||||
_hasLoaded = true;
|
||||
|
||||
if (!isGreeterMode && typeof Theme !== "undefined")
|
||||
if (typeof Theme !== "undefined")
|
||||
Theme.generateSystemThemesFromCurrentTheme();
|
||||
|
||||
loaded();
|
||||
@@ -452,7 +445,7 @@ Singleton {
|
||||
_loadedSessionSnapshot = getCurrentSessionJson();
|
||||
_hasLoaded = true;
|
||||
|
||||
if (!isGreeterMode && typeof Theme !== "undefined")
|
||||
if (typeof Theme !== "undefined")
|
||||
Theme.generateSystemThemesFromCurrentTheme();
|
||||
|
||||
loaded();
|
||||
@@ -485,7 +478,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
if (isGreeterMode || _parseError || !_hasLoaded)
|
||||
if (_parseError || !_hasLoaded)
|
||||
return;
|
||||
settingsFile.setText(getCurrentSessionJson());
|
||||
if (_isReadOnly)
|
||||
@@ -859,8 +852,6 @@ Singleton {
|
||||
}
|
||||
|
||||
function _screenShareShouldHold() {
|
||||
if (isGreeterMode)
|
||||
return false;
|
||||
if (typeof SettingsData === "undefined" || !SettingsData.notificationDndWhileScreenSharing)
|
||||
return false;
|
||||
if (typeof PrivacyService === "undefined" || !PrivacyService.screensharingActive)
|
||||
@@ -909,9 +900,6 @@ Singleton {
|
||||
}
|
||||
|
||||
function syncScreenShareDnd() {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
|
||||
if (!_screenShareShouldHold()) {
|
||||
if (!doNotDisturbHeldByScreenShare && !screenShareDndDismissed)
|
||||
return;
|
||||
@@ -1883,14 +1871,12 @@ Singleton {
|
||||
FileView {
|
||||
id: settingsFile
|
||||
|
||||
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/dms/session.json"
|
||||
path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/dms/session.json"
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
watchChanges: !isGreeterMode
|
||||
watchChanges: true
|
||||
onLoaded: {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
_hasUnsavedChanges = false;
|
||||
parseSettings(settingsFile.text());
|
||||
}
|
||||
@@ -1900,39 +1886,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property string _greeterCacheDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
|
||||
|
||||
property string greeterSessionBaseDir: root._greeterCacheDir
|
||||
|
||||
function setGreeterSessionBaseDir(dir) {
|
||||
const next = dir || root._greeterCacheDir;
|
||||
if (greeterSessionBaseDir === next)
|
||||
return;
|
||||
greeterSessionBaseDir = next;
|
||||
if (isGreeterMode)
|
||||
greeterSessionFile.reload();
|
||||
}
|
||||
|
||||
function resetGreeterSessionBaseDir() {
|
||||
setGreeterSessionBaseDir(root._greeterCacheDir);
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: greeterSessionFile
|
||||
|
||||
path: isGreeterMode && root.greeterSessionBaseDir ? (root.greeterSessionBaseDir + "/session.json") : ""
|
||||
preload: isGreeterMode
|
||||
blockLoading: false
|
||||
blockWrites: true
|
||||
watchChanges: false
|
||||
printErrors: true
|
||||
onLoaded: {
|
||||
if (isGreeterMode) {
|
||||
parseSettings(greeterSessionFile.text());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: sessionWritableCheckProcess
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ Singleton {
|
||||
|
||||
readonly property int settingsConfigVersion: 42
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
enum Position {
|
||||
Top,
|
||||
Bottom,
|
||||
@@ -1435,8 +1433,6 @@ Singleton {
|
||||
signal notificationPopupsInvalidated
|
||||
|
||||
function refreshAuthAvailability() {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
Processes.detectAuthCapabilities();
|
||||
}
|
||||
|
||||
@@ -1506,8 +1502,6 @@ Singleton {
|
||||
}
|
||||
|
||||
function checkIconThemeDrift() {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
if (resolveIconTheme() === "System Default")
|
||||
return;
|
||||
if (!SessionData.lastAppliedIconTheme)
|
||||
@@ -1664,8 +1658,6 @@ Singleton {
|
||||
}
|
||||
|
||||
function scheduleAuthApply() {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
Qt.callLater(() => {
|
||||
Processes.settingsRoot = root;
|
||||
Processes.scheduleAuthApply();
|
||||
@@ -1673,8 +1665,6 @@ Singleton {
|
||||
}
|
||||
|
||||
function scheduleGreeterAutoLoginSync() {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
Qt.callLater(() => {
|
||||
Processes.settingsRoot = root;
|
||||
Processes.scheduleGreeterAutoLoginSync();
|
||||
@@ -1689,8 +1679,6 @@ Singleton {
|
||||
}
|
||||
|
||||
function markGreeterSyncPending(who, key, oldValue) {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
if (!(key in SessionData.greeterSyncBaseline)) {
|
||||
var baseline = Object.assign({}, SessionData.greeterSyncBaseline);
|
||||
baseline[key] = oldValue;
|
||||
@@ -1840,7 +1828,7 @@ Singleton {
|
||||
let loadedSettings = JSON.stringify(obj);
|
||||
|
||||
const oldVersion = obj?.configVersion ?? 0;
|
||||
if (oldVersion > settingsConfigVersion && !isGreeterMode) {
|
||||
if (oldVersion > settingsConfigVersion) {
|
||||
newerSettingsBackup.path = `${_configDir}/dms/settings.v${oldVersion}.json`;
|
||||
newerSettingsBackup.setText(JSON.stringify(obj, null, 2));
|
||||
}
|
||||
@@ -2063,16 +2051,14 @@ Singleton {
|
||||
return splitSettings;
|
||||
}
|
||||
function saveSettings() {
|
||||
if (isGreeterMode || _loading || !_hasLoaded)
|
||||
if (_loading || !_hasLoaded)
|
||||
return;
|
||||
unsavedUserChanges = true;
|
||||
settingsSaveDebounce.restart();
|
||||
}
|
||||
function _saveSettings(userChanges) {
|
||||
let reason = null;
|
||||
if (isGreeterMode) {
|
||||
reason = "running in greeter mode.";
|
||||
} else if (_loading) {
|
||||
if (_loading) {
|
||||
reason = "some files are being loaded";
|
||||
} else if (_parseError) {
|
||||
reason = "failed to parse settings.";
|
||||
@@ -2092,7 +2078,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function savePluginSettings() {
|
||||
if (isGreeterMode || _pluginSettingsLoading || _pluginParseError)
|
||||
if (_pluginSettingsLoading || _pluginParseError)
|
||||
return;
|
||||
pluginSettingsFile.setText(JSON.stringify(pluginSettings, null, 2));
|
||||
}
|
||||
@@ -3725,11 +3711,11 @@ Singleton {
|
||||
property FileView fileView: FileView {
|
||||
id: settingsFileView
|
||||
|
||||
path: isGreeterMode ? "" : filePath
|
||||
path: filePath
|
||||
blockLoading: false
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
watchChanges: !isGreeterMode
|
||||
watchChanges: true
|
||||
onFileChanged: {
|
||||
if (selfWrite) {
|
||||
selfWrite = false;
|
||||
@@ -3740,9 +3726,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
onLoaded: {
|
||||
if (isGreeterMode) {
|
||||
return;
|
||||
}
|
||||
isLoading = true;
|
||||
_loading = true;
|
||||
const hadParseFailed = hasParseFailed;
|
||||
@@ -3776,9 +3759,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
if (isGreeterMode) {
|
||||
return;
|
||||
}
|
||||
isLoading = false;
|
||||
_loading = _anySettingsFile(file => file.isLoading);
|
||||
if (error === FileViewError.FileNotFound) {
|
||||
@@ -3922,7 +3902,7 @@ Singleton {
|
||||
}
|
||||
|
||||
property bool _settingsFolderExists: false
|
||||
property bool _settingsFolderChecked: isGreeterMode
|
||||
property bool _settingsFolderChecked: false
|
||||
function _syncSettingsFilesModels() {
|
||||
if (!_settingsFolderChecked) {
|
||||
settingsFilesModelSyncDebounce.restart();
|
||||
@@ -3963,7 +3943,6 @@ Singleton {
|
||||
}
|
||||
Loader {
|
||||
id: settingsFolderLoader
|
||||
active: !isGreeterMode
|
||||
function unloadModel() {
|
||||
active = false;
|
||||
}
|
||||
@@ -4012,59 +3991,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property string _greeterCacheDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
|
||||
|
||||
property string greeterSettingsBaseDir: root._greeterCacheDir
|
||||
|
||||
function setGreeterSettingsBaseDir(dir) {
|
||||
const next = dir || root._greeterCacheDir;
|
||||
if (greeterSettingsBaseDir === next)
|
||||
return;
|
||||
greeterSettingsBaseDir = next;
|
||||
if (isGreeterMode)
|
||||
greeterSettingsFile.reload();
|
||||
}
|
||||
|
||||
function resetGreeterSettingsBaseDir() {
|
||||
setGreeterSettingsBaseDir(root._greeterCacheDir);
|
||||
}
|
||||
|
||||
function loadGreeterSettings(txt) {
|
||||
_loading = true;
|
||||
_hasLoaded = false;
|
||||
try {
|
||||
Store.parse(root, (txt && txt.trim()) ? JSON.parse(txt) : {});
|
||||
_parseError = false;
|
||||
} catch (e) {
|
||||
_parseError = true;
|
||||
log.error("Failed to parse greeter settings.json:", e.message);
|
||||
Store.parse(root, {});
|
||||
} finally {
|
||||
_loading = false;
|
||||
}
|
||||
_hasLoaded = true;
|
||||
applyStoredTheme();
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: greeterSettingsFile
|
||||
|
||||
path: isGreeterMode && root.greeterSettingsBaseDir ? (root.greeterSettingsBaseDir + "/settings.json") : ""
|
||||
preload: isGreeterMode
|
||||
blockLoading: false
|
||||
blockWrites: true
|
||||
watchChanges: false
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
if (isGreeterMode)
|
||||
loadGreeterSettings(greeterSettingsFile.text());
|
||||
}
|
||||
onLoadFailed: {
|
||||
if (isGreeterMode)
|
||||
loadGreeterSettings("");
|
||||
}
|
||||
}
|
||||
|
||||
// Our first save drops the keys a newer shell wrote, so its settings are kept for going back.
|
||||
FileView {
|
||||
id: newerSettingsBackup
|
||||
@@ -4078,21 +4004,17 @@ Singleton {
|
||||
FileView {
|
||||
id: pluginSettingsFile
|
||||
|
||||
path: isGreeterMode ? "" : pluginSettingsPath
|
||||
path: pluginSettingsPath
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
printErrors: false
|
||||
watchChanges: !isGreeterMode
|
||||
watchChanges: true
|
||||
onLoaded: {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
pluginSettingsFileExists = true;
|
||||
parsePluginSettings(pluginSettingsFile.text());
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
const msg = String(error || "");
|
||||
const missing = _isMissingPluginSettingsError(error);
|
||||
pluginSettingsFileExists = !missing;
|
||||
|
||||
@@ -160,6 +160,7 @@ Singleton {
|
||||
"children": [
|
||||
{
|
||||
"id": "dock_general",
|
||||
"hidden": true,
|
||||
"text": I18n.tr("General"),
|
||||
"icon": "tune",
|
||||
"tabIndex": 5,
|
||||
@@ -167,6 +168,7 @@ Singleton {
|
||||
},
|
||||
{
|
||||
"id": "dock_widgets",
|
||||
"hidden": true,
|
||||
"text": I18n.tr("Apps & widgets"),
|
||||
"icon": "widgets",
|
||||
"tabIndex": 57,
|
||||
@@ -174,6 +176,7 @@ Singleton {
|
||||
},
|
||||
{
|
||||
"id": "dock_appearance",
|
||||
"hidden": true,
|
||||
"text": I18n.tr("Appearance"),
|
||||
"icon": "palette",
|
||||
"tabIndex": 58,
|
||||
|
||||
+43
-51
@@ -13,7 +13,6 @@ import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.DCommon.Common as DCommon
|
||||
import qs.Services
|
||||
import qs.Modules.Greetd
|
||||
import "StockThemes.js" as StockThemes
|
||||
import "GSettings.js" as GSettings
|
||||
|
||||
@@ -1191,13 +1190,7 @@ Singleton {
|
||||
readonly property real groupedListInnerRadius: cornerRadiusXS
|
||||
readonly property real groupedListOuterRadius: cornerRadiusL
|
||||
|
||||
property string fontFamily: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return DCommon.Fonts.sans;
|
||||
if (SettingsData.isGreeterMode && SettingsData.lockScreenFontFamily !== "")
|
||||
return resolvedFontFamily(SettingsData.lockScreenFontFamily);
|
||||
return resolvedFontFamily(SettingsData.fontFamily);
|
||||
}
|
||||
property string fontFamily: typeof SettingsData !== "undefined" ? resolvedFontFamily(SettingsData.fontFamily) : DCommon.Fonts.sans
|
||||
|
||||
property string monoFontFamily: typeof SettingsData !== "undefined" ? resolvedMonoFontFamily(SettingsData.monoFontFamily) : DCommon.Fonts.mono
|
||||
property string displayFontFamily: typeof SettingsData !== "undefined" ? resolvedDisplayFontFamily(SettingsData.displayFontFamily) : DCommon.Fonts.display
|
||||
@@ -1422,15 +1415,12 @@ Singleton {
|
||||
currentThemeCategory = "generic";
|
||||
}
|
||||
}
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
|
||||
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode) {
|
||||
if (savePrefs && typeof SettingsData !== "undefined") {
|
||||
SettingsData.set("currentThemeCategory", currentThemeCategory);
|
||||
SettingsData.set("currentThemeName", currentTheme);
|
||||
}
|
||||
|
||||
if (!isGreeterMode) {
|
||||
generateSystemThemesFromCurrentTheme();
|
||||
}
|
||||
generateSystemThemesFromCurrentTheme();
|
||||
}
|
||||
|
||||
function setLightMode(light, savePrefs = true, enableTransition = false) {
|
||||
@@ -1447,17 +1437,14 @@ Singleton {
|
||||
return;
|
||||
}
|
||||
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
|
||||
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) {
|
||||
if (savePrefs && typeof SessionData !== "undefined") {
|
||||
SessionData.setLightMode(light);
|
||||
}
|
||||
|
||||
if (!isGreeterMode) {
|
||||
if (typeof SettingsData !== "undefined") {
|
||||
SettingsData.updateCosmicThemeMode(light);
|
||||
}
|
||||
generateSystemThemesFromCurrentTheme();
|
||||
if (typeof SettingsData !== "undefined") {
|
||||
SettingsData.updateCosmicThemeMode(light);
|
||||
}
|
||||
generateSystemThemesFromCurrentTheme();
|
||||
}
|
||||
|
||||
function toggleLightMode(savePrefs = true) {
|
||||
@@ -1876,9 +1863,9 @@ Singleton {
|
||||
}
|
||||
|
||||
const runKey = Qt.md5(JSON.stringify(args));
|
||||
const skipUnchanged = !_matugenRunKey && !colorsFileLoadFailed && typeof SettingsData !== "undefined" && !SettingsData.generateThemeAtStartup;
|
||||
if (skipUnchanged && runKey === CacheData.matugenAppliedKey) {
|
||||
log.info("Theme inputs unchanged since the last run, skipping startup generation");
|
||||
const startupRun = !_matugenRunKey && typeof SettingsData !== "undefined" && SettingsData.generateThemeAtStartup;
|
||||
if (!startupRun && !colorsFileLoadFailed && runKey === CacheData.matugenAppliedKey) {
|
||||
log.info("Theme inputs unchanged since the last run, skipping generation");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1938,8 +1925,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function generateSystemThemesFromCurrentTheme() {
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
|
||||
if (!matugenAvailable || isGreeterMode)
|
||||
if (!matugenAvailable)
|
||||
return;
|
||||
|
||||
_pendingGenerateParams = true;
|
||||
@@ -2395,32 +2381,11 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property string _greeterCacheDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
|
||||
|
||||
property string greeterColorsBaseDir: root._greeterCacheDir
|
||||
|
||||
function setGreeterColorsBaseDir(dir) {
|
||||
const next = dir || root._greeterCacheDir;
|
||||
if (greeterColorsBaseDir === next)
|
||||
return;
|
||||
greeterColorsBaseDir = next;
|
||||
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
|
||||
dynamicColorsFileView.reload();
|
||||
}
|
||||
|
||||
function resetGreeterColorsBaseDir() {
|
||||
setGreeterColorsBaseDir(root._greeterCacheDir);
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: dynamicColorsFileView
|
||||
path: {
|
||||
if (SessionData.isGreeterMode)
|
||||
return root.greeterColorsBaseDir ? (root.greeterColorsBaseDir + "/colors.json") : "";
|
||||
return stateDir + "/dms-colors.json";
|
||||
}
|
||||
path: stateDir + "/dms-colors.json"
|
||||
blockLoading: false
|
||||
watchChanges: !SessionData.isGreeterMode
|
||||
watchChanges: true
|
||||
|
||||
function parseAndLoadColors() {
|
||||
try {
|
||||
@@ -2462,9 +2427,6 @@ Singleton {
|
||||
if (currentTheme !== dynamic)
|
||||
return;
|
||||
|
||||
if (SessionData.isGreeterMode)
|
||||
return;
|
||||
|
||||
if (workerRunning) {
|
||||
colorsReloadRetry.restart();
|
||||
return;
|
||||
@@ -2496,6 +2458,36 @@ Singleton {
|
||||
onTriggered: dynamicColorsFileView.reload()
|
||||
}
|
||||
|
||||
readonly property bool publishTypography: typeof SettingsData !== "undefined" && SettingsData._hasLoaded
|
||||
readonly property string typographyJson: JSON.stringify({
|
||||
"fontFamily": fontFamily,
|
||||
"monoFontFamily": monoFontFamily,
|
||||
"displayFontFamily": displayFontFamily,
|
||||
"fontWeight": fontWeight,
|
||||
"fontScale": fontScale
|
||||
}, null, 2)
|
||||
|
||||
onPublishTypographyChanged: typographyPublishDebounce.restart()
|
||||
onTypographyJsonChanged: typographyPublishDebounce.restart()
|
||||
|
||||
FileView {
|
||||
id: typographyFileView
|
||||
path: root.publishTypography ? root.stateDir + "/dms-typography.json" : ""
|
||||
blockLoading: false
|
||||
printErrors: false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: typographyPublishDebounce
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (!root.publishTypography)
|
||||
return;
|
||||
typographyFileView.setText(root.typographyJson);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "theme"
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ function mangoActiveWorkspace(raw, screenName) {
|
||||
|
||||
function mangoWindowsOnWorkspace(windows, workspace) {
|
||||
// mangoTags are 1-based; workspace.id is 0-based.
|
||||
return windows.filter(win => win && (win.mangoTags || []).includes(workspace.id + 1));
|
||||
return windows.filter(win => win && win.mangoMonitor === workspace.output && (win.mangoTags || []).includes(workspace.id + 1));
|
||||
}
|
||||
|
||||
function i3Key(ws) {
|
||||
|
||||
@@ -16,14 +16,14 @@ Singleton {
|
||||
property var settingsRoot: null
|
||||
|
||||
onSettingsRootChanged: {
|
||||
if (settingsRoot && !settingsRoot.isGreeterMode)
|
||||
if (settingsRoot)
|
||||
consumeGreeterAutoLoginPendingSync();
|
||||
}
|
||||
|
||||
readonly property string greeterAutoLoginPendingSyncPath: (Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter") + "/.local/state/auto-login-sync-pending"
|
||||
|
||||
function consumeGreeterAutoLoginPendingSync() {
|
||||
if (!settingsRoot || settingsRoot.isGreeterMode)
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
greeterAutoLoginPendingCheckProcess.running = true;
|
||||
}
|
||||
@@ -292,7 +292,7 @@ Singleton {
|
||||
property string authApplyTerminalFallbackStderr: ""
|
||||
|
||||
function scheduleAuthApply() {
|
||||
if (!settingsRoot || settingsRoot.isGreeterMode)
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
|
||||
authApplyQueued = true;
|
||||
@@ -305,7 +305,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function beginAuthApply() {
|
||||
if (!authApplyQueued || authApplyRunning || !settingsRoot || settingsRoot.isGreeterMode)
|
||||
if (!authApplyQueued || authApplyRunning || !settingsRoot)
|
||||
return;
|
||||
|
||||
authApplyQueued = false;
|
||||
@@ -344,7 +344,7 @@ Singleton {
|
||||
property string greeterAutoLoginSyncStderr: ""
|
||||
|
||||
function scheduleGreeterAutoLoginSync() {
|
||||
if (!settingsRoot || settingsRoot.isGreeterMode)
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
|
||||
greeterAutoLoginSyncQueued = true;
|
||||
@@ -357,7 +357,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function beginGreeterAutoLoginSync() {
|
||||
if (!greeterAutoLoginSyncQueued || greeterAutoLoginSyncRunning || !settingsRoot || settingsRoot.isGreeterMode)
|
||||
if (!greeterAutoLoginSyncQueued || greeterAutoLoginSyncRunning || !settingsRoot)
|
||||
return;
|
||||
|
||||
greeterAutoLoginSyncQueued = false;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Modules.Greetd
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
GreeterSurface {}
|
||||
}
|
||||
@@ -744,8 +744,12 @@ Item {
|
||||
readonly property real barSurfaceThickness: Theme.px(effectiveBarThickness + surfaceSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel && spansEdge ? _wingR : 0), _dpr) + _shadowBuffer
|
||||
readonly property bool islandSheetOut: islandHost?.sheetOut ?? false
|
||||
readonly property real islandSatelliteShadowBuffer: isIsland && islandSatellitesEnabled && !islandSatelliteBackground ? _shadowBuffer : 0
|
||||
readonly property real hostThickness: isIsland ? Math.max(islandHost?.hostThickness ?? 0, islandStripThickness + islandSatelliteShadowBuffer) : Math.max(barSurfaceThickness, islandSheetOut ? islandHost.hostThickness : 0)
|
||||
readonly property real hideSlideThickness: isIsland ? hostThickness : barSurfaceThickness
|
||||
// A grown layer surface jumps to its new origin before the client draws at the new size, so a far-edge host never grows for the sheet.
|
||||
readonly property bool islandReservesSheet: !!islandHost && (axis.edge === "bottom" || axis.edge === "right")
|
||||
readonly property real hostThickness: isIsland ? Math.max(islandHost?.hostThickness ?? 0, islandStripThickness + islandSatelliteShadowBuffer) : Math.max(barSurfaceThickness, islandSheetOut || islandReservesSheet ? islandHost.hostThickness : 0)
|
||||
// The host reserves the expanded island's height, so sliding by it would move a compact island off-screen within a frame.
|
||||
readonly property real islandCompactThickness: Math.max(islandStripThickness + islandSatelliteShadowBuffer, (islandHost?.outerGap ?? 0) + (islandHost?.compactThickness ?? 0) + (islandHost?.shadowPadding ?? 0))
|
||||
readonly property real hideSlideThickness: isIsland ? islandCompactThickness : barSurfaceThickness
|
||||
readonly property real surfaceImplicitHeight: !isVertical ? hostThickness : 0
|
||||
readonly property real surfaceImplicitWidth: isVertical ? hostThickness : 0
|
||||
|
||||
|
||||
@@ -39,26 +39,20 @@ BasePill {
|
||||
return Qt.rgba(from.r + (to.r - from.r) * progress, from.g + (to.g - from.g) * progress, from.b + (to.b - from.b) * progress, from.a + (to.a - from.a) * progress);
|
||||
}
|
||||
|
||||
SequentialAnimation on criticalPulse {
|
||||
property int criticalTick: 0
|
||||
|
||||
// Stepped on purpose: an Animation repaints the bar at the full refresh rate.
|
||||
Timer {
|
||||
interval: 50
|
||||
repeat: true
|
||||
running: battery.critical && battery.surfaceLive && !SettingsData.reduceMotion
|
||||
loops: Animation.Infinite
|
||||
onRunningChanged: {
|
||||
if (!running)
|
||||
battery.criticalPulse = 0;
|
||||
battery.criticalTick = 0;
|
||||
battery.criticalPulse = 0;
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 1000
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
from: 1
|
||||
to: 0
|
||||
duration: 1000
|
||||
easing.type: Easing.Linear
|
||||
onTriggered: {
|
||||
battery.criticalTick = (battery.criticalTick + 1) % 40;
|
||||
battery.criticalPulse = 1 - Math.abs(20 - battery.criticalTick) / 20;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ BasePill {
|
||||
}
|
||||
property bool isOccupied: root.useNativeWorkspaces && CompositorService.workspaceOccupied(record)
|
||||
property bool isPlaceholder: !!(record && record.placeholder)
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
property bool isHovered: pillHover.hovered
|
||||
|
||||
property bool loadedIsUrgent: false
|
||||
property bool isUrgent: root.useNativeWorkspaces ? CompositorService.workspaceUrgent(record, loadedIsUrgent) : (modelData?.urgent ?? false)
|
||||
@@ -615,6 +615,11 @@ BasePill {
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: pillHover
|
||||
enabled: !isPlaceholder
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -680,6 +680,8 @@ DPopout {
|
||||
readonly property bool ready: targetHeight >= 0 && settledHeight === targetHeight
|
||||
readonly property real availableHeight: root.screen ? root.screen.height - mainContainer.horizontalChromeHeight - DashMetrics.contentPadding * 2 - Theme.barHeight - Theme.spacingL * 2 : contentHeight
|
||||
readonly property int rowBudget: DashMetrics.rowCapFor(availableHeight)
|
||||
// Growing a panel not hung from a top bar moves the grid under a held card, which retargets the drop.
|
||||
readonly property bool heightHeld: (currentItem?.cardInteracting ?? false) && root.effectiveBarPosition % 4 !== SettingsData.Top
|
||||
|
||||
x: (root.verticalNavigation && root.navigationEdge === SettingsData.Left ? root.navigationWidth : 0) - root.editGutter
|
||||
y: (!root.verticalNavigation && root.navigationEdge !== SettingsData.Bottom ? mainContainer.horizontalChromeHeight : 0) - root.editGutter
|
||||
@@ -691,12 +693,13 @@ DPopout {
|
||||
clip: contentHeight > height
|
||||
|
||||
function updateContentHeight() {
|
||||
if (targetHeight < 0)
|
||||
if (targetHeight < 0 || heightHeld)
|
||||
return;
|
||||
settledHeight = targetHeight;
|
||||
}
|
||||
|
||||
onTargetHeightChanged: Qt.callLater(updateContentHeight)
|
||||
onHeightHeldChanged: Qt.callLater(updateContentHeight)
|
||||
|
||||
Column {
|
||||
id: emptyContent
|
||||
|
||||
@@ -214,6 +214,7 @@ Singleton {
|
||||
readonly property int mediaLyricsRequestTimeout: 16000
|
||||
readonly property int mediaLyricsLoadingDelay: 300
|
||||
readonly property real mediaLyricsPositionTolerance: 0.05
|
||||
readonly property real mediaLyricsWholeSecondSlack: 0.005
|
||||
readonly property int mediaLyricsPositionProbeInterval: 5000
|
||||
readonly property real mediaLyricsPositionProbeDrift: 1.5
|
||||
readonly property real mediaLyricsPositionProbeThreshold: 1
|
||||
|
||||
@@ -25,6 +25,7 @@ Item {
|
||||
readonly property Item focusTarget: grid
|
||||
readonly property bool blocksTabNavigation: editMode || addMenu.open || optionsSheet.shown
|
||||
readonly property bool cardResizing: grid.sizePreview !== null
|
||||
readonly property bool cardInteracting: grid.interacting
|
||||
|
||||
implicitHeight: Math.max(DashMetrics.tabMinHeight, grid.implicitHeight)
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
|
||||
@@ -123,7 +123,7 @@ Item {
|
||||
interval: DashMetrics.mediaPositionPollInterval
|
||||
running: root.live && root.activePlayer?.playbackState === MprisPlaybackState.Playing && !root.isSeeking
|
||||
repeat: true
|
||||
onTriggered: root.activePlayer?.positionChanged()
|
||||
onTriggered: MprisController.refreshPosition(root.activePlayer)
|
||||
}
|
||||
|
||||
function syncAudioRef(wanted) {
|
||||
|
||||
@@ -53,7 +53,7 @@ Card {
|
||||
interval: DashMetrics.mediaPositionPollInterval
|
||||
running: root.live && root.visible && root.interactive && root.showSeekbar && root.playing && !root.isSeeking
|
||||
repeat: true
|
||||
onTriggered: root.activePlayer?.positionSupported && root.activePlayer.positionChanged()
|
||||
onTriggered: MprisController.refreshPosition(root.activePlayer)
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
||||
@@ -26,6 +26,7 @@ FocusScope {
|
||||
readonly property var addable: grid.addableEntries
|
||||
readonly property int usedColumns: grid.usedColumns
|
||||
readonly property bool cardResizing: grid.sizePreview !== null
|
||||
readonly property bool cardInteracting: grid.interacting
|
||||
readonly property bool blocksTabNavigation: grid.blocksTabNavigation || detailSheet.shown || editorSheet.shown || optionsSheet.shown || addMenu.open
|
||||
|
||||
implicitWidth: DashMetrics.contentWidthFor(SettingsData.showWeekNumber)
|
||||
|
||||
@@ -36,6 +36,7 @@ Item {
|
||||
}
|
||||
]
|
||||
readonly property bool blocksTabNavigation: widgets.blocksTabNavigation
|
||||
readonly property bool cardInteracting: widgets.cardInteracting
|
||||
readonly property var forecastOptions: [DashRegistry.choice("forecast", I18n.tr("Forecast", "weather widget option label for forecast display type"), "chart", [
|
||||
{
|
||||
"value": "chart",
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
.pragma library
|
||||
|
||||
function readBoolOverride(envReader, names, fallbackValue) {
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const name = names[i];
|
||||
const raw = envReader(name);
|
||||
if (raw === undefined || raw === null || raw === "")
|
||||
continue;
|
||||
|
||||
const normalized = String(raw).trim().toLowerCase();
|
||||
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on")
|
||||
return true;
|
||||
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off")
|
||||
return false;
|
||||
|
||||
console.warn("Invalid boolean override for", name + ":", raw, "- trying next override/fallback");
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "GreetdEnv.js" as GreetdEnv
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("GreetdMemory")
|
||||
|
||||
readonly property string greetCfgDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
|
||||
readonly property string sessionConfigPath: greetCfgDir + "/session.json"
|
||||
readonly property string memoryFile: greetCfgDir + "/.local/state/memory.json"
|
||||
readonly property bool rememberLastSession: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_SESSION", "DMS_SAVE_SESSION"], true)
|
||||
readonly property bool rememberLastUser: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_USER", "DMS_SAVE_USERNAME"], true)
|
||||
|
||||
property string lastSessionId: ""
|
||||
property string lastSessionDesktopId: ""
|
||||
property string lastSessionExec: ""
|
||||
property string lastSuccessfulUser: ""
|
||||
property bool memoryReady: false
|
||||
property bool isLightMode: false
|
||||
property bool nightModeEnabled: false
|
||||
|
||||
Component.onCompleted: {
|
||||
loadMemory();
|
||||
loadSessionConfig();
|
||||
}
|
||||
|
||||
function loadMemory() {
|
||||
parseMemory(memoryFileView.text());
|
||||
}
|
||||
|
||||
function loadSessionConfig() {
|
||||
parseSessionConfig(sessionConfigFileView.text());
|
||||
}
|
||||
|
||||
function parseSessionConfig(content) {
|
||||
try {
|
||||
if (content && content.trim()) {
|
||||
const config = JSON.parse(content);
|
||||
isLightMode = config.isLightMode !== undefined ? config.isLightMode : false;
|
||||
nightModeEnabled = config.nightModeEnabled !== undefined ? config.nightModeEnabled : false;
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn("Failed to parse greeter session config:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function parseMemory(content) {
|
||||
try {
|
||||
if (!content || !content.trim())
|
||||
return;
|
||||
const memory = JSON.parse(content);
|
||||
lastSessionId = rememberLastSession ? (memory.lastSessionId || "") : "";
|
||||
lastSessionDesktopId = rememberLastSession ? (memory.lastSessionDesktopId || "") : "";
|
||||
lastSessionExec = rememberLastSession ? (memory.lastSessionExec || "") : "";
|
||||
lastSuccessfulUser = rememberLastUser ? (memory.lastSuccessfulUser || "") : "";
|
||||
if (!rememberLastSession || !rememberLastUser)
|
||||
saveMemory();
|
||||
} catch (e) {
|
||||
log.warn("Failed to parse greetd memory:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function saveMemory() {
|
||||
let memory = {};
|
||||
if (rememberLastSession && lastSessionId)
|
||||
memory.lastSessionId = lastSessionId;
|
||||
if (rememberLastSession && lastSessionDesktopId)
|
||||
memory.lastSessionDesktopId = lastSessionDesktopId;
|
||||
if (rememberLastUser && lastSuccessfulUser)
|
||||
memory.lastSuccessfulUser = lastSuccessfulUser;
|
||||
memoryFileView.setText(JSON.stringify(memory, null, 2));
|
||||
}
|
||||
|
||||
function setLastSession(id, desktopId) {
|
||||
if (!rememberLastSession) {
|
||||
if (lastSessionId !== "" || lastSessionDesktopId !== "" || lastSessionExec !== "") {
|
||||
lastSessionId = "";
|
||||
lastSessionDesktopId = "";
|
||||
lastSessionExec = "";
|
||||
saveMemory();
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastSessionId = id || "";
|
||||
lastSessionDesktopId = desktopId || "";
|
||||
lastSessionExec = "";
|
||||
if (!lastSessionId)
|
||||
lastSessionDesktopId = "";
|
||||
saveMemory();
|
||||
}
|
||||
|
||||
function setLastSessionId(id) {
|
||||
setLastSession(id, lastSessionDesktopId);
|
||||
}
|
||||
|
||||
function setLastSessionDesktopId(id) {
|
||||
setLastSession(lastSessionId, id);
|
||||
}
|
||||
|
||||
function setLastSessionExec(exec) {
|
||||
if (!rememberLastSession) {
|
||||
if (lastSessionExec !== "") {
|
||||
lastSessionExec = "";
|
||||
saveMemory();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (lastSessionExec !== "") {
|
||||
lastSessionExec = "";
|
||||
saveMemory();
|
||||
}
|
||||
}
|
||||
|
||||
function setLastSuccessfulUser(username) {
|
||||
if (!rememberLastUser) {
|
||||
if (lastSuccessfulUser !== "") {
|
||||
lastSuccessfulUser = "";
|
||||
saveMemory();
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastSuccessfulUser = username || "";
|
||||
saveMemory();
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: memoryFileView
|
||||
path: root.memoryFile
|
||||
blockLoading: false
|
||||
blockWrites: false
|
||||
atomicWrites: true
|
||||
watchChanges: false
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
parseMemory(memoryFileView.text());
|
||||
root.memoryReady = true;
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.memoryReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: sessionConfigFileView
|
||||
path: root.sessionConfigPath
|
||||
blockLoading: false
|
||||
blockWrites: true
|
||||
atomicWrites: false
|
||||
watchChanges: false
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
parseSessionConfig(sessionConfigFileView.text());
|
||||
}
|
||||
onLoadFailed: {}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import "GreetdEnv.js" as GreetdEnv
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property var _envRememberLastSession: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_SESSION", "DMS_SAVE_SESSION"], undefined)
|
||||
readonly property var _envRememberLastUser: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_USER", "DMS_SAVE_USERNAME"], undefined)
|
||||
|
||||
readonly property bool settingsLoaded: SettingsData._hasLoaded
|
||||
readonly property bool rememberLastSession: _envRememberLastSession !== undefined ? _envRememberLastSession : SettingsData.greeterRememberLastSession
|
||||
readonly property bool rememberLastUser: _envRememberLastUser !== undefined ? _envRememberLastUser : SettingsData.greeterRememberLastUser
|
||||
|
||||
function setConfigBaseDir(dir) {
|
||||
SettingsData.setGreeterSettingsBaseDir(dir);
|
||||
}
|
||||
|
||||
function resetConfigBaseDir() {
|
||||
SettingsData.resetGreeterSettingsBaseDir();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,36 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string passwordBuffer: ""
|
||||
property string username: ""
|
||||
property string usernameInput: ""
|
||||
property bool showPasswordInput: false
|
||||
property string selectedSession: ""
|
||||
property string selectedSessionPath: ""
|
||||
property string selectedSessionDesktopId: ""
|
||||
property string pamState: ""
|
||||
property bool unlocking: false
|
||||
|
||||
property var sessionList: []
|
||||
property var sessionExecs: []
|
||||
property var sessionPaths: []
|
||||
property var sessionDesktopIds: []
|
||||
property int currentSessionIndex: 0
|
||||
property bool sessionManuallySelected: false
|
||||
property var availableUsers: []
|
||||
property int selectedUserIndex: -1
|
||||
|
||||
function reset() {
|
||||
showPasswordInput = false;
|
||||
username = "";
|
||||
usernameInput = "";
|
||||
passwordBuffer = "";
|
||||
pamState = "";
|
||||
selectedUserIndex = -1;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property var modelData
|
||||
|
||||
screen: modelData
|
||||
anchors {
|
||||
left: true
|
||||
right: true
|
||||
top: true
|
||||
bottom: true
|
||||
}
|
||||
exclusionMode: ExclusionMode.Normal
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
color: "transparent"
|
||||
|
||||
GreeterContent {
|
||||
anchors.fill: parent
|
||||
screenName: root.screen?.name ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.DCommon.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property bool expanded: false
|
||||
property int maxExpandedHeight: 400
|
||||
property bool autoLoginVisible: false
|
||||
property bool autoLoginChecked: false
|
||||
property bool manualEntryVisible: false
|
||||
|
||||
signal userSelected(string username)
|
||||
signal toggleRequested
|
||||
signal autoLoginToggled
|
||||
signal manualEntryRequested
|
||||
|
||||
readonly property int rowHeight: 52
|
||||
readonly property int collapsedBarHeight: 36
|
||||
readonly property int actionRowHeight: 44
|
||||
|
||||
readonly property int userListFullHeight: {
|
||||
const count = GreeterUsersService.users.length;
|
||||
if (count === 0)
|
||||
return 0;
|
||||
return count * rowHeight + Math.max(0, count - 1) * Theme.spacingXS;
|
||||
}
|
||||
readonly property int manualEntryBlockHeight: manualEntryVisible ? actionRowHeight + Theme.spacingXS : 0
|
||||
readonly property int autoLoginBlockHeight: autoLoginVisible ? actionRowHeight + Theme.spacingXS : 0
|
||||
readonly property int expandedContentHeight: {
|
||||
if (!expanded)
|
||||
return 0;
|
||||
if (GreeterUsersService.users.length === 0 && !autoLoginVisible && !manualEntryVisible)
|
||||
return 0;
|
||||
return Math.min(maxExpandedHeight, userListFullHeight + manualEntryBlockHeight + autoLoginBlockHeight);
|
||||
}
|
||||
|
||||
function encodeFileUrl(path) {
|
||||
if (!path)
|
||||
return "";
|
||||
return "file://" + path.split("/").map(s => encodeURIComponent(s)).join("/");
|
||||
}
|
||||
|
||||
function profileImageSource(username) {
|
||||
const path = GreeterUsersService.profileImagePath(username);
|
||||
if (path)
|
||||
return encodeFileUrl(path);
|
||||
return "";
|
||||
}
|
||||
|
||||
implicitHeight: expanded ? expandedContentHeight : collapsedBarHeight
|
||||
implicitWidth: parent ? parent.width : 320
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: collapsedBarHeight
|
||||
visible: !expanded
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: GreeterState.username ? GreeterUsersService.optionLabel(GreeterState.username) : I18n.tr("Select user...", "greeter user picker placeholder")
|
||||
color: GreeterState.username ? Theme.surfaceText : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
DIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
name: "expand_more"
|
||||
size: 20
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.toggleRequested()
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
height: root.expandedContentHeight
|
||||
visible: expanded
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DListView {
|
||||
id: userListView
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - root.manualEntryBlockHeight - root.autoLoginBlockHeight
|
||||
clip: true
|
||||
interactive: contentHeight > height
|
||||
spacing: Theme.spacingXS
|
||||
model: GreeterUsersService.users
|
||||
|
||||
delegate: Rectangle {
|
||||
id: userRow
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: userListView.width
|
||||
height: root.rowHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: userRowMouse.containsMouse ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0)
|
||||
border.color: GreeterState.username === userRow.modelData.username ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
||||
border.width: GreeterState.username === userRow.modelData.username ? 1 : 0
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 36
|
||||
Layout.preferredHeight: 36
|
||||
|
||||
DCircularImage {
|
||||
anchors.fill: parent
|
||||
ringWidth: Theme.avatarRingWidth
|
||||
ringColor: Theme.avatarRingColor
|
||||
imageSource: root.profileImageSource(userRow.modelData.username)
|
||||
fallbackIcon: "material:person"
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: GreeterUsersService.optionLabel(userRow.modelData.username)
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: userRowMouse
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.userSelected(userRow.modelData.username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: root.actionRowHeight
|
||||
visible: root.manualEntryVisible
|
||||
radius: Theme.cornerRadius
|
||||
color: manualEntryRowMouse.containsMouse ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0)
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
name: "person_add"
|
||||
size: 20
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("Not listed?", "greeter link to switch to manual username entry")
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: manualEntryRowMouse
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.manualEntryRequested()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: root.actionRowHeight
|
||||
visible: root.autoLoginVisible
|
||||
radius: Theme.cornerRadius
|
||||
color: autoLoginRowMouse.containsMouse ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0)
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
name: root.autoLoginChecked ? "check_box" : "check_box_outline_blank"
|
||||
size: 20
|
||||
color: root.autoLoginChecked ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("Auto-login")
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: autoLoginRowMouse
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.autoLoginToggled()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property var log: Log.scoped("GreeterUserTheme")
|
||||
readonly property string greetCfgDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
|
||||
|
||||
property string activeUsername: ""
|
||||
|
||||
function userCacheDir(username) {
|
||||
if (!username)
|
||||
return "";
|
||||
return greetCfgDir + "/users/" + username;
|
||||
}
|
||||
|
||||
function applyForUser(username) {
|
||||
const name = (username || "").trim();
|
||||
activeUsername = name;
|
||||
if (!name) {
|
||||
applyDefault();
|
||||
return;
|
||||
}
|
||||
const dir = userCacheDir(name);
|
||||
if (typeof GreeterUsersService !== "undefined" && GreeterUsersService.hasSyncedTheme(name)) {
|
||||
Theme.setGreeterColorsBaseDir(dir);
|
||||
SessionData.setGreeterSessionBaseDir(dir);
|
||||
GreetdSettings.setConfigBaseDir(dir);
|
||||
return;
|
||||
}
|
||||
applyDefault();
|
||||
}
|
||||
|
||||
function applyDefault() {
|
||||
activeUsername = "";
|
||||
Theme.resetGreeterColorsBaseDir();
|
||||
SessionData.resetGreeterSessionBaseDir();
|
||||
GreetdSettings.resetConfigBaseDir();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# Embedded Greetd module
|
||||
|
||||
TODO: remove when dank-greeter is available in the Arch extra repos.
|
||||
|
||||
The greeter moved to [dank-greeter](https://github.com/duxlabs-dev/dux-greeter). This embedded copy exists only for archinstall compatibility: the `niri - Dux Shell` archinstall profile writes an `/etc/greetd/config.toml` that launches `Modules/Greetd/assets/dms-greeter` from the packaged DMS tree, and dank-greeter is not installable from the official Arch repos yet.
|
||||
|
||||
Do not extend this module. New greeter work goes to dank-greeter. Installing greetd-dms-greeter-bin from the AUR and running `dms-greeter sync` migrates a system off this embedded copy.
|
||||
@@ -1,506 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
COMPOSITOR=""
|
||||
COMPOSITOR_CONFIG=""
|
||||
DMS_PATH="dms-greeter"
|
||||
CACHE_DIR="/var/cache/dms-greeter"
|
||||
REMEMBER_LAST_SESSION=""
|
||||
REMEMBER_LAST_USER=""
|
||||
DEBUG_MODE=0
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
dms-greeter - Dux Shell greeter launcher
|
||||
|
||||
Usage: dms-greeter --command COMPOSITOR [OPTIONS]
|
||||
|
||||
Required:
|
||||
--command COMPOSITOR Compositor to use (niri, hyprland, sway, scroll, miracle, mango, or labwc)
|
||||
|
||||
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)
|
||||
--remember-last-session BOOL
|
||||
Persist selected session to greeter memory
|
||||
(BOOL: true/false, default: from settings.json)
|
||||
--remember-last-user BOOL
|
||||
Persist last successful username to greeter memory
|
||||
(BOOL: true/false, default: from settings.json)
|
||||
--no-save-session Alias for --remember-last-session false
|
||||
--no-save-username Alias for --remember-last-user false
|
||||
--debug Enable verbose startup logging to stderr
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
dms-greeter --command niri
|
||||
dms-greeter --command hyprland -C /etc/greetd/custom-hypr.lua
|
||||
dms-greeter --command sway -p /home/user/.config/quickshell/custom-dms
|
||||
dms-greeter --command scroll -p /home/user/.config/quickshell/custom-dms
|
||||
dms-greeter --command niri --cache-dir /tmp/dmsgreeter
|
||||
dms-greeter --command niri --no-save-session --no-save-username
|
||||
dms-greeter --command mango
|
||||
dms-greeter --command labwc
|
||||
EOF
|
||||
}
|
||||
|
||||
require_command() {
|
||||
local cmd="$1"
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
echo "Error: required command '$cmd' was not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
normalize_bool_flag() {
|
||||
local flag_name="$1"
|
||||
local value="$2"
|
||||
local normalized="${value,,}"
|
||||
|
||||
case "$normalized" in
|
||||
1|true|yes|on)
|
||||
echo "1"
|
||||
;;
|
||||
0|false|no|off)
|
||||
echo "0"
|
||||
;;
|
||||
*)
|
||||
echo "Error: $flag_name must be true/false (or 1/0, yes/no, on/off)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_hyprland_lua_config() {
|
||||
local config_file="$1"
|
||||
case "$config_file" in
|
||||
*.lua)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
if [[ -f "$config_file" ]] && grep -qE '(^|[^[:alnum:]_])hl\.' "$config_file"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
clear_vt() {
|
||||
local clear_sequence=$'\033[2J\033[H\033[3J\033[?25l'
|
||||
|
||||
# Clear retained console text on the controlling VT.
|
||||
if printf '%s' "$clear_sequence" >/dev/tty 2>/dev/null; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
printf '%s' "$clear_sequence"
|
||||
fi
|
||||
}
|
||||
|
||||
exec_compositor() {
|
||||
local log_tag="$1"
|
||||
shift
|
||||
|
||||
if [[ "$DEBUG_MODE" == "1" ]]; then
|
||||
exec "$@"
|
||||
fi
|
||||
|
||||
clear_vt
|
||||
|
||||
if command -v systemd-cat >/dev/null 2>&1; then
|
||||
exec "$@" > >(systemd-cat -t "dms-greeter/$log_tag" -p info 2>/dev/null) 2>&1
|
||||
fi
|
||||
|
||||
# Preserve diagnostics on systems without journald.
|
||||
exec "$@" >>"$CACHE_DIR/$log_tag.log" 2>&1
|
||||
}
|
||||
|
||||
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
|
||||
;;
|
||||
--remember-last-session)
|
||||
REMEMBER_LAST_SESSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--remember-last-user)
|
||||
REMEMBER_LAST_USER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-save-session)
|
||||
REMEMBER_LAST_SESSION="0"
|
||||
shift
|
||||
;;
|
||||
--no-save-username)
|
||||
REMEMBER_LAST_USER="0"
|
||||
shift
|
||||
;;
|
||||
--debug)
|
||||
DEBUG_MODE=1
|
||||
shift
|
||||
;;
|
||||
-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
|
||||
|
||||
locate_dms_config() {
|
||||
local config_name="$1"
|
||||
local search_paths=()
|
||||
|
||||
local config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
search_paths+=("$config_home/quickshell/$config_name")
|
||||
|
||||
search_paths+=("/usr/share/quickshell/$config_name")
|
||||
|
||||
local config_dirs="${XDG_CONFIG_DIRS:-/etc/xdg}"
|
||||
IFS=':' read -ra dirs <<< "$config_dirs"
|
||||
for dir in "${dirs[@]}"; do
|
||||
if [[ -n "$dir" ]]; then
|
||||
search_paths+=("$dir/quickshell/$config_name")
|
||||
fi
|
||||
done
|
||||
|
||||
for path in "${search_paths[@]}"; do
|
||||
if [[ -f "$path/shell.qml" ]]; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if [[ ! -d "$CACHE_DIR" ]]; then
|
||||
echo "Error: cache directory '$CACHE_DIR' does not exist." >&2
|
||||
echo " Run 'dms greeter sync' to initialize it, or pass --cache-dir to an existing directory." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export DMS_GREET_CFG_DIR="$CACHE_DIR"
|
||||
|
||||
if [[ -n "$REMEMBER_LAST_SESSION" ]]; then
|
||||
DMS_GREET_REMEMBER_LAST_SESSION=$(normalize_bool_flag "--remember-last-session" "$REMEMBER_LAST_SESSION")
|
||||
export DMS_GREET_REMEMBER_LAST_SESSION
|
||||
if [[ "$DMS_GREET_REMEMBER_LAST_SESSION" == "1" ]]; then
|
||||
DMS_SAVE_SESSION=true
|
||||
else
|
||||
DMS_SAVE_SESSION=false
|
||||
fi
|
||||
export DMS_SAVE_SESSION
|
||||
fi
|
||||
|
||||
if [[ -n "$REMEMBER_LAST_USER" ]]; then
|
||||
DMS_GREET_REMEMBER_LAST_USER=$(normalize_bool_flag "--remember-last-user" "$REMEMBER_LAST_USER")
|
||||
export DMS_GREET_REMEMBER_LAST_USER
|
||||
if [[ "$DMS_GREET_REMEMBER_LAST_USER" == "1" ]]; then
|
||||
DMS_SAVE_USERNAME=true
|
||||
else
|
||||
DMS_SAVE_USERNAME=false
|
||||
fi
|
||||
export DMS_SAVE_USERNAME
|
||||
fi
|
||||
|
||||
export HOME="$CACHE_DIR"
|
||||
export XDG_STATE_HOME="$CACHE_DIR/.local/state"
|
||||
export XDG_DATA_HOME="$CACHE_DIR/.local/share"
|
||||
export XDG_CACHE_HOME="$CACHE_DIR/.cache"
|
||||
|
||||
# Fallback runtime dir for systems without logind/pam_rundir (e.g. Void+seatd),
|
||||
# where Wayland compositors abort with "RuntimeDirNotSet". Guarded so logind
|
||||
# systems keep their own.
|
||||
if [[ -z "${XDG_RUNTIME_DIR:-}" || ! -d "${XDG_RUNTIME_DIR:-}" ]]; then
|
||||
export XDG_RUNTIME_DIR="$CACHE_DIR/run"
|
||||
mkdir -p "$XDG_RUNTIME_DIR"
|
||||
chmod 700 "$XDG_RUNTIME_DIR"
|
||||
fi
|
||||
|
||||
|
||||
# Keep greeter VT clean by default; callers can override via env or --debug.
|
||||
if [[ -z "${RUST_LOG:-}" ]]; then
|
||||
export RUST_LOG=warn
|
||||
fi
|
||||
if [[ -z "${NIRI_LOG:-}" ]]; then
|
||||
export NIRI_LOG=warn
|
||||
fi
|
||||
|
||||
# Export the user's configured cursor (from synced settings.json) for the
|
||||
# compositor and greeter shell, but only if that theme is installed system-wide.
|
||||
apply_user_cursor() {
|
||||
command -v jq >/dev/null 2>&1 || return 0
|
||||
local settings="$DMS_GREET_CFG_DIR/settings.json"
|
||||
[[ -f "$settings" ]] || return 0
|
||||
|
||||
local theme size
|
||||
theme=$(jq -r '.cursorSettings.theme // empty' "$settings" 2>/dev/null)
|
||||
size=$(jq -r '.cursorSettings.size // empty' "$settings" 2>/dev/null)
|
||||
[[ -n "$theme" && "$theme" != "System Default" ]] || return 0
|
||||
|
||||
local dirs=()
|
||||
IFS=':' read -ra data_dirs <<< "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
|
||||
local d
|
||||
for d in "${data_dirs[@]}"; do
|
||||
[[ -n "$d" ]] && dirs+=("$d/icons")
|
||||
done
|
||||
dirs+=(/run/current-system/sw/share/icons /usr/share/icons /usr/local/share/icons)
|
||||
|
||||
for d in "${dirs[@]}"; do
|
||||
if [[ -d "$d/$theme/cursors" ]]; then
|
||||
export XCURSOR_PATH="$d${XCURSOR_PATH:+:$XCURSOR_PATH}"
|
||||
export XCURSOR_THEME="$theme"
|
||||
[[ -n "$size" ]] && export XCURSOR_SIZE="$size"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
apply_user_cursor
|
||||
|
||||
if command -v qs >/dev/null 2>&1; then
|
||||
QS_BIN="qs"
|
||||
elif command -v quickshell >/dev/null 2>&1; then
|
||||
QS_BIN="quickshell"
|
||||
else
|
||||
echo "Error: neither 'qs' nor 'quickshell' was found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
QS_CMD="$QS_BIN"
|
||||
if [[ "$DMS_PATH" == /* ]]; then
|
||||
QS_CMD="$QS_BIN -p $DMS_PATH"
|
||||
else
|
||||
RESOLVED_PATH=$(locate_dms_config "$DMS_PATH")
|
||||
if [[ $? -eq 0 && -n "$RESOLVED_PATH" ]]; then
|
||||
if [[ "$DEBUG_MODE" == "1" ]]; then
|
||||
echo "Located DMS config at: $RESOLVED_PATH" >&2
|
||||
fi
|
||||
QS_CMD="$QS_BIN -p $RESOLVED_PATH"
|
||||
else
|
||||
echo "Error: Could not find DMS config '$DMS_PATH' (shell.qml) in any valid config path" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$COMPOSITOR" in
|
||||
niri)
|
||||
require_command "niri"
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
# base/default config
|
||||
cat > "$TEMP_CONFIG" << NIRI_EOF
|
||||
hotkey-overlay {
|
||||
skip-at-startup
|
||||
}
|
||||
|
||||
environment {
|
||||
DMS_RUN_GREETER "1"
|
||||
}
|
||||
|
||||
gestures {
|
||||
hot-corners {
|
||||
off
|
||||
}
|
||||
}
|
||||
|
||||
layout {
|
||||
background-color "#000000"
|
||||
}
|
||||
NIRI_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
fi
|
||||
# Append includes if override_files exist
|
||||
OVERRIDE_FILES=(
|
||||
/usr/share/greetd/niri_overrides.kdl # for building into a system image
|
||||
/etc/greetd/niri_overrides.kdl # for local overrides
|
||||
)
|
||||
for override_file in "${OVERRIDE_FILES[@]}"; do
|
||||
if [[ -e "$override_file" ]]; then
|
||||
# TODO: maybe move to optional=true when Niri Next drops, so we can avoid these checks ;-)
|
||||
cat >> "$TEMP_CONFIG" << NIRI_EOF
|
||||
|
||||
include "$override_file"
|
||||
NIRI_EOF
|
||||
fi
|
||||
done
|
||||
# Append spawn command
|
||||
cat >> "$TEMP_CONFIG" << NIRI_EOF
|
||||
|
||||
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
|
||||
NIRI_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
exec_compositor "niri" niri -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
hyprland)
|
||||
if ! command -v start-hyprland >/dev/null 2>&1 && ! command -v Hyprland >/dev/null 2>&1; then
|
||||
echo "Error: neither 'start-hyprland' nor 'Hyprland' was found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$COMPOSITOR_CONFIG" ]] && is_hyprland_lua_config "$COMPOSITOR_CONFIG"; then
|
||||
TEMP_CONFIG=$(mktemp --suffix=.lua)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << HYPRLAND_LUA_EOF
|
||||
|
||||
hl.on("hyprland.start", function()
|
||||
hl.exec_cmd("sh -c \"$QS_CMD; hyprctl dispatch 'hl.dsp.exit()'\"")
|
||||
end)
|
||||
HYPRLAND_LUA_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
elif [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp --suffix=.lua)
|
||||
cat > "$TEMP_CONFIG" << HYPRLAND_LUA_EOF
|
||||
hl.env("DMS_RUN_GREETER", "1")
|
||||
|
||||
hl.config({
|
||||
misc = {
|
||||
disable_hyprland_logo = true,
|
||||
},
|
||||
})
|
||||
|
||||
hl.on("hyprland.start", function()
|
||||
hl.exec_cmd("sh -c \"$QS_CMD; hyprctl dispatch 'hl.dsp.exit()'\"")
|
||||
end)
|
||||
HYPRLAND_LUA_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
# hyprlang stays usable until 0.57 ships; legacy dispatch is the only form a .conf session accepts
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||
|
||||
exec-once = sh -c "$QS_CMD; hyprctl dispatch exit"
|
||||
HYPRLAND_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
if command -v start-hyprland >/dev/null 2>&1; then
|
||||
exec_compositor "hyprland" start-hyprland -- --config "$COMPOSITOR_CONFIG"
|
||||
else
|
||||
exec_compositor "hyprland" Hyprland -c "$COMPOSITOR_CONFIG"
|
||||
fi
|
||||
;;
|
||||
|
||||
sway)
|
||||
require_command "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_compositor "sway" sway --unsupported-gpu -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
scroll)
|
||||
require_command "scroll"
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << SCROLL_EOF
|
||||
exec "$QS_CMD; scrollmsg exit"
|
||||
SCROLL_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << SCROLL_EOF
|
||||
|
||||
exec "$QS_CMD; scrollmsg exit"
|
||||
SCROLL_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec_compositor "scroll" scroll -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
miracle|miracle-wm)
|
||||
require_command "miracle-wm"
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << MIRACLE_EOF
|
||||
exec "$QS_CMD; miraclemsg exit"
|
||||
MIRACLE_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << MIRACLE_EOF
|
||||
|
||||
exec "$QS_CMD; miraclemsg exit"
|
||||
MIRACLE_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec_compositor "miracle" miracle-wm -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
labwc)
|
||||
require_command "labwc"
|
||||
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
|
||||
exec_compositor "labwc" labwc --config "$COMPOSITOR_CONFIG" --session "$QS_CMD"
|
||||
else
|
||||
exec_compositor "labwc" labwc --session "$QS_CMD"
|
||||
fi
|
||||
;;
|
||||
|
||||
mango|mangowc)
|
||||
require_command "mango"
|
||||
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
|
||||
exec_compositor "mango" mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg dispatch quit"
|
||||
else
|
||||
# Without -c mango falls back to /etc/mango/config.conf, whose spawn/quit binds work at the login screen.
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
exec_compositor "mango" mango -c "$TEMP_CONFIG" -s "$QS_CMD && mmsg dispatch quit"
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Error: Unsupported compositor: $COMPOSITOR" >&2
|
||||
echo "Supported compositors: niri, hyprland, sway, scroll, miracle, mango, labwc" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,8 +0,0 @@
|
||||
-- Minimal Hyprland (Lua) session for greetd — replace _DMS_PATH_ with your DMS checkout.
|
||||
-- Copy to `/etc/greetd/dms-hypr.lua` alongside `greet-hyprland.sh`.
|
||||
|
||||
hl.env("DMS_RUN_GREETER", "1")
|
||||
|
||||
hl.on("hyprland.start", function()
|
||||
hl.exec_cmd("sh -c \"qs -p _DMS_PATH_; hyprctl dispatch 'hl.dsp.exit()'\"")
|
||||
end)
|
||||
@@ -1,19 +0,0 @@
|
||||
hotkey-overlay {
|
||||
skip-at-startup
|
||||
}
|
||||
|
||||
environment {
|
||||
DMS_RUN_GREETER "1"
|
||||
}
|
||||
|
||||
spawn-at-startup "sh" "-c" "qs -p _DMS_PATH_; niri msg action quit --skip-confirmation"
|
||||
|
||||
gestures {
|
||||
hot-corners {
|
||||
off
|
||||
}
|
||||
}
|
||||
|
||||
layout {
|
||||
background-color "#000000"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
export XDG_SESSION_TYPE=wayland
|
||||
export QT_QPA_PLATFORM=wayland
|
||||
export EGL_PLATFORM=gbm
|
||||
if command -v start-hyprland >/dev/null 2>&1; then
|
||||
exec start-hyprland -- -c /etc/greetd/dms-hypr.lua
|
||||
else
|
||||
exec Hyprland -c /etc/greetd/dms-hypr.lua
|
||||
fi
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
export XDG_SESSION_TYPE=wayland
|
||||
export QT_QPA_PLATFORM=wayland
|
||||
export EGL_PLATFORM=gbm
|
||||
|
||||
exec niri -c /etc/greetd/dms-niri.kdl
|
||||
@@ -55,7 +55,7 @@ Rectangle {
|
||||
|
||||
function saveDisplayImage() {
|
||||
const data = root.notificationData;
|
||||
if (!root.persistImage || !data?.hasDisplayImage || data.persistedImagePath)
|
||||
if (!root.persistImage || !data?.notification || !data.hasDisplayImage || data.persistedImagePath)
|
||||
return;
|
||||
const image = data.image || "";
|
||||
if (!image.startsWith("image://qsimage/") && !NotificationService.notificationIconFromImage(image).startsWith("/"))
|
||||
|
||||
@@ -61,6 +61,7 @@ PanelWindow {
|
||||
readonly property bool cardShown: presenting && (presentationProgress > 0 || !exiting)
|
||||
readonly property bool layoutPinned: !exiting && (hovered || contextMenuActive || swipeActive)
|
||||
property bool exiting: false
|
||||
property var exitSnapshot: null
|
||||
property bool exitStarted: false
|
||||
property bool collapseChromeOnExit: false
|
||||
property bool _isDestroying: false
|
||||
@@ -124,6 +125,7 @@ PanelWindow {
|
||||
}
|
||||
enterAnimation.stop();
|
||||
enterDelay.stop();
|
||||
exitSnapshot = snapshotNotification(notificationData);
|
||||
exiting = true;
|
||||
if (NotificationService.removeFromVisibleNotifications)
|
||||
NotificationService.removeFromVisibleNotifications(win.notificationData);
|
||||
@@ -172,6 +174,29 @@ PanelWindow {
|
||||
win.exitFinished();
|
||||
}
|
||||
|
||||
// The wrapper is destroyed as soon as its notification closes, which would blank the card mid-exit.
|
||||
function snapshotNotification(data) {
|
||||
if (!data)
|
||||
return null;
|
||||
return {
|
||||
"appName": data.appName,
|
||||
"appIcon": data.appIcon,
|
||||
"desktopEntry": data.desktopEntry,
|
||||
"summary": data.summary,
|
||||
"body": data.body,
|
||||
"htmlBody": data.htmlBody,
|
||||
"image": data.image,
|
||||
"hasDisplayImage": data.hasDisplayImage,
|
||||
"displayImage": data.displayImage,
|
||||
"persistedImagePath": data.persistedImagePath,
|
||||
"timeStr": data.timeStr,
|
||||
"urgency": data.urgency,
|
||||
"actions": Array.from(data.actions || [], action => ({
|
||||
"text": action.text
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
function closeTransientUi() {
|
||||
transientSurfaces.closeAll();
|
||||
popupContextMenuLoader.active = false;
|
||||
@@ -719,7 +744,7 @@ PanelWindow {
|
||||
chipColor: Theme.notificationChipSurface
|
||||
width: parent.width
|
||||
height: win.inlineHeightAnimating ? Math.min(targetHeight, cardSurface.height - win.timeoutRailClearance) : targetHeight
|
||||
notificationData: win.notificationData
|
||||
notificationData: win.exitSnapshot ?? win.notificationData
|
||||
descriptionExpanded: win.descriptionExpanded
|
||||
privacyMode: SettingsData.notificationPopupPrivacyMode
|
||||
bodyInvokesAction: win.bodyClickInvokesAction
|
||||
|
||||
@@ -557,6 +557,13 @@ QtObject {
|
||||
}
|
||||
|
||||
Component.onCompleted: _sync(NotificationService.visibleNotifications)
|
||||
Component.onDestruction: {
|
||||
for (const p of popupWindows)
|
||||
p?.destroy();
|
||||
const screenName = manager.modelData?.name || "";
|
||||
if (screenName)
|
||||
ConnectedModeState.releaseSurface(screenName, "notification", "notification:" + screenName);
|
||||
}
|
||||
onPopupSpacingChanged: _repositionAll()
|
||||
onNotificationConnectedModeChanged: _scheduleNotificationChromeSync()
|
||||
onCloseGapNotificationsChanged: _scheduleNotificationChromeSync()
|
||||
|
||||
@@ -12,6 +12,7 @@ Column {
|
||||
property var parentModal: null
|
||||
property string editingDockId: ""
|
||||
property string renameDraft: ""
|
||||
readonly property var dockPages: ["dock_general", "dock_widgets", "dock_appearance"].map(id => SettingsTabs.page(id)).filter(page => page)
|
||||
|
||||
function finishRename(value) {
|
||||
const id = editingDockId;
|
||||
@@ -44,11 +45,10 @@ Column {
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "dock_to_bottom"
|
||||
title: I18n.tr("Docks", "noun plural, settings card title listing configured docks")
|
||||
settingKey: "dockConfiguration"
|
||||
tags: ["dock", "configuration", "add", "remove", "name", "show", "enable"]
|
||||
tags: ["dock", "configuration", "add", "remove", "show", "enable"]
|
||||
headerActions: DButton {
|
||||
text: I18n.tr("Add", "verb, button that adds a new item to a list")
|
||||
iconName: "add"
|
||||
@@ -75,11 +75,18 @@ Column {
|
||||
onDeleteRequested: root.removeDock(modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
iconName: "dock_to_bottom"
|
||||
title: dock.config?.name ?? ""
|
||||
settingKey: "dockPlacement"
|
||||
tags: ["dock", "layout", "placement", "position", "edge", "name", "rename"]
|
||||
visible: dock.hasConfig
|
||||
|
||||
SettingsRow {
|
||||
title: I18n.tr("Name")
|
||||
subtitle: root.editingDockId ? "" : dock.config?.name ?? ""
|
||||
visible: dock.hasConfig
|
||||
|
||||
DActionButton {
|
||||
iconName: root.editingDockId ? "check" : "edit"
|
||||
@@ -120,25 +127,33 @@ Column {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
visible: dock.hasConfig
|
||||
iconName: "dock_to_bottom"
|
||||
title: I18n.tr("Layout")
|
||||
settingKey: "dockPlacement"
|
||||
tags: ["dock", "layout", "placement", "position", "edge"]
|
||||
SettingsRow {
|
||||
title: I18n.tr("Layout", "noun, settings section title for arrangement options")
|
||||
|
||||
SettingsLayoutPicker {
|
||||
edgePlacement: true
|
||||
choices: [SettingsData.Position.Top, SettingsData.Position.Bottom, SettingsData.Position.Left, SettingsData.Position.Right].map(position => ({
|
||||
key: String(position),
|
||||
label: dock.positionLabel(position),
|
||||
enabled: dock.positionChoices.includes(position)
|
||||
}))
|
||||
selectedKey: String(dock.config?.position ?? SettingsData.Position.Bottom)
|
||||
onSelected: key => dock.setEnabled(true, Number(key))
|
||||
body: SettingsLayoutPicker {
|
||||
edgePlacement: true
|
||||
choices: [SettingsData.Position.Top, SettingsData.Position.Bottom, SettingsData.Position.Left, SettingsData.Position.Right].map(position => ({
|
||||
key: String(position),
|
||||
label: dock.positionLabel(position),
|
||||
enabled: dock.positionChoices.includes(position)
|
||||
}))
|
||||
selectedKey: String(dock.config?.position ?? SettingsData.Position.Bottom)
|
||||
onSelected: key => dock.setEnabled(true, Number(key))
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.dockPages
|
||||
|
||||
delegate: SettingsNavRow {
|
||||
required property var modelData
|
||||
|
||||
iconName: modelData.icon
|
||||
title: modelData.text
|
||||
hint: modelData.hint ?? ""
|
||||
onClicked: keyboard => root.parentModal?.navigateTo(modelData.id, keyboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +62,11 @@ Item {
|
||||
property string greeterStatusStderr: ""
|
||||
readonly property bool greeterBinaryExists: GreeterService.binaryExists
|
||||
readonly property bool greeterEnabled: GreeterService.enabled
|
||||
property bool embeddedGreeterConfigured: false
|
||||
readonly property bool embeddedGreeterOnly: embeddedGreeterConfigured && !greeterBinaryExists
|
||||
readonly property bool busy: greeterSyncRunning || greeterInstallActionRunning
|
||||
|
||||
readonly property string greeterState: {
|
||||
if (!greeterBinaryExists)
|
||||
return embeddedGreeterOnly ? "embedded" : "missing";
|
||||
return "missing";
|
||||
if (!greeterEnabled)
|
||||
return "inactive";
|
||||
switch (GreeterService.slotState) {
|
||||
@@ -103,8 +101,6 @@ Item {
|
||||
return I18n.tr("Not active", "greeter status, greetd does not run the DMS greeter");
|
||||
case "unlinked":
|
||||
return I18n.tr("Not linked", "greeter status, no greeter slot for this user");
|
||||
case "embedded":
|
||||
return I18n.tr("Bundled greeter active", "greeter status");
|
||||
}
|
||||
return I18n.tr("Not installed", "greeter status");
|
||||
}
|
||||
@@ -118,8 +114,6 @@ Item {
|
||||
return I18n.tr("greetd is not using the DMS greeter yet.", "greeter status");
|
||||
case "unlinked":
|
||||
return GreeterService.profileSyncSufficient ? I18n.tr("Link your DMS settings so the login screen follows them.", "greeter status") : I18n.tr("Link your DMS settings so the login screen follows them. Administrator access is required.", "greeter status");
|
||||
case "embedded":
|
||||
return I18n.tr("The greeter bundled with DMS is active (archinstall setup). It keeps working as is, but syncing theme and settings needs the standalone greeter. Install greetd-dms-greeter-bin from the AUR, then run Sync to migrate the login screen.", "embedded greeter status");
|
||||
}
|
||||
return I18n.tr("dms-greeter is not installed. Install the dms-greeter package to manage the greeter.", "greeter status placeholder");
|
||||
}
|
||||
@@ -149,7 +143,6 @@ Item {
|
||||
|
||||
function checkGreeterInstallState() {
|
||||
GreeterService.refresh();
|
||||
embeddedGreeterCheckProcess.running = true;
|
||||
}
|
||||
|
||||
function runGreeterStatus() {
|
||||
@@ -205,17 +198,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: embeddedGreeterCheckProcess
|
||||
// archinstall's DMS profile points greetd at this launcher inside the packaged DMS tree
|
||||
command: ["sh", "-c", "grep -q 'Modules/Greetd/assets/dms-greeter' /etc/greetd/config.toml 2>/dev/null"]
|
||||
running: false
|
||||
|
||||
onExited: exitCode => {
|
||||
root.embeddedGreeterConfigured = (exitCode === 0);
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: greeterStatusProcess
|
||||
command: ["dms-greeter", "status"]
|
||||
|
||||
@@ -1577,7 +1577,9 @@ RegistryBrowserWindow {
|
||||
visible: (detailPane.plugin.permissions || []).length > 0
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("This plugin can", "plugin detail section, introduces the permission list")
|
||||
width: parent.width
|
||||
text: I18n.tr("Declared by the author, not enforced", "plugin detail section, introduces the permission list the plugin declares about itself")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Theme.fontWeightMedium
|
||||
color: Theme.surfaceVariantText
|
||||
|
||||
@@ -399,12 +399,16 @@ Column {
|
||||
|
||||
readonly property var fillModes: ["Stretch", "Fit", "Fill", "Scrolling", "Tile", "TileVertically", "TileHorizontally", "Pad"]
|
||||
readonly property var fillModeLabels: [I18n.tr("Stretch", "wallpaper fill mode"), I18n.tr("Fit", "wallpaper fill mode"), I18n.tr("Fill", "wallpaper fill mode"), I18n.tr("Scroll", "wallpaper fill mode"), I18n.tr("Tile", "wallpaper fill mode"), I18n.tr("Tile Vertically", "wallpaper fill mode"), I18n.tr("Tile Horizontally", "wallpaper fill mode"), I18n.tr("Pad", "wallpaper fill mode")]
|
||||
// dms picks the animation driver at launch from this setting
|
||||
readonly property bool simpleAnimationDriver: Quickshell.env("QSG_USE_SIMPLE_ANIMATION_DRIVER") === "1"
|
||||
readonly property bool driverMismatch: (SettingsData.wallpaperFillMode === "Scrolling") !== simpleAnimationDriver
|
||||
|
||||
tab: "wallpaper"
|
||||
tags: ["background", "fill", "fit", "stretch", "tile", "scale"]
|
||||
settingKey: "wallpaperFillMode"
|
||||
resetKeys: root.perMonitor ? [] : ["wallpaperFillMode"]
|
||||
text: I18n.tr("Fill mode")
|
||||
description: driverMismatch ? I18n.tr("Requires restart") : ""
|
||||
visible: root.wallpaperIsImage
|
||||
dropdownWidth: 190
|
||||
options: fillModeLabels
|
||||
|
||||
@@ -17,10 +17,7 @@ SettingsDropdownRow {
|
||||
function _enumerate() {
|
||||
if (_enumerated)
|
||||
return;
|
||||
const bundled = DCommon.Fonts.bundledFamilies.filter(f => f !== root.defaultFamily);
|
||||
const system = Qt.fontFamilies().filter(f => !f.startsWith(".") && f !== root.defaultFamily && !bundled.includes(f));
|
||||
system.sort();
|
||||
_families = ["Default"].concat(bundled, system);
|
||||
_families = ["Default"].concat(DCommon.Fonts.selectableFamilies(root.defaultFamily));
|
||||
_enumerated = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Singleton {
|
||||
readonly property string markerPath: currentVersion !== "" ? Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/dms/.changelog-" + currentVersion : ""
|
||||
property bool markerChecked: false
|
||||
property bool markerSeen: false
|
||||
readonly property bool ready: !SessionData.isGreeterMode && FirstLaunchService.checkComplete && markerChecked
|
||||
readonly property bool ready: FirstLaunchService.checkComplete && markerChecked
|
||||
|
||||
property bool changelogDismissed: false
|
||||
property var release: null
|
||||
|
||||
@@ -468,7 +468,7 @@ Singleton {
|
||||
const yKey = y.niriWindowId !== undefined ? y.niriWindowId : y.mangoWindowId;
|
||||
if (xKey === undefined || yKey === undefined)
|
||||
return false;
|
||||
if (xKey !== yKey || x.niriWorkspaceId !== y.niriWorkspaceId || x.appId !== y.appId || x.title !== y.title || !!x.activated !== !!y.activated || !!x.fullscreen !== !!y.fullscreen || !!x.maximized !== !!y.maximized || !!x.minimized !== !!y.minimized)
|
||||
if (xKey !== yKey || x.niriWorkspaceId !== y.niriWorkspaceId || x.mangoMonitor !== y.mangoMonitor || x.appId !== y.appId || x.title !== y.title || !!x.activated !== !!y.activated || !!x.fullscreen !== !!y.fullscreen || !!x.maximized !== !!y.maximized || !!x.minimized !== !!y.minimized)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property var log: Log.scoped("GreeterUsersService")
|
||||
|
||||
readonly property string greetCfgDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
|
||||
readonly property string usersCacheDir: greetCfgDir + "/users"
|
||||
|
||||
property var users: []
|
||||
property var usernames: []
|
||||
property var profileImageMap: ({})
|
||||
property bool loaded: false
|
||||
property bool refreshing: false
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!SessionData.isGreeterMode)
|
||||
return;
|
||||
refresh();
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
if (refreshing)
|
||||
return;
|
||||
refreshing = true;
|
||||
_loadUsers();
|
||||
}
|
||||
|
||||
function displayName(username) {
|
||||
const u = _findUser(username);
|
||||
if (!u)
|
||||
return username || "";
|
||||
const gecos = (u.gecos || "").trim();
|
||||
return gecos.length > 0 ? gecos : username;
|
||||
}
|
||||
|
||||
function optionLabel(username) {
|
||||
const label = displayName(username);
|
||||
return label !== username ? label : username;
|
||||
}
|
||||
|
||||
function usernameFromOptionLabel(label) {
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
if (root.optionLabel(users[i].username) === label)
|
||||
return users[i].username;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
function hasSyncedTheme(username) {
|
||||
if (!username)
|
||||
return false;
|
||||
return syncedThemePaths[username] === true;
|
||||
}
|
||||
|
||||
property var syncedThemePaths: ({})
|
||||
|
||||
function userCacheDir(username) {
|
||||
if (!username)
|
||||
return "";
|
||||
return usersCacheDir + "/" + username;
|
||||
}
|
||||
|
||||
function syncedSettingsPath(username) {
|
||||
const dir = userCacheDir(username);
|
||||
return dir ? dir + "/settings.json" : "";
|
||||
}
|
||||
|
||||
function _findUser(name) {
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
if (users[i].username === name)
|
||||
return users[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function _loadUsers() {
|
||||
Proc.runCommand("greeterUsersService-loadUsers", ["sh", "-c", "getent passwd | awk -F: '$3>=1000 && $3<60000 && $1!=\"nobody\" && $7!~/(nologin|false)$/ && $6!=\"/var/empty\" {print $1\":\"$3\":\"$5\":\"$6\":\"$7}'"], (output, exitCode) => {
|
||||
const lines = (output || "").trim().split("\n").filter(l => l.length > 0);
|
||||
const list = [];
|
||||
const names = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const parts = lines[i].split(":");
|
||||
if (parts.length < 5)
|
||||
continue;
|
||||
const username = parts[0];
|
||||
list.push({
|
||||
username,
|
||||
uid: parseInt(parts[1], 10),
|
||||
gecos: (parts[2] || "").split(",")[0],
|
||||
home: parts[3] || "",
|
||||
shell: parts[4] || ""
|
||||
});
|
||||
names.push(username);
|
||||
}
|
||||
list.sort((a, b) => a.username.localeCompare(b.username));
|
||||
names.sort((a, b) => a.localeCompare(b));
|
||||
root.users = list;
|
||||
root.usernames = names;
|
||||
root.loaded = true;
|
||||
root.refreshing = false;
|
||||
_refreshSyncedThemeFlags();
|
||||
_loadProfileIcons();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function _refreshSyncedThemeFlags() {
|
||||
if (usernames.length === 0) {
|
||||
syncedThemePaths = ({});
|
||||
return;
|
||||
}
|
||||
const checks = usernames.map(u => `[ -f "${syncedSettingsPath(u)}" ] && echo "${u}:1" || echo "${u}:0"`).join("; ");
|
||||
Proc.runCommand("greeterUsersService-syncedThemes", ["sh", "-c", checks], (output, exitCode) => {
|
||||
const map = {};
|
||||
const lines = (output || "").trim().split("\n").filter(l => l.length > 0);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const parts = lines[i].split(":");
|
||||
if (parts.length >= 2)
|
||||
map[parts[0]] = parts[1] === "1";
|
||||
}
|
||||
root.syncedThemePaths = map;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function profileImagePath(username) {
|
||||
if (!username)
|
||||
return "";
|
||||
return profileImageMap[username] || "";
|
||||
}
|
||||
|
||||
function _loadProfileIcons() {
|
||||
if (users.length === 0) {
|
||||
profileImageMap = ({});
|
||||
return;
|
||||
}
|
||||
const script = users.map(u => {
|
||||
const safeUser = u.username.replace(/'/g, "'\\''");
|
||||
const safeHome = (u.home || "").replace(/'/g, "'\\''");
|
||||
const cacheDir = usersCacheDir + "/" + u.username;
|
||||
return `( icon=""; for f in "${cacheDir}/profile.jpg" "${cacheDir}/profile.jpeg" "${cacheDir}/profile.png" "${cacheDir}/profile.webp" "/var/lib/AccountsService/icons/${safeUser}" "${safeHome}/.face" "${safeHome}/.face.icon"; do if [ -f "$f" ] && [ -r "$f" ]; then icon="$f"; break; fi; done; echo "${u.username}:$icon" )`;
|
||||
}).join("; ");
|
||||
Proc.runCommand("greeterUsersService-profileIcons", ["sh", "-c", script], (output, exitCode) => {
|
||||
const map = {};
|
||||
const lines = (output || "").trim().split("\n").filter(l => l.length > 0);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const idx = lines[i].indexOf(":");
|
||||
if (idx <= 0)
|
||||
continue;
|
||||
const user = lines[i].substring(0, idx);
|
||||
const icon = lines[i].substring(idx + 1).trim();
|
||||
map[user] = icon && icon.length > 0 ? icon : "";
|
||||
}
|
||||
for (let j = 0; j < users.length; j++) {
|
||||
const u = users[j].username;
|
||||
if (!(u in map))
|
||||
map[u] = "";
|
||||
}
|
||||
root.profileImageMap = map;
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,11 @@ QtObject {
|
||||
if (enabled && (state === "none" || state === "error"))
|
||||
useEmbeddedText(state);
|
||||
}
|
||||
onPositionChanged: scheduleAnchor()
|
||||
onPositionChanged: {
|
||||
if (MprisController.refreshingPosition || flooredEcho())
|
||||
return;
|
||||
scheduleAnchor();
|
||||
}
|
||||
onRateChanged: updatePlaybackClock()
|
||||
onPlayingChanged: {
|
||||
probe = null;
|
||||
@@ -379,6 +383,7 @@ QtObject {
|
||||
if (!line || !Number.isFinite(line.t))
|
||||
return false;
|
||||
player.position = Math.max(0.1, Math.min(line.t, duration * 0.99));
|
||||
reanchor();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -434,6 +439,13 @@ QtObject {
|
||||
resync();
|
||||
}
|
||||
|
||||
// pear-desktop floors Position to whole seconds on pause, resume and track events.
|
||||
function flooredEcho() {
|
||||
const lead = currentTime() - position;
|
||||
const whole = Math.abs(position - Math.round(position)) < DashMetrics.mediaLyricsWholeSecondSlack;
|
||||
return whole && lead >= 0 && lead < 1 + DashMetrics.mediaLyricsPositionTolerance;
|
||||
}
|
||||
|
||||
function scheduleAnchor() {
|
||||
positionWall = Date.now();
|
||||
if (enabled && synced && !stopped && !anchorUpdate.running)
|
||||
|
||||
@@ -34,6 +34,15 @@ Singleton {
|
||||
property bool _mprisPublishDirty: false
|
||||
property int _mprisConnectionEpoch: 0
|
||||
property int positionConsumers: 0
|
||||
property bool refreshingPosition: false
|
||||
|
||||
function refreshPosition(player) {
|
||||
if (!player?.positionSupported)
|
||||
return;
|
||||
refreshingPosition = true;
|
||||
player.positionChanged();
|
||||
refreshingPosition = false;
|
||||
}
|
||||
|
||||
function addPositionRef() {
|
||||
positionConsumers++;
|
||||
@@ -398,7 +407,7 @@ Singleton {
|
||||
interval: 1000
|
||||
running: root.positionConsumers > 0 && root.activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
repeat: true
|
||||
onTriggered: root.activePlayer?.positionChanged()
|
||||
onTriggered: root.refreshPosition(root.activePlayer)
|
||||
}
|
||||
|
||||
function _hasMPRISSubscription(): bool {
|
||||
|
||||
@@ -334,9 +334,18 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
if (sameScales(displayScales, scales))
|
||||
return;
|
||||
displayScales = scales;
|
||||
}
|
||||
|
||||
function sameScales(a, b) {
|
||||
const names = Object.keys(a);
|
||||
if (names.length !== Object.keys(b).length)
|
||||
return false;
|
||||
return names.every(name => a[name] === b[name]);
|
||||
}
|
||||
|
||||
function sortWindowsByLayout(windowList) {
|
||||
const enriched = windowList.map(w => {
|
||||
const ws = workspaces[w.workspace_id];
|
||||
|
||||
@@ -308,8 +308,21 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function _stripDotSlash(p) {
|
||||
return p.startsWith("./") ? p.slice(2) : p;
|
||||
function _pluginFilePath(dir, rel) {
|
||||
if (!rel)
|
||||
return "";
|
||||
const path = String(rel);
|
||||
let decoded;
|
||||
try {
|
||||
decoded = decodeURIComponent(path);
|
||||
} catch (e) {
|
||||
decoded = "..";
|
||||
}
|
||||
if (decoded.split("/").includes("..")) {
|
||||
log.warn("plugin path outside its directory, refusing:", path, "in", dir);
|
||||
return "";
|
||||
}
|
||||
return dir + "/" + (path.startsWith("./") ? path.slice(2) : path);
|
||||
}
|
||||
|
||||
function _deriveLegacySurface(type, capabilities) {
|
||||
@@ -334,17 +347,16 @@ Singleton {
|
||||
log.warn("unknown plugin surface", surface, "in", dir);
|
||||
continue;
|
||||
}
|
||||
const rel = manifest.components[surface];
|
||||
if (!rel)
|
||||
const path = _pluginFilePath(dir, manifest.components[surface]);
|
||||
if (!path)
|
||||
continue;
|
||||
paths[surface] = dir + "/" + _stripDotSlash(rel);
|
||||
paths[surface] = path;
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
if (manifest.component) {
|
||||
const surface = _deriveLegacySurface(manifest.type, manifest.capabilities);
|
||||
paths[surface] = dir + "/" + _stripDotSlash(manifest.component);
|
||||
}
|
||||
const legacyPath = _pluginFilePath(dir, manifest.component);
|
||||
if (legacyPath)
|
||||
paths[_deriveLegacySurface(manifest.type, manifest.capabilities)] = legacyPath;
|
||||
return paths;
|
||||
}
|
||||
|
||||
@@ -360,12 +372,8 @@ Singleton {
|
||||
}
|
||||
|
||||
const dir = absPath.substring(0, absPath.lastIndexOf('/'));
|
||||
let settings = manifest.settings;
|
||||
if (settings && settings.startsWith("./"))
|
||||
settings = settings.slice(2);
|
||||
let startupCheck = manifest.startupCheck;
|
||||
if (startupCheck && startupCheck.startsWith("./"))
|
||||
startupCheck = startupCheck.slice(2);
|
||||
const settingsPath = _pluginFilePath(dir, manifest.settings);
|
||||
const startupCheckPath = _pluginFilePath(dir, manifest.startupCheck);
|
||||
|
||||
const componentPaths = _resolveComponentPaths(manifest, dir);
|
||||
const surfaces = Object.keys(componentPaths);
|
||||
@@ -397,8 +405,8 @@ Singleton {
|
||||
info.componentPaths = componentPaths;
|
||||
info.surfaces = surfaces;
|
||||
info.componentPath = componentPaths.widget || componentPaths[surfaces[0]];
|
||||
info.settingsPath = settings ? (dir + "/" + settings) : null;
|
||||
info.startupCheckPath = startupCheck ? (dir + "/" + startupCheck) : null;
|
||||
info.settingsPath = settingsPath || null;
|
||||
info.startupCheckPath = startupCheckPath || null;
|
||||
info.loaded = isPluginLoaded(manifest.id);
|
||||
info.type = manifest.type || (manifest.components ? "composite" : "widget");
|
||||
info.source = sourceTag;
|
||||
|
||||
@@ -58,11 +58,6 @@ Singleton {
|
||||
profileImage = "";
|
||||
return;
|
||||
}
|
||||
if (Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true") {
|
||||
profileImage = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!freedeskAvailable) {
|
||||
profileImage = "";
|
||||
return;
|
||||
@@ -296,52 +291,6 @@ Singleton {
|
||||
});
|
||||
}
|
||||
|
||||
property string pendingGreeterProfileUser: ""
|
||||
|
||||
function getGreeterUserProfileImage(username) {
|
||||
if (!username) {
|
||||
profileImage = "";
|
||||
pendingGreeterProfileUser = "";
|
||||
return;
|
||||
}
|
||||
if (typeof GreeterUsersService !== "undefined") {
|
||||
const cachedPath = GreeterUsersService.profileImagePath(username);
|
||||
if (cachedPath) {
|
||||
profileImage = cachedPath;
|
||||
pendingGreeterProfileUser = "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
pendingGreeterProfileUser = username;
|
||||
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;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: userProfileCheckProcess
|
||||
command: []
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const trimmed = text.trim();
|
||||
if (trimmed && trimmed !== "" && !trimmed.includes("Error") && trimmed !== "/var/lib/AccountsService/icons/") {
|
||||
root.profileImage = trimmed;
|
||||
} else {
|
||||
root.profileImage = "";
|
||||
}
|
||||
root.pendingGreeterProfileUser = "";
|
||||
}
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0 && root.pendingGreeterProfileUser !== "") {
|
||||
root.profileImage = "";
|
||||
root.pendingGreeterProfileUser = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "profile"
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modules.Greetd
|
||||
import "../Common/suncalc.js" as SunCalc
|
||||
|
||||
Singleton {
|
||||
@@ -463,7 +462,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function getConfiguredLocationName() {
|
||||
return SessionData.isGreeterMode ? SessionData.weatherLocation : SettingsData.weatherLocation;
|
||||
return SettingsData.weatherLocation;
|
||||
}
|
||||
|
||||
function setLocation(lat, lon, city, country) {
|
||||
@@ -517,8 +516,8 @@ Singleton {
|
||||
|
||||
function updateLocation() {
|
||||
const useAuto = SettingsData.useAutoLocation;
|
||||
const coords = SessionData.isGreeterMode ? SessionData.weatherCoordinates : SettingsData.weatherCoordinates;
|
||||
const cityName = SessionData.isGreeterMode ? SessionData.weatherLocation : SettingsData.weatherLocation;
|
||||
const coords = SettingsData.weatherCoordinates;
|
||||
const cityName = SettingsData.weatherLocation;
|
||||
|
||||
if (useAuto) {
|
||||
getLocationFromService();
|
||||
@@ -1046,7 +1045,7 @@ Singleton {
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: nextInterval()
|
||||
running: root.refCount > 0 && SettingsData.weatherEnabled && !SessionData.isGreeterMode
|
||||
running: root.refCount > 0 && SettingsData.weatherEnabled
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
|
||||
@@ -227,7 +227,7 @@ Item {
|
||||
isPlaying: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
|
||||
onFrameTicked: {
|
||||
if (!root.isSeeking)
|
||||
activePlayer.positionChanged();
|
||||
MprisController.refreshPosition(activePlayer);
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -12,7 +12,7 @@ local config_home = vim.env.XDG_CONFIG_HOME
|
||||
if config_home == nil or #config_home == 0 then
|
||||
config_home = vim.fs.joinpath(vim.env.HOME, ".config")
|
||||
end
|
||||
local settings_file_path = vim.fs.joinpath(config_home, "Dux Shell", "settings.json")
|
||||
local settings_file_path = vim.fs.joinpath(config_home, "dms", "settings.json")
|
||||
local settings_file = io.open(settings_file_path, "r")
|
||||
if settings_file == nil then
|
||||
vim.notify(
|
||||
|
||||
@@ -112,7 +112,6 @@ fi
|
||||
targets=(
|
||||
"${quickshell_dir}/shell.qml"
|
||||
"${quickshell_dir}/DMSShell.qml"
|
||||
"${quickshell_dir}/DMSGreeter.qml"
|
||||
)
|
||||
|
||||
qmllint_args=(
|
||||
|
||||
+5
-15
@@ -18,20 +18,21 @@ import qs.Services
|
||||
ShellRoot {
|
||||
id: entrypoint
|
||||
|
||||
readonly property bool runGreeter: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
readonly property bool disableHotReload: Quickshell.env("DMS_DISABLE_HOT_RELOAD") === "1" || Quickshell.env("DMS_DISABLE_HOT_RELOAD") === "true"
|
||||
|
||||
readonly property bool shellReady: !!Quickshell.env("NOTIFY_SOCKET") && !runGreeter && dmsShellLoader.status === Loader.Ready && DMSService.isConnected && DMSService.apiVersion >= 38
|
||||
readonly property bool shellReady: !!Quickshell.env("NOTIFY_SOCKET") && dmsShellLoader.status === Loader.Ready && DMSService.isConnected && DMSService.apiVersion >= 38
|
||||
|
||||
onShellReadyChanged: {
|
||||
if (shellReady)
|
||||
DMSService.sendRequest("shell.ready", {"pid": Quickshell.processId});
|
||||
DMSService.sendRequest("shell.ready", {
|
||||
"pid": Quickshell.processId
|
||||
});
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: Quickshell
|
||||
property: "watchFiles"
|
||||
value: !entrypoint.disableHotReload && (entrypoint.runGreeter || !IdleService.isShellLocked)
|
||||
value: !entrypoint.disableHotReload && !IdleService.isShellLocked
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -46,8 +47,6 @@ ShellRoot {
|
||||
DC.Host.hyprlandFocusGrab = Qt.binding(() => CompositorService.useHyprlandFocusGrab);
|
||||
DC.Host.ownWindowDecorations = Qt.binding(() => SettingsData.floatingWindowTitleBars);
|
||||
void IconThemeService.ready;
|
||||
if (entrypoint.runGreeter)
|
||||
return;
|
||||
// Start SNI registration even when no tray widget is visible.
|
||||
void SystemTray.items;
|
||||
// Build the polkit agent here, outside incubation: first-touching it from a Connections target during DMSShell's async load crashed QQmlConnections::connectSignalsToMethods.
|
||||
@@ -56,7 +55,6 @@ ShellRoot {
|
||||
|
||||
Loader {
|
||||
id: wallpaperLoader
|
||||
active: !entrypoint.runGreeter
|
||||
asynchronous: false
|
||||
|
||||
sourceComponent: Scope {
|
||||
@@ -73,7 +71,6 @@ ShellRoot {
|
||||
|
||||
Loader {
|
||||
id: shellCoreLoader
|
||||
active: !entrypoint.runGreeter
|
||||
asynchronous: true
|
||||
source: "ShellCore.qml"
|
||||
onLoaded: dmsShellLoader.setSource("DMSShell.qml", {
|
||||
@@ -85,11 +82,4 @@ ShellRoot {
|
||||
id: dmsShellLoader
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: dmsGreeterLoader
|
||||
active: entrypoint.runGreeter
|
||||
asynchronous: false
|
||||
source: "DMSGreeter.qml"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -36,8 +36,8 @@
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
{ "appId": "alacritty", "mangoTags": [3] },
|
||||
{ "appId": "firefox", "mangoTags": [2, 3] },
|
||||
{ "appId": "alacritty", "mangoMonitor": "DP-1", "mangoTags": [3] },
|
||||
{ "appId": "firefox", "mangoMonitor": "DP-1", "mangoTags": [2, 3] },
|
||||
{ "appId": "untagged" }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -20,6 +20,14 @@ const aqueousService = source("../Services/AqueousService.qml");
|
||||
const model = vm.createContext({});
|
||||
vm.runInContext(source("../Common/WindowModel.js").replace(/^\.pragma.*$/m, ""), model);
|
||||
|
||||
test("mango monitor changes invalidate cached toplevels", () => {
|
||||
const context = vm.createContext({});
|
||||
lift(compositorService, "_toplevelListEquivalent", context);
|
||||
const window = { mangoWindowId: 1, appId: "app1", mangoMonitor: "DP-1", mangoTags: [1] };
|
||||
assert.equal(context._toplevelListEquivalent([window], [{ ...window }]), true);
|
||||
assert.equal(context._toplevelListEquivalent([window], [{ ...window, mangoMonitor: "HDMI-A-1" }]), false);
|
||||
});
|
||||
|
||||
function niriState(scenario) {
|
||||
const s = fixture("niri")[scenario];
|
||||
const toplevels = s.toplevels.map(t => ({ ...t }));
|
||||
|
||||
@@ -291,8 +291,23 @@ test("mango scroll targets, active tag and windows", () => {
|
||||
assert.deepEqual(plain(model.mangoActiveWorkspace(dp1, "DP-1")), { id: 2, idx: 3, name: "", output: "DP-1", active: true, placeholder: false });
|
||||
assert.equal(model.mangoActiveWorkspace(mango("available", "HDMI-A-1"), "HDMI-A-1").id, 0);
|
||||
assert.equal(model.mangoActiveWorkspace(mango("available", "DP-2"), "DP-2"), null);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(dp1.windows, { id: 2 }), "appId"), ["alacritty", "firefox"]);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(dp1.windows, { id: 1 }), "appId"), ["firefox"]);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(dp1.windows, { id: 2, output: "DP-1" }), "appId"), ["alacritty", "firefox"]);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(dp1.windows, { id: 1, output: "DP-1" }), "appId"), ["firefox"]);
|
||||
});
|
||||
|
||||
test("mango workspace apps follow their output when windows share a tag", () => {
|
||||
const windows = [
|
||||
{ appId: "app1", mangoMonitor: "DP-1", mangoTags: [1] },
|
||||
{ appId: "app2", mangoMonitor: "HDMI-A-1", mangoTags: [1] }
|
||||
];
|
||||
const dp1 = model.mangoRecord(0, {}, "DP-1");
|
||||
const hdmi = model.mangoRecord(0, {}, "HDMI-A-1");
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(windows, dp1), "appId"), ["app1"]);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(windows, hdmi), "appId"), ["app2"]);
|
||||
|
||||
windows[0].mangoMonitor = "HDMI-A-1";
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(windows, dp1), "appId"), []);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(windows, hdmi), "appId"), ["app1", "app2"]);
|
||||
});
|
||||
|
||||
test("i3 lists order numbered before named and strip the number prefix", () => {
|
||||
|
||||
@@ -286,6 +286,11 @@ def extract_i18n_string(value):
|
||||
return None
|
||||
|
||||
|
||||
def is_status_notice(value):
|
||||
"""A description with an empty branch only shows while some state holds; it is not searchable text."""
|
||||
return re.search(r"""\?\s*(""|'')\s*:|:\s*(""|'')\s*$""", value.strip()) is not None
|
||||
|
||||
|
||||
def extract_tags(value):
|
||||
match = re.search(r"\[([^\]]+)\]", value)
|
||||
if not match:
|
||||
@@ -489,7 +494,7 @@ def find_settings_components(content, filename, wrappers, tab_meta, hub_meta):
|
||||
|
||||
desc_raw = extract_property(own, "description") or extract_property(own, "summary") or defaults.get("description") or borrowed_description(block, setting_key)
|
||||
description = None
|
||||
if desc_raw:
|
||||
if desc_raw and not is_status_notice(desc_raw):
|
||||
description = extract_i18n_string(desc_raw)
|
||||
|
||||
visible_raw = extract_property(own_scope(block), "visible")
|
||||
|
||||
@@ -106,12 +106,6 @@ def extract_qstr_strings(root_dir):
|
||||
if relative_path.parts[0] in ('PLUGINS', EXTERNAL_PLUGINS_DIR):
|
||||
continue
|
||||
|
||||
# Modules/Greetd terms are owned by the dux-greeter repo (tagged
|
||||
# dms-greeter in POEditor); the embedded copy exists only for
|
||||
# archinstall compatibility and must not upload untagged duplicates.
|
||||
if relative_path.parts[:2] == ('Modules', 'Greetd'):
|
||||
continue
|
||||
|
||||
with open(qml_file, 'r', encoding='utf-8') as f:
|
||||
for line_num, line in enumerate(f, 1):
|
||||
occ = {'file': str(relative_path), 'line': line_num}
|
||||
|
||||
@@ -941,6 +941,26 @@
|
||||
"icon": "dock_to_bottom",
|
||||
"description": "Position, pinned apps, trash"
|
||||
},
|
||||
{
|
||||
"section": "dockPlacement",
|
||||
"label": "Dock",
|
||||
"tabIndex": -1,
|
||||
"category": "Dock",
|
||||
"keywords": [
|
||||
"dock",
|
||||
"edge",
|
||||
"launcher bar",
|
||||
"layout",
|
||||
"name",
|
||||
"panel",
|
||||
"placement",
|
||||
"position",
|
||||
"rename",
|
||||
"taskbar"
|
||||
],
|
||||
"page": "dock",
|
||||
"icon": "dock_to_bottom"
|
||||
},
|
||||
{
|
||||
"section": "dockConfiguration",
|
||||
"label": "Docks",
|
||||
@@ -953,7 +973,6 @@
|
||||
"docks",
|
||||
"enable",
|
||||
"launcher bar",
|
||||
"name",
|
||||
"panel",
|
||||
"remove",
|
||||
"show",
|
||||
@@ -1407,24 +1426,6 @@
|
||||
"page": "theme_surfaces",
|
||||
"description": "Faint edge on cards, pills and widgets"
|
||||
},
|
||||
{
|
||||
"section": "dockPlacement",
|
||||
"label": "Layout",
|
||||
"tabIndex": -1,
|
||||
"category": "Dock",
|
||||
"keywords": [
|
||||
"dock",
|
||||
"edge",
|
||||
"launcher bar",
|
||||
"layout",
|
||||
"panel",
|
||||
"placement",
|
||||
"position",
|
||||
"taskbar"
|
||||
],
|
||||
"page": "dock",
|
||||
"icon": "dock_to_bottom"
|
||||
},
|
||||
{
|
||||
"section": "iconThemeLight",
|
||||
"label": "Light mode theme",
|
||||
@@ -11275,7 +11276,6 @@
|
||||
"tabIndex": 25,
|
||||
"category": "Gamma control",
|
||||
"keywords": [
|
||||
"available",
|
||||
"blue light",
|
||||
"color",
|
||||
"color temperature",
|
||||
@@ -11290,15 +11290,13 @@
|
||||
"output",
|
||||
"outputs",
|
||||
"redshift",
|
||||
"requires",
|
||||
"resolution",
|
||||
"screen",
|
||||
"screens",
|
||||
"temperature",
|
||||
"warm"
|
||||
],
|
||||
"parentLabel": "Displays",
|
||||
"description": "Gamma control not available. Requires DMS API v6+."
|
||||
"parentLabel": "Displays"
|
||||
},
|
||||
{
|
||||
"section": "nightModeTransitionMinutes",
|
||||
@@ -15896,12 +15894,10 @@
|
||||
"schedule",
|
||||
"settings",
|
||||
"share",
|
||||
"shared",
|
||||
"tint",
|
||||
"wallpaper"
|
||||
],
|
||||
"parentLabel": "Wallpaper & colors",
|
||||
"description": "Using shared settings from Gamma Control"
|
||||
"parentLabel": "Wallpaper & colors"
|
||||
},
|
||||
{
|
||||
"section": "wallpaperCycling",
|
||||
|
||||
@@ -19,7 +19,7 @@ if [[ -n "${QS_ARGS:-}" ]]; then
|
||||
# shellcheck disable=SC2206
|
||||
qs_args=($QS_ARGS)
|
||||
else
|
||||
state_file="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/danklinux.path"
|
||||
state_file="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/dms.path"
|
||||
config_path="$(cat "$state_file" 2>/dev/null || true)"
|
||||
[[ -n "$config_path" ]] || { echo "no running dms shell ($state_file missing)" >&2; exit 1; }
|
||||
qs_args=(-p "$config_path")
|
||||
|
||||
Reference in New Issue
Block a user