Compare commits

..
20 Commits
Author SHA1 Message Date
bbedward 4ab57f14b8 plugins: label declared permissions as unenforced
(cherry picked from commit 14b3ddaf83)
2026-10-22 09:50:36 -04:00
bbedward ce81dfc95a plugins: keep manifest paths inside the plugin directory
(cherry picked from commit 6e86b53ccd)
2026-10-22 09:50:29 -04:00
bbedward a5511fe8d7 greeter: remove the embedded legacy greeter (again)
(cherry picked from commit c5d1d4fe46)
2026-10-22 09:50:19 -04:00
purian23 7d930f8f6c distos: compare ubuntu published commit by prefix
(cherry picked from commit 39a58f77ec)
2026-10-22 09:50:12 -04:00
Anton Kesy 11ef76b9d0 docs: use dux project names in README (#3847)
(cherry picked from commit 4f969c8e10)
2026-10-22 09:50:03 -04:00
purian23 e48de4cc2a notifications: keep popup content while it exits after the notification closes
Port 2.0

(cherry picked from commit 47c9d8d89b)
2026-10-22 01:58:36 +00:00
bbedward 1683fceac5 bar: keep far-edge island hosts at sheet size so the bar never jumps on expand
fixes #3855
port 2.0

(cherry picked from commit d8786eaaf7)
2026-10-22 01:57:39 +00:00
bbedward 2a83c5c69b battery: throttle pulse animation to 20fps
ref #3849

(cherry picked from commit 1b59a474dd)
2026-10-21 14:16:02 -04:00
bbedward 1acfae1755 dash: hold panel height while dragging cards unless hung from a top bar
lyrics: ignore floored position samples and shell refresh emits

(cherry picked from commit 28f921fa02)
2026-10-21 13:19:05 -04:00
Adarsh 1e1dbebb70 Theme Antigravity IDE via the VS Code extension (#3836)
(cherry picked from commit 479b1434de)
2026-10-21 13:18:58 -04:00
bbedward 53412d1f22 theme: publish typography for dux apps
(cherry picked from commit 8f2a486006)
2026-10-21 12:42:02 -04:00
bbedward 9ece8f26fa theme: skip matugen runs whose inputs match the last applied run
fixes #3841

(cherry picked from commit afeea489c9)
2026-10-21 11:25:29 -04:00
bbedward fd1295f959 core: only use the simple animation driver for the scrolling wallpaper
fixes #3837

(cherry picked from commit 27a703367f)
2026-10-21 11:25:20 -04:00
bbedward 1e3f16ac9d bar: slide an auto-hidden island by its compact height
fixes #3838

(cherry picked from commit 8e851b3dc4)
2026-10-21 11:25:11 -04:00
bbedward 4fa6f58720 workspaces: keep the pill highlight while hovering its app icons
fixes #3839

(cherry picked from commit 112d05901f)
2026-10-21 11:25:02 -04:00
Marlon Kranz ff45b3b4b2 fix(notifications): close popups when their screen stops showing them (#3844)
(cherry picked from commit b81954454a)
2026-10-21 11:24:49 -04:00
Tomás 12e54e0225 Keep MangoWM workspace apps on their own monitor (#3832)
port 2.0

(cherry picked from commit 203b1369b3)
2026-10-21 11:24:35 -04:00
bbedward f63d8bf4fb niri: only emit displayScales when a scale changed
ref #3842
port 2.0

(cherry picked from commit 90d3c9731c)
2026-10-21 15:18:54 +00:00
Leonard 1e8423992d refactor(ui): match the dock settings page with the bar settings page (#3829)
port 2.0

(cherry picked from commit 5ffc581ecf)
2026-10-21 00:54:58 +00:00
bbedward c4ecb988b4 matugen: neovim template reads the dms config dir"
port 2.0

(cherry picked from commit 3284acab28)
2026-10-20 21:03:37 +00:00
66 changed files with 349 additions and 3230 deletions
+7 -7
View File
@@ -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
+27 -1
View File
@@ -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 {
+1
View File
@@ -965,6 +965,7 @@ var vscodeEditors = []vscodeEditor{
{"cursor", ".cursor"},
{"windsurf", ".windsurf"},
{"positron", ".positron"},
{"antigravity", ".antigravity-ide"},
{"vscode-insiders", ".vscode-insiders"},
}
+3 -3
View File
@@ -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
Generated
+3 -3
View File
@@ -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": {
+3 -11
View File
@@ -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");
}
}
+5 -52
View File
@@ -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
+9 -87
View File
@@ -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;
+3
View File
@@ -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
View File
@@ -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"
+1 -1
View File
@@ -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) {
+6 -6
View File
@@ -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;
-9
View File
@@ -1,9 +0,0 @@
import QtQuick
import Quickshell
import qs.Modules.Greetd
Scope {
id: root
GreeterSurface {}
}
+6 -2
View File
@@ -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
+11 -17
View File
@@ -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
+4 -1
View File
@@ -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
+1
View File
@@ -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
+1 -1
View File
@@ -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 {
+1
View File
@@ -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)
+1
View File
@@ -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",
-20
View File
@@ -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;
}
-162
View File
@@ -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();
}
}
-7
View File
@@ -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()
+35 -20
View File
@@ -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)
}
}
}
}
+1 -19
View File
@@ -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;
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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;
-166
View File
@@ -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);
}
}
+13 -1
View File
@@ -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)
+10 -1
View File
@@ -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 {
+9
View File
@@ -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];
+25 -17
View File
@@ -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;
-51
View File
@@ -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"
+4 -5
View File
@@ -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: {
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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" }
]
},
+8
View File
@@ -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 }));
+17 -2
View File
@@ -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",
+1 -1
View File
@@ -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")