1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-31 08:52:49 -05:00

Compare commits

...

15 Commits

Author SHA1 Message Date
Karsten Zeides
bc27253cbf fix(README): fixes documentation link to include trailing slash (#920)
fixes same issue as described in AvengeMedia/DankLinux-Docs#25
2025-12-09 08:13:33 +01:00
Lucas
0672b711f3 nix: fix greeter custom theme (#954) 2025-12-09 07:14:13 +01:00
bbedward
ed9ee6e347 gamma: fix transition on enable 2025-12-09 00:46:49 -05:00
bbedward
7ad23ad4a2 gamma: fix night mode toggling 2025-12-09 00:35:52 -05:00
bbedward
8a83f03cc1 keybinds: fix provider loading via IPC 2025-12-09 00:30:14 -05:00
bbedward
0be9ac4097 keybinds: fix cheatsheet on non niri
- separate read only logic from writeread
2025-12-09 00:03:39 -05:00
bbedward
ba5be6b516 wallpaper: cleanup transitions 2025-12-08 23:53:50 -05:00
bbedward
c4aea6d326 themes: dont handle custom themes in onCompleted
- Defer entirley to FileView
2025-12-08 23:44:04 -05:00
bbedward
858c6407a9 dankinstall: ;remove keyring file on debian 2025-12-08 23:37:13 -05:00
bbedward
c4313395b5 dankinstall: use gpg batch for deb 2025-12-08 23:36:14 -05:00
bbedward
a32aec3d59 dankinstall: fix other debian sudo cmd 2025-12-08 23:31:08 -05:00
bbedward
696bcfe8fa dankinstall: fix deb sudo command 2025-12-08 23:30:03 -05:00
bbedward
2f3a253c6a wallpaper: fix per-monitor wallpaper in dash 2025-12-08 23:25:02 -05:00
bbedward
e41fbe0188 misc: change transmission icon override 2025-12-08 23:11:17 -05:00
bbedward
ef9d28597b dankinstall: don't fail suse if addrepo fails 2025-12-08 23:03:46 -05:00
15 changed files with 309 additions and 203 deletions

View File

@@ -127,7 +127,7 @@ dms plugins search # Browse plugin registry
## Documentation ## Documentation
- **Website:** [danklinux.com](https://danklinux.com) - **Website:** [danklinux.com](https://danklinux.com)
- **Docs:** [danklinux.com/docs](https://danklinux.com/docs) - **Docs:** [danklinux.com/docs](https://danklinux.com/docs/)
- **Theming:** [Application themes](https://danklinux.com/docs/dankmaterialshell/application-themes) | [Custom themes](https://danklinux.com/docs/dankmaterialshell/custom-themes) - **Theming:** [Application themes](https://danklinux.com/docs/dankmaterialshell/application-themes) | [Custom themes](https://danklinux.com/docs/dankmaterialshell/custom-themes)
- **Plugins:** [Development guide](https://danklinux.com/docs/dankmaterialshell/plugins-overview) - **Plugins:** [Development guide](https://danklinux.com/docs/dankmaterialshell/plugins-overview)
- **Support:** [Ko-fi](https://ko-fi.com/avengemediallc) - **Support:** [Ko-fi](https://ko-fi.com/avengemediallc)

View File

@@ -208,7 +208,7 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential") checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential")
if err := checkCmd.Run(); err != nil { if err := checkCmd.Run(); err != nil {
cmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y build-essential") cmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential")
if err := d.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil { if err := d.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil {
return fmt.Errorf("failed to install build-essential: %w", err) return fmt.Errorf("failed to install build-essential: %w", err)
} }
@@ -225,7 +225,7 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
} }
devToolsCmd := ExecSudoCommand(ctx, sudoPassword, devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
"apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev") "DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil { if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
return fmt.Errorf("failed to install development tools: %w", err) return fmt.Errorf("failed to install development tools: %w", err)
} }
@@ -453,7 +453,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
CommandInfo: fmt.Sprintf("curl & gpg to add key for %s", pkg.RepoURL), CommandInfo: fmt.Sprintf("curl & gpg to add key for %s", pkg.RepoURL),
} }
keyCmd := fmt.Sprintf("curl -fsSL %s/Release.key | gpg --dearmor -o %s", baseURL, keyringPath) keyCmd := fmt.Sprintf("bash -c 'rm -f %s && curl -fsSL %s/Release.key | gpg --batch --dearmor -o %s'", keyringPath, baseURL, keyringPath)
cmd := ExecSudoCommand(ctx, sudoPassword, keyCmd) cmd := ExecSudoCommand(ctx, sudoPassword, keyCmd)
if err := d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.18, 0.20); err != nil { if err := d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.18, 0.20); err != nil {
return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err) return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
@@ -471,7 +471,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
} }
addRepoCmd := ExecSudoCommand(ctx, sudoPassword, addRepoCmd := ExecSudoCommand(ctx, sudoPassword,
fmt.Sprintf("echo '%s' | tee %s", repoLine, listFile)) fmt.Sprintf("bash -c \"echo '%s' | tee %s\"", repoLine, listFile))
if err := d.runWithProgress(addRepoCmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil { if err := d.runWithProgress(addRepoCmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
return fmt.Errorf("failed to add OBS repo %s: %w", pkg.RepoURL, err) return fmt.Errorf("failed to add OBS repo %s: %w", pkg.RepoURL, err)
} }
@@ -506,7 +506,7 @@ func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []
d.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", "))) d.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
args := []string{"apt-get", "install", "-y"} args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
args = append(args, packages...) args = append(args, packages...)
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
@@ -616,7 +616,7 @@ func (d *DebianDistribution) installRust(ctx context.Context, sudoPassword strin
CommandInfo: "sudo apt-get install rustup", CommandInfo: "sudo apt-get install rustup",
} }
rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y rustup") rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y rustup")
if err := d.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil { if err := d.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
return fmt.Errorf("failed to install rustup: %w", err) return fmt.Errorf("failed to install rustup: %w", err)
} }
@@ -655,7 +655,7 @@ func (d *DebianDistribution) installGo(ctx context.Context, sudoPassword string,
CommandInfo: "sudo apt-get install golang-go", CommandInfo: "sudo apt-get install golang-go",
} }
installCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y golang-go") installCmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y golang-go")
return d.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.87, 0.90) return d.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.87, 0.90)
} }

