1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-30 08:22:51 -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
- **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)
- **Plugins:** [Development guide](https://danklinux.com/docs/dankmaterialshell/plugins-overview)
- **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")
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 {
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,
"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 {
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),
}
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)
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)
@@ -471,7 +471,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
}
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 {
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, ", ")))
args := []string{"apt-get", "install", "-y"}
args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
args = append(args, packages...)
progressChan <- InstallProgressMsg{
@@ -616,7 +616,7 @@ func (d *DebianDistribution) installRust(ctx context.Context, sudoPassword strin
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 {
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",
}
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)
}

View File

@@ -476,7 +476,7 @@ func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Pac
cmd := ExecSudoCommand(ctx, sudoPassword,
fmt.Sprintf("zypper addrepo -f %s", repoURL))
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

View File

@@ -562,53 +562,62 @@ func (m *Manager) transitionWorker() {
case <-m.stopChan:
return
case targetTemp := <-m.transitionChan:
m.transitionMutex.Lock()
currentTemp := m.currentTemp
m.targetTemp = targetTemp
m.transitionMutex.Unlock()
m.runTransition(targetTemp, steps, stepDur)
}
}
}
if currentTemp == targetTemp {
continue
func (m *Manager) runTransition(targetTemp int, steps int, stepDur time.Duration) {
for {
m.transitionMutex.Lock()
currentTemp := m.currentTemp
m.targetTemp = targetTemp
m.transitionMutex.Unlock()
if currentTemp == targetTemp {
return
}
log.Debugf("Starting smooth transition: %dK -> %dK over %v", currentTemp, targetTemp, stepDur*time.Duration(steps))
redirected := false
for i := 0; i <= steps; i++ {
select {
case newTarget := <-m.transitionChan:
m.transitionMutex.Lock()
m.targetTemp = newTarget
m.transitionMutex.Unlock()
if newTarget != targetTemp {
log.Debugf("Transition %dK -> %dK redirected to %dK", currentTemp, targetTemp, newTarget)
targetTemp = newTarget
redirected = true
}
default:
}
log.Debugf("Starting smooth transition: %dK -> %dK over %v", currentTemp, targetTemp, dur)
stepLoop:
for i := 0; i <= steps; i++ {
select {
case newTarget := <-m.transitionChan:
m.transitionMutex.Lock()
m.targetTemp = newTarget
m.transitionMutex.Unlock()
log.Debugf("Transition %dK -> %dK aborted (newer transition started)", currentTemp, targetTemp)
break stepLoop
default:
}
m.transitionMutex.RLock()
if m.targetTemp != targetTemp {
m.transitionMutex.RUnlock()
break
}
m.transitionMutex.RUnlock()
progress := float64(i) / float64(steps)
temp := currentTemp + int(float64(targetTemp-currentTemp)*progress)
m.post(func() { m.applyNowOnActor(temp) })
if i < steps {
time.Sleep(stepDur)
}
if redirected {
break
}
m.transitionMutex.RLock()
finalTarget := m.targetTemp
currentTarget := m.targetTemp
m.transitionMutex.RUnlock()
if finalTarget == targetTemp {
log.Debugf("Transition complete: now at %dK", targetTemp)
if currentTarget != targetTemp {
targetTemp = currentTarget
break
}
progress := float64(i) / float64(steps)
temp := currentTemp + int(float64(targetTemp-currentTemp)*progress)
m.post(func() { m.applyNowOnActor(temp) })
if i == steps {
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) {
m.configMutex.Lock()
wasEnabled := m.config.Enabled
m.config.Enabled = enabled
highTemp := m.config.HighTemp
m.configMutex.Unlock()
if enabled {
if !m.controlsInitialized {
m.transitionMutex.Lock()
m.currentTemp = highTemp
m.targetTemp = highTemp
m.transitionMutex.Unlock()
m.post(func() {
log.Info("Creating gamma controls")
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)
} else {
m.controlsInitialized = true
m.triggerUpdate()
}
})
} else {
} else if !wasEnabled {
m.triggerUpdate()
}
} else {

View File

@@ -10,13 +10,14 @@
user = config.services.greetd.settings.default_session.user;
cacheDir = "/var/lib/dms-greeter";
greeterScript = pkgs.writeShellScriptBin "dms-greeter" ''
export PATH=$PATH:${lib.makeBinPath [cfg.quickshell.package config.programs.${cfg.compositor.name}.package]}
${lib.escapeShellArgs ([
"sh"
"${../../quickshell/Modules/Greetd/assets/dms-greeter}"
"--cache-dir"
"/var/lib/dmsgreeter"
cacheDir
"--command"
cfg.compositor.name
"-p"
@@ -27,6 +28,8 @@
"${pkgs.writeText "dmsgreeter-compositor-config" cfg.compositor.customConfig}"
])} ${lib.optionalString cfg.logs.save "> ${cfg.logs.path} 2>&1"}
'';
jq = lib.getExe pkgs.jq;
in {
imports = let
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
];
systemd.tmpfiles.settings."10-dmsgreeter" = {
"/var/lib/dmsgreeter".d = {
${cacheDir}.d = {
user = user;
group =
if config.users.users.${user}.group != ""
then config.users.users.${user}.group
else "greeter";
mode = "0755";
mode = "0750";
};
};
systemd.services.greetd.preStart = ''
cd /var/lib/dmsgreeter
cd ${cacheDir}
${lib.concatStringsSep "\n" (lib.map (f: ''
if [ -f "${f}" ]; then
cp "${f}" .
@@ -112,9 +115,16 @@ in {
cfg.configFiles)}
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
${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

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")
return "homeassistant-desktop";
if (appId.includes("com.transmissionbt.transmission"))
return "transmission-gtk";
return "transmission";
return appId;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,6 @@ import QtQuick.Effects
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
Item {
id: aboutTab
@@ -200,6 +199,16 @@ Item {
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 {
id: resourceButtonsRow
anchors.horizontalCenter: parent.horizontalCenter

View File

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

View File

@@ -22,6 +22,18 @@ Singleton {
property bool available: CompositorService.isNiri && shortcutInhibitorAvailable
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 saving: false
property bool fixing: false
@@ -58,17 +70,14 @@ Singleton {
signal bindSaveCompleted(bool success)
signal bindRemoved(string key)
signal dmsBindsFixed
Component.onCompleted: {
if (available)
Qt.callLater(loadBinds);
}
signal cheatsheetLoaded
Connections {
target: CompositorService
function onCompositorChanged() {
if (CompositorService.isNiri)
Qt.callLater(root.loadBinds);
if (!CompositorService.isNiri)
return;
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 {
id: loadProcess
running: false
@@ -199,6 +233,17 @@ Singleton {
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) {
if (loadProcess.running || !available)
return;

View File

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