View File

@@ -476,7 +476,7 @@ func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Pac
cmd := ExecSudoCommand(ctx, sudoPassword, cmd := ExecSudoCommand(ctx, sudoPassword,
fmt.Sprintf("zypper addrepo -f %s", repoURL)) fmt.Sprintf("zypper addrepo -f %s", repoURL))
if err := o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil { if err := o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
return fmt.Errorf("failed to enable OBS repo %s: %w", pkg.RepoURL, err) o.log(fmt.Sprintf("OBS repo %s add failed (may already exist): %v", pkg.RepoURL, err))
} }
enabledRepos[pkg.RepoURL] = true enabledRepos[pkg.RepoURL] = true

View File

@@ -562,53 +562,62 @@ func (m *Manager) transitionWorker() {
case <-m.stopChan: case <-m.stopChan:
return return
case targetTemp := <-m.transitionChan: case targetTemp := <-m.transitionChan:
m.runTransition(targetTemp, steps, stepDur)
}
}
}
func (m *Manager) runTransition(targetTemp int, steps int, stepDur time.Duration) {
for {
m.transitionMutex.Lock() m.transitionMutex.Lock()
currentTemp := m.currentTemp currentTemp := m.currentTemp
m.targetTemp = targetTemp m.targetTemp = targetTemp
m.transitionMutex.Unlock() m.transitionMutex.Unlock()
if currentTemp == targetTemp { if currentTemp == targetTemp {
continue return
} }
log.Debugf("Starting smooth transition: %dK -> %dK over %v", currentTemp, targetTemp, dur) log.Debugf("Starting smooth transition: %dK -> %dK over %v", currentTemp, targetTemp, stepDur*time.Duration(steps))
stepLoop: redirected := false
for i := 0; i <= steps; i++ { for i := 0; i <= steps; i++ {
select { select {
case newTarget := <-m.transitionChan: case newTarget := <-m.transitionChan:
m.transitionMutex.Lock() m.transitionMutex.Lock()
m.targetTemp = newTarget m.targetTemp = newTarget
m.transitionMutex.Unlock() m.transitionMutex.Unlock()
log.Debugf("Transition %dK -> %dK aborted (newer transition started)", currentTemp, targetTemp) if newTarget != targetTemp {
break stepLoop log.Debugf("Transition %dK -> %dK redirected to %dK", currentTemp, targetTemp, newTarget)
targetTemp = newTarget
redirected = true
}
default: default:
} }
m.transitionMutex.RLock() if redirected {
if m.targetTemp != targetTemp {
m.transitionMutex.RUnlock()
break break
} }
m.transitionMutex.RLock()
currentTarget := m.targetTemp
m.transitionMutex.RUnlock() m.transitionMutex.RUnlock()
if currentTarget != targetTemp {
targetTemp = currentTarget
break
}
progress := float64(i) / float64(steps) progress := float64(i) / float64(steps)
temp := currentTemp + int(float64(targetTemp-currentTemp)*progress) temp := currentTemp + int(float64(targetTemp-currentTemp)*progress)
m.post(func() { m.applyNowOnActor(temp) }) m.post(func() { m.applyNowOnActor(temp) })
if i < steps { if i == steps {
time.Sleep(stepDur)
}
}
m.transitionMutex.RLock()
finalTarget := m.targetTemp
m.transitionMutex.RUnlock()
if finalTarget == targetTemp {
log.Debugf("Transition complete: now at %dK", targetTemp) log.Debugf("Transition complete: now at %dK", targetTemp)
return
} }
time.Sleep(stepDur)
} }
} }
} }
@@ -1206,11 +1215,18 @@ func (m *Manager) SetGamma(gamma float64) error {
func (m *Manager) SetEnabled(enabled bool) { func (m *Manager) SetEnabled(enabled bool) {
m.configMutex.Lock() m.configMutex.Lock()
wasEnabled := m.config.Enabled
m.config.Enabled = enabled m.config.Enabled = enabled
highTemp := m.config.HighTemp
m.configMutex.Unlock() m.configMutex.Unlock()
if enabled { if enabled {
if !m.controlsInitialized { if !m.controlsInitialized {
m.transitionMutex.Lock()
m.currentTemp = highTemp
m.targetTemp = highTemp
m.transitionMutex.Unlock()
m.post(func() { m.post(func() {
log.Info("Creating gamma controls") log.Info("Creating gamma controls")
gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1) gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
@@ -1221,9 +1237,10 @@ func (m *Manager) SetEnabled(enabled bool) {
log.Errorf("Failed to create gamma controls: %v", err) log.Errorf("Failed to create gamma controls: %v", err)
} else { } else {
m.controlsInitialized = true m.controlsInitialized = true
m.triggerUpdate()
} }
}) })
} else { } else if !wasEnabled {
m.triggerUpdate() m.triggerUpdate()
} }
} else { } else {

View File

@@ -10,13 +10,14 @@
user = config.services.greetd.settings.default_session.user; user = config.services.greetd.settings.default_session.user;
cacheDir = "/var/lib/dms-greeter";
greeterScript = pkgs.writeShellScriptBin "dms-greeter" '' greeterScript = pkgs.writeShellScriptBin "dms-greeter" ''
export PATH=$PATH:${lib.makeBinPath [cfg.quickshell.package config.programs.${cfg.compositor.name}.package]} export PATH=$PATH:${lib.makeBinPath [cfg.quickshell.package config.programs.${cfg.compositor.name}.package]}
${lib.escapeShellArgs ([ ${lib.escapeShellArgs ([
"sh" "sh"
"${../../quickshell/Modules/Greetd/assets/dms-greeter}" "${../../quickshell/Modules/Greetd/assets/dms-greeter}"
"--cache-dir" "--cache-dir"
"/var/lib/dmsgreeter" cacheDir
"--command" "--command"
cfg.compositor.name cfg.compositor.name
"-p" "-p"
@@ -27,6 +28,8 @@
"${pkgs.writeText "dmsgreeter-compositor-config" cfg.compositor.customConfig}" "${pkgs.writeText "dmsgreeter-compositor-config" cfg.compositor.customConfig}"
])} ${lib.optionalString cfg.logs.save "> ${cfg.logs.path} 2>&1"} ])} ${lib.optionalString cfg.logs.save "> ${cfg.logs.path} 2>&1"}
''; '';
jq = lib.getExe pkgs.jq;
in { in {
imports = let imports = let
msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead."; msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead.";
@@ -93,17 +96,17 @@ in {
material-symbols material-symbols
]; ];
systemd.tmpfiles.settings."10-dmsgreeter" = { systemd.tmpfiles.settings."10-dmsgreeter" = {
"/var/lib/dmsgreeter".d = { ${cacheDir}.d = {
user = user; user = user;
group = group =
if config.users.users.${user}.group != "" if config.users.users.${user}.group != ""
then config.users.users.${user}.group then config.users.users.${user}.group
else "greeter"; else "greeter";
mode = "0755"; mode = "0750";
}; };
}; };
systemd.services.greetd.preStart = '' systemd.services.greetd.preStart = ''
cd /var/lib/dmsgreeter cd ${cacheDir}
${lib.concatStringsSep "\n" (lib.map (f: '' ${lib.concatStringsSep "\n" (lib.map (f: ''
if [ -f "${f}" ]; then if [ -f "${f}" ]; then
cp "${f}" . cp "${f}" .
@@ -112,9 +115,16 @@ in {
cfg.configFiles)} cfg.configFiles)}
if [ -f session.json ]; then if [ -f session.json ]; then
if cp "$(${lib.getExe pkgs.jq} -r '.wallpaperPath' session.json)" wallpaper.jpg; then if cp "$(${jq} -r '.wallpaperPath' session.json)" wallpaper.jpg; then
mv session.json session.orig.json mv session.json session.orig.json
${lib.getExe pkgs.jq} '.wallpaperPath = "/var/lib/dmsgreeter/wallpaper.jpg"' session.orig.json > session.json ${jq} '.wallpaperPath = "${cacheDir}/wallpaper.jpg"' session.orig.json > session.json
fi
fi
if [ -f settings.json ]; then
if cp "$(${jq} -r '.customThemeFile' settings.json)" custom-theme.json; then
mv settings.json settings.orig.json
${jq} '.customThemeFile = "${cacheDir}/custom-theme.json"' settings.orig.json > settings.json
fi fi
fi fi

1
quickshell/CODENAME Normal file
View File

@@ -0,0 +1 @@
The Dark Knight

View File

@@ -53,7 +53,7 @@ Singleton {
if (appId === "home assistant desktop") if (appId === "home assistant desktop")
return "homeassistant-desktop"; return "homeassistant-desktop";
if (appId.includes("com.transmissionbt.transmission")) if (appId.includes("com.transmissionbt.transmission"))
return "transmission-gtk"; return "transmission";
return appId; return appId;
} }

View File

@@ -128,21 +128,13 @@ Singleton {
setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType); setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType);
} }
} }
} else { } else if (currentTheme !== "custom") {
let primaryColor; const darkTheme = StockThemes.getThemeByName(currentTheme, false);
let matugenType; const lightTheme = StockThemes.getThemeByName(currentTheme, true);
if (currentTheme === "custom") { if (darkTheme && darkTheme.primary) {
if (customThemeData && customThemeData.primary) { const stockColors = buildMatugenColorsFromTheme(darkTheme, lightTheme);
primaryColor = customThemeData.primary; const themeData = isLight ? lightTheme : darkTheme;
matugenType = customThemeData.matugen_type; setDesiredTheme("hex", themeData.primary, isLight, iconTheme, themeData.matugen_type, stockColors);
}
} else {
primaryColor = currentThemeData.primary;
matugenType = currentThemeData.matugen_type;
}
if (primaryColor) {
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType);
} }
} }
}, 0); }, 0);

View File

@@ -332,18 +332,16 @@ Item {
if (!provider) if (!provider)
return "ERROR: No provider specified"; return "ERROR: No provider specified";
KeybindsService.currentProvider = provider; KeybindsService.loadCheatsheet(provider);
KeybindsService.loadBinds();
root.hyprKeybindsModalLoader.active = true; root.hyprKeybindsModalLoader.active = true;
if (!root.hyprKeybindsModalLoader.item) if (!root.hyprKeybindsModalLoader.item)
return `KEYBINDS_TOGGLE_FAILED: ${provider}`; return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) { if (root.hyprKeybindsModalLoader.item.shouldBeVisible)
root.hyprKeybindsModalLoader.item.close(); root.hyprKeybindsModalLoader.item.close();
} else { else
root.hyprKeybindsModalLoader.item.open(); root.hyprKeybindsModalLoader.item.open();
}
return `KEYBINDS_TOGGLE_SUCCESS: ${provider}`; return `KEYBINDS_TOGGLE_SUCCESS: ${provider}`;
} }
@@ -351,18 +349,16 @@ Item {
if (!provider) if (!provider)
return "ERROR: No provider specified"; return "ERROR: No provider specified";
KeybindsService.currentProvider = provider; KeybindsService.loadCheatsheet(provider);
KeybindsService.loadBinds();
root.hyprKeybindsModalLoader.active = true; root.hyprKeybindsModalLoader.active = true;
if (!root.hyprKeybindsModalLoader.item) if (!root.hyprKeybindsModalLoader.item)
return `KEYBINDS_TOGGLE_FAILED: ${provider}`; return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) { if (root.hyprKeybindsModalLoader.item.shouldBeVisible)
root.hyprKeybindsModalLoader.item.close(); root.hyprKeybindsModalLoader.item.close();
} else { else
root.hyprKeybindsModalLoader.item.open(); root.hyprKeybindsModalLoader.item.open();
}
return `KEYBINDS_TOGGLE_SUCCESS: ${provider} (${path})`; return `KEYBINDS_TOGGLE_SUCCESS: ${provider} (${path})`;
} }
@@ -370,8 +366,7 @@ Item {
if (!provider) if (!provider)
return "ERROR: No provider specified"; return "ERROR: No provider specified";
KeybindsService.currentProvider = provider; KeybindsService.loadCheatsheet(provider);
KeybindsService.loadBinds();
root.hyprKeybindsModalLoader.active = true; root.hyprKeybindsModalLoader.active = true;
if (!root.hyprKeybindsModalLoader.item) if (!root.hyprKeybindsModalLoader.item)
@@ -385,8 +380,7 @@ Item {
if (!provider) if (!provider)
return "ERROR: No provider specified"; return "ERROR: No provider specified";
KeybindsService.currentProvider = provider; KeybindsService.loadCheatsheet(provider);
KeybindsService.loadBinds();
root.hyprKeybindsModalLoader.active = true; root.hyprKeybindsModalLoader.active = true;
if (!root.hyprKeybindsModalLoader.item) if (!root.hyprKeybindsModalLoader.item)

View File

@@ -17,7 +17,11 @@ DankModal {
modalWidth: _maxW modalWidth: _maxW
modalHeight: _maxH modalHeight: _maxH
onBackgroundClicked: close() onBackgroundClicked: close()
onOpened: () => Qt.callLater(() => modalFocusScope.forceActiveFocus()) onOpened: {
Qt.callLater(() => modalFocusScope.forceActiveFocus());
if (!Object.keys(KeybindsService.cheatsheet).length && KeybindsService.cheatsheetAvailable)
KeybindsService.loadCheatsheet();
}
HyprlandFocusGrab { HyprlandFocusGrab {
windows: [root.contentWindow] windows: [root.contentWindow]
@@ -66,7 +70,7 @@ DankModal {
spacing: Theme.spacingL spacing: Theme.spacingL
StyledText { StyledText {
text: KeybindsService.keybinds.title || "Keybinds" text: KeybindsService.cheatsheet.title || "Keybinds"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.primary color: Theme.primary
@@ -82,7 +86,7 @@ DankModal {
Component.onCompleted: root.activeFlickable = mainFlickable Component.onCompleted: root.activeFlickable = mainFlickable
property var rawBinds: KeybindsService.keybinds.binds || {} property var rawBinds: KeybindsService.cheatsheet.binds || {}
property var categories: { property var categories: {
const processed = {}; const processed = {};
for (const cat in rawBinds) { for (const cat in rawBinds) {
@@ -114,12 +118,36 @@ DankModal {
} }
property var categoryKeys: Object.keys(categories) property var categoryKeys: Object.keys(categories)
function estimateCategoryHeight(catName) {
const catData = categories[catName];
if (!catData)
return 0;
let bindCount = 0;
for (const key of catData.subcatKeys) {
bindCount += catData.subcats[key]?.length || 0;
if (key !== "_root")
bindCount += 1;
}
return 40 + bindCount * 28;
}
function distributeCategories(cols) { function distributeCategories(cols) {
const columns = []; const columns = [];
for (let i = 0; i < cols; i++) const heights = [];
for (let i = 0; i < cols; i++) {
columns.push([]); columns.push([]);
for (let i = 0; i < categoryKeys.length; i++) heights.push(0);
columns[i % cols].push(categoryKeys[i]); }
const sorted = [...categoryKeys].sort((a, b) => estimateCategoryHeight(b) - estimateCategoryHeight(a));
for (const cat of sorted) {
let minIdx = 0;
for (let i = 1; i < cols; i++) {
if (heights[i] < heights[minIdx])
minIdx = i;
}
columns[minIdx].push(cat);
heights[minIdx] += estimateCategoryHeight(cat);
}
return columns; return columns;
} }
@@ -137,7 +165,7 @@ DankModal {
Column { Column {
id: masonryColumn id: masonryColumn
width: (rowLayout.width - rowLayout.spacing * (rowLayout.numColumns - 1)) / rowLayout.numColumns width: (rowLayout.width - rowLayout.spacing * (rowLayout.numColumns - 1)) / rowLayout.numColumns
spacing: Theme.spacingM spacing: Theme.spacingXL
Repeater { Repeater {
model: rowLayout.columnCategories[index] || [] model: rowLayout.columnCategories[index] || []
@@ -199,37 +227,37 @@ DankModal {
Repeater { Repeater {
model: parent.parent.subcatBinds model: parent.parent.subcatBinds
Row { Item {
width: parent.width width: parent.width
spacing: Theme.spacingS height: 24
StyledRect { StyledRect {
width: Math.min(140, parent.width * 0.42) id: keyBadge
width: Math.min(keyText.implicitWidth + 12, 160)
height: 22 height: 22
radius: 4 radius: 4
opacity: 0.9 anchors.verticalCenter: parent.verticalCenter
StyledText { StyledText {
id: keyText
anchors.centerIn: parent anchors.centerIn: parent
anchors.margins: 2
width: parent.width - 4
color: Theme.secondary color: Theme.secondary
text: modelData.key || "" text: modelData.key || ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
isMonospace: true isMonospace: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
} }
} }
StyledText { StyledText {
width: parent.width - 150 anchors.left: parent.left
anchors.leftMargin: 170
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: modelData.desc || modelData.action || "" text: modelData.desc || modelData.action || ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
opacity: 0.9 opacity: 0.9
elide: Text.ElideRight elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
} }
} }
} }

View File

@@ -384,7 +384,7 @@ DankPopout {
active: true active: true
tabBarItem: tabBar tabBarItem: tabBar
keyForwardTarget: mainContainer keyForwardTarget: mainContainer
targetScreen: root.triggerScreen targetScreen: root.screen
parentPopout: root parentPopout: root
} }
} }

View File

@@ -3,7 +3,6 @@ import QtQuick.Effects
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.Settings.Widgets
Item { Item {
id: aboutTab id: aboutTab
@@ -200,6 +199,16 @@ Item {
width: parent.width width: parent.width
} }
StyledText {
visible: SystemUpdateService.shellCodename.length > 0
text: `"${SystemUpdateService.shellCodename}"`
font.pixelSize: Theme.fontSizeMedium
font.italic: true
color: Theme.surfaceVariantText
horizontalAlignment: Text.AlignHCenter
width: parent.width
}
Row { Row {
id: resourceButtonsRow id: resourceButtonsRow
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -82,6 +82,7 @@ Variants {
readonly property bool transitioning: transitionAnimation.running readonly property bool transitioning: transitionAnimation.running
property bool effectActive: false property bool effectActive: false
property bool useNextForEffect: false property bool useNextForEffect: false
property string pendingWallpaper: ""
function getFillMode(modeName) { function getFillMode(modeName) {
switch (modeName) { switch (modeName) {
@@ -162,12 +163,10 @@ Variants {
return; return;
if (!newPath || newPath.startsWith("#")) if (!newPath || newPath.startsWith("#"))
return; return;
if (root.transitioning) {
transitionAnimation.stop(); if (root.transitioning || root.effectActive) {
root.transitionProgress = 0; root.pendingWallpaper = newPath;
root.effectActive = false; return;
currentWallpaper.source = nextWallpaper.source;
nextWallpaper.source = "";
} }
if (!currentWallpaper.source) { if (!currentWallpaper.source) {
@@ -488,24 +487,28 @@ Variants {
currentWallpaper.source = nextWallpaper.source; currentWallpaper.source = nextWallpaper.source;
} }
root.useNextForEffect = false; root.useNextForEffect = false;
Qt.callLater(() => {
nextWallpaper.source = ""; nextWallpaper.source = "";
Qt.callLater(() => { root.transitionProgress = 0.0;
root.effectActive = false;
currentWallpaper.layer.enabled = false; currentWallpaper.layer.enabled = false;
nextWallpaper.layer.enabled = false; nextWallpaper.layer.enabled = false;
currentWallpaper.cache = true; currentWallpaper.cache = true;
nextWallpaper.cache = false; nextWallpaper.cache = false;
root.transitionProgress = 0.0; root.effectActive = false;
});
if (root.pendingWallpaper) {
var pending = root.pendingWallpaper;
root.pendingWallpaper = "";
Qt.callLater(() => {
root.changeWallpaper(pending, true);
}); });
} }
} }
}
MultiEffect { MultiEffect {
anchors.fill: parent anchors.fill: parent
source: effectLoader.active ? effectLoader.item : (root.actualTransitionType === "none" ? currentWallpaper : null) source: effectLoader.active ? effectLoader.item : currentWallpaper
visible: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && source !== null visible: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
blurEnabled: true blurEnabled: true
blur: 0.8 blur: 0.8
blurMax: 75 blurMax: 75

View File

@@ -22,6 +22,18 @@ Singleton {
property bool available: CompositorService.isNiri && shortcutInhibitorAvailable property bool available: CompositorService.isNiri && shortcutInhibitorAvailable
property string currentProvider: "niri" property string currentProvider: "niri"
readonly property string cheatsheetProvider: {
if (CompositorService.isNiri)
return "niri";
if (CompositorService.isHyprland)
return "hyprland";
return "";
}
property bool cheatsheetAvailable: cheatsheetProvider !== ""
property bool cheatsheetLoading: false
property var cheatsheet: ({})
property bool loading: false property bool loading: false
property bool saving: false property bool saving: false
property bool fixing: false property bool fixing: false
@@ -58,16 +70,13 @@ Singleton {
signal bindSaveCompleted(bool success) signal bindSaveCompleted(bool success)
signal bindRemoved(string key) signal bindRemoved(string key)
signal dmsBindsFixed signal dmsBindsFixed
signal cheatsheetLoaded
Component.onCompleted: {
if (available)
Qt.callLater(loadBinds);
}
Connections { Connections {
target: CompositorService target: CompositorService
function onCompositorChanged() { function onCompositorChanged() {
if (CompositorService.isNiri) if (!CompositorService.isNiri)
return;
Qt.callLater(root.loadBinds); Qt.callLater(root.loadBinds);
} }
} }
@@ -80,6 +89,31 @@ Singleton {
} }
} }
Process {
id: cheatsheetProcess
running: false
stdout: StdioCollector {
onStreamFinished: {
try {
root.cheatsheet = JSON.parse(text);
} catch (e) {
console.error("[KeybindsService] Failed to parse cheatsheet:", e);
root.cheatsheet = {};
}
root.cheatsheetLoading = false;
root.cheatsheetLoaded();
}
}
onExited: exitCode => {
if (exitCode === 0)
return;
console.warn("[KeybindsService] Cheatsheet load failed with code:", exitCode);
root.cheatsheetLoading = false;
}
}
Process { Process {
id: loadProcess id: loadProcess
running: false running: false
@@ -199,6 +233,17 @@ Singleton {
loadBinds(true); loadBinds(true);
} }
function loadCheatsheet(provider) {
if (cheatsheetProcess.running)
return;
const target = provider || cheatsheetProvider;
if (!target)
return;
cheatsheetLoading = true;
cheatsheetProcess.command = ["dms", "keybinds", "show", target];
cheatsheetProcess.running = true;
}
function loadBinds(showLoading) { function loadBinds(showLoading) {
if (loadProcess.running || !available) if (loadProcess.running || !available)
return; return;

View File

@@ -1,5 +1,4 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
@@ -20,6 +19,7 @@ Singleton {
property string distribution: "" property string distribution: ""
property bool distributionSupported: false property bool distributionSupported: false
property string shellVersion: "" property string shellVersion: ""
property string shellCodename: ""
readonly property var archBasedUCSettings: { readonly property var archBasedUCSettings: {
"listUpdatesSettings": { "listUpdatesSettings": {
@@ -34,7 +34,7 @@ Singleton {
"currentVersion": match[2], "currentVersion": match[2],
"newVersion": match[3], "newVersion": match[3],
"description": `${match[1]} ${match[2]} ${match[3]}` "description": `${match[1]} ${match[2]} ${match[3]}`
} };
} }
} }
} }
@@ -56,7 +56,7 @@ Singleton {
"currentVersion": match[2], "currentVersion": match[2],
"newVersion": match[3], "newVersion": match[3],
"description": `${match[1]} ${match[2]} ${match[3]}` "description": `${match[1]} ${match[2]} ${match[3]}`
} };
} }
} }
} }
@@ -78,7 +78,7 @@ Singleton {
"currentVersion": "", "currentVersion": "",
"newVersion": match[2], "newVersion": match[2],
"description": `${match[1]} ${match[2]}` "description": `${match[1]} ${match[2]}`
} };
} }
} }
} }
@@ -100,40 +100,49 @@ Singleton {
command: ["sh", "-c", "cat /etc/os-release | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'"] command: ["sh", "-c", "cat /etc/os-release | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'"]
running: true running: true
onExited: (exitCode) => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {
distribution = stdout.text.trim().toLowerCase() distribution = stdout.text.trim().toLowerCase();
distributionSupported = supportedDistributions.includes(distribution) distributionSupported = supportedDistributions.includes(distribution);
if (distributionSupported) { if (distributionSupported) {
updateFinderDetection.running = true updateFinderDetection.running = true;
pkgManagerDetection.running = true pkgManagerDetection.running = true;
checkForUpdates() checkForUpdates();
} else { } else {
console.warn("SystemUpdate: Unsupported distribution:", distribution) console.warn("SystemUpdate: Unsupported distribution:", distribution);
} }
} else { } else {
console.warn("SystemUpdate: Failed to detect distribution") console.warn("SystemUpdate: Failed to detect distribution");
} }
} }
stdout: StdioCollector {} stdout: StdioCollector {}
Component.onCompleted: { Component.onCompleted: {
versionDetection.running = true versionDetection.running = true;
} }
} }
Process { Process {
id: versionDetection id: versionDetection
command: [ command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -d .git ]; then echo "(git) $(git rev-parse --short HEAD)"; elif [ -f VERSION ]; then cat VERSION; fi`]
"sh", "-c",
`cd "${Quickshell.shellDir}" && if [ -d .git ]; then echo "(git) $(git rev-parse --short HEAD)"; elif [ -f VERSION ]; then cat VERSION; fi`
]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
shellVersion = text.trim() shellVersion = text.trim();
}
}
}
Process {
id: codenameDetection
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f CODENAME ]; then cat CODENAME; fi`]
running: true
stdout: StdioCollector {
onStreamFinished: {
shellCodename = text.trim();
} }
} }
} }
@@ -142,12 +151,12 @@ Singleton {
id: updateFinderDetection id: updateFinderDetection
command: ["sh", "-c", "which checkupdates"] command: ["sh", "-c", "which checkupdates"]
onExited: (exitCode) => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {
const exeFound = stdout.text.trim() const exeFound = stdout.text.trim();
updChecker = exeFound.split('/').pop() updChecker = exeFound.split('/').pop();
} else { } else {
console.warn("SystemUpdate: No update checker found. Will use package manager.") console.warn("SystemUpdate: No update checker found. Will use package manager.");
} }
} }
@@ -158,12 +167,12 @@ Singleton {
id: pkgManagerDetection id: pkgManagerDetection
command: ["sh", "-c", "which paru || which yay || which dnf"] command: ["sh", "-c", "which paru || which yay || which dnf"]
onExited: (exitCode) => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {
const exeFound = stdout.text.trim() const exeFound = stdout.text.trim();
pkgManager = exeFound.split('/').pop() pkgManager = exeFound.split('/').pop();
} else { } else {
console.warn("SystemUpdate: No package manager found") console.warn("SystemUpdate: No package manager found");
} }
} }
@@ -173,19 +182,17 @@ Singleton {
Process { Process {
id: updateChecker id: updateChecker
onExited: (exitCode) => { onExited: exitCode => {
isChecking = false isChecking = false;
const correctExitCodes = updChecker.length > 0 ? const correctExitCodes = updChecker.length > 0 ? [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.correctExitCodes) : [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.correctExitCodes);
[updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.correctExitCodes) :
[pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.correctExitCodes)
if (correctExitCodes.includes(exitCode)) { if (correctExitCodes.includes(exitCode)) {
parseUpdates(stdout.text) parseUpdates(stdout.text);
hasError = false hasError = false;
errorMessage = "" errorMessage = "";
} else { } else {
hasError = true hasError = true;
errorMessage = "Failed to check for updates" errorMessage = "Failed to check for updates";
console.warn("SystemUpdate: Update check failed with code:", exitCode) console.warn("SystemUpdate: Update check failed with code:", exitCode);
} }
} }
@@ -194,67 +201,67 @@ Singleton {
Process { Process {
id: updater id: updater
onExited: (exitCode) => { onExited: exitCode => {
checkForUpdates() checkForUpdates();
} }
} }
function checkForUpdates() { function checkForUpdates() {
if (!distributionSupported || (!pkgManager && !updChecker) || isChecking) return if (!distributionSupported || (!pkgManager && !updChecker) || isChecking)
return;
isChecking = true isChecking = true;
hasError = false hasError = false;
if (updChecker.length > 0) { if (updChecker.length > 0) {
updateChecker.command = [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.params) updateChecker.command = [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.params);
} else { } else {
updateChecker.command = [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.params) updateChecker.command = [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.params);
} }
updateChecker.running = true updateChecker.running = true;
} }
function parseUpdates(output) { function parseUpdates(output) {
const lines = output.trim().split('\n').filter(line => line.trim()) const lines = output.trim().split('\n').filter(line => line.trim());
const updates = [] const updates = [];
const regex = packageManagerParams[pkgManager].parserSettings.lineRegex const regex = packageManagerParams[pkgManager].parserSettings.lineRegex;
const entryProducer = packageManagerParams[pkgManager].parserSettings.entryProducer const entryProducer = packageManagerParams[pkgManager].parserSettings.entryProducer;
for (const line of lines) { for (const line of lines) {
const match = line.match(regex) const match = line.match(regex);
if (match) { if (match) {
updates.push(entryProducer(match)) updates.push(entryProducer(match));
} }
} }
availableUpdates = updates availableUpdates = updates;
} }
function runUpdates() { function runUpdates() {
if (!distributionSupported || !pkgManager || updateCount === 0) return if (!distributionSupported || !pkgManager || updateCount === 0)
return;
const terminal = Quickshell.env("TERMINAL") || "xterm" const terminal = Quickshell.env("TERMINAL") || "xterm";
if (SettingsData.updaterUseCustomCommand && SettingsData.updaterCustomCommand.length > 0) { if (SettingsData.updaterUseCustomCommand && SettingsData.updaterCustomCommand.length > 0) {
const updateCommand = `${SettingsData.updaterCustomCommand} && echo "Updates complete! Press Enter to close..." && read` const updateCommand = `${SettingsData.updaterCustomCommand} && echo "Updates complete! Press Enter to close..." && read`;
const termClass = SettingsData.updaterTerminalAdditionalParams const termClass = SettingsData.updaterTerminalAdditionalParams;
var finalCommand = [terminal] var finalCommand = [terminal];
if (termClass.length > 0) { if (termClass.length > 0) {
finalCommand = finalCommand.concat(termClass.split(" ")) finalCommand = finalCommand.concat(termClass.split(" "));
} }
finalCommand.push("-e") finalCommand.push("-e");
finalCommand.push("sh") finalCommand.push("sh");
finalCommand.push("-c") finalCommand.push("-c");
finalCommand.push(updateCommand) finalCommand.push(updateCommand);
updater.command = finalCommand updater.command = finalCommand;
} else { } else {
const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ") const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ");
const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : "" const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : "";
const updateCommand = `${sudo} ${pkgManager} ${params} && echo "Updates complete! Press Enter to close..." && read` const updateCommand = `${sudo} ${pkgManager} ${params} && echo "Updates complete! Press Enter to close..." && read`;
updater.command = [terminal, "-e", "sh", "-c", updateCommand] updater.command = [terminal, "-e", "sh", "-c", updateCommand];
} }
updater.running = true updater.running = true;
} }
Timer { Timer {
@@ -269,16 +276,16 @@ Singleton {
function updatestatus(): string { function updatestatus(): string {
if (root.isChecking) { if (root.isChecking) {
return "ERROR: already checking" return "ERROR: already checking";
} }
if (!distributionSupported) { if (!distributionSupported) {
return "ERROR: distribution not supported" return "ERROR: distribution not supported";
} }
if (!pkgManager && !updChecker) { if (!pkgManager && !updChecker) {
return "ERROR: update checker not available" return "ERROR: update checker not available";
} }
root.checkForUpdates() root.checkForUpdates();
return "SUCCESS: Now checking..." return "SUCCESS: Now checking...";
} }
} }
} }