1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-15 00:32:47 -04:00

Compare commits

...

18 Commits

Author SHA1 Message Date
bbedward 26c1e62204 fix(dankbar): use ID as tie breaker 2026-03-10 11:48:33 -04:00
purian23 7b2d4dbe30 dankinstall: Update Arch/Quickshell installation 2026-03-10 11:05:25 -04:00
CaptainSpof 78c5d46c6b fix(wallpaper): follow symlinks when scanning wallpaper directory (#1947) 2026-03-10 11:05:25 -04:00
purian23 3fb85df504 fix(Clipboard) remove unused copyServe logic 2026-03-10 11:05:00 -04:00
micko 227dd24726 update deprecated syntax (#1928) 2026-03-10 11:05:00 -04:00
purian23 ae6a656899 fix(Clipboard): Epic RAM Growth - Closes #1920 2026-03-10 11:05:00 -04:00
Connor Welsh a4055e0f01 fix(Calendar): add missing qs.Common import (#1926)
fixes calendar events getting dropped
2026-03-10 11:05:00 -04:00
Lucas 6d98c229ef flake: allow extra QT packages in dms-shell package (#1903) 2026-03-10 11:04:01 -04:00
Michael Erdely 71d93ad85e Not everyone uses paru or yay on Arch: Support pacman command (#1900)
* Not everyone uses paru or yay on Arch: Support pacman command
* Handle sudo properly when using pacman
* Move pacman to bottom per Purian23
* Remote duplicate which -- thanks Purian23!
2026-03-10 11:04:01 -04:00
Triệu Kha 4ec21fcd3d fix(dock): Dock flickering when having cursor floating by the side (#1897) 2026-03-10 11:04:01 -04:00
Lucas 0a2fe03fee ipc: update DankBar selection (#1894)
* ipc: update DankBar selection

* ipc: use getPreferredBar in dash open

* ipc: don't toggle dash on dash open
2026-03-10 11:04:01 -04:00
Triệu Kha 4f4745609b fix(osd): play/pause icon flipped in MediaPlaybackOSD (#1889) 2026-03-10 11:03:23 -04:00
purian23 a69cd515fb fix(dbar): Fixes autohide + click through edge case 2026-03-10 11:03:23 -04:00
purian23 06c4b97a6b fix(notifications): Allow duplicate history entry management w/unique IDs & source tracking 2026-03-10 11:03:23 -04:00
purian23 a6cf71a190 fix(notifications): Apply appIdSubs to iconFrImage fallback path - Consistent with the appIcon PR changes in #1880. 2026-03-10 11:01:34 -04:00
odt 21750156dc refactor(icons): centralize icon resolution into Paths.resolveIconPath/resolveIconUrl (#1880)
Supersedes #1878. Rather than duplicating the moddedAppId + file path
substitution pattern inline across 8 files, this introduces two
centralized functions in Paths.qml:

- resolveIconPath(iconName): for Quickshell.iconPath() callsites,
  with DesktopService.resolveIconPath() fallback
- resolveIconUrl(iconName): for image://icon/ URL callsites

All consumer files now use one-line calls. When no substitutions are
configured, moddedAppId() returns the original name unchanged (zero
cost), so this has no impact on users who don't use the feature.

Affected components:
- AppIconRenderer (8 lines → 1)
- NotificationCard, NotificationPopup, HistoryNotificationCard
- DockContextMenu, AppsDockContextMenu
- LauncherContent, LauncherTab (×3)

Co-authored-by: odtgit <odtgit@taliops.com>
2026-03-10 11:00:57 -04:00
supposede f9b737f543 Update toolbar button styles with primary color (#1879) 2026-03-10 10:55:58 -04:00
odt 246b59f3b9 fix(icons): apply file path substitutions in launcher icon resolution (#1877)
Follow-up to #1867. The launcher's AppIconRenderer used its own
Quickshell.iconPath() call without going through appIdSubstitutions,
so PWA icons configured via regex file path rules were not resolved
in the app launcher.

Co-authored-by: odtgit <odtgit@taliops.com>
2026-03-10 10:55:53 -04:00
24 changed files with 405 additions and 202 deletions
+11 -1
View File
@@ -222,16 +222,19 @@ func init() {
func runClipCopy(cmd *cobra.Command, args []string) {
var data []byte
copyFromStdin := false
switch {
case len(args) > 0:
data = []byte(args[0])
default:
case clipCopyDownload || clipCopyType == "__multi__":
var err error
data, err = io.ReadAll(os.Stdin)
if err != nil {
log.Fatalf("read stdin: %v", err)
}
default:
copyFromStdin = true
}
if clipCopyDownload {
@@ -257,6 +260,13 @@ func runClipCopy(cmd *cobra.Command, args []string) {
return
}
if copyFromStdin {
if err := clipboard.CopyReader(os.Stdin, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
log.Fatalf("copy: %v", err)
}
return
}
if err := clipboard.CopyOpts(data, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
log.Fatalf("copy: %v", err)
}
+92 -8
View File
@@ -1,10 +1,12 @@
package clipboard
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"syscall"
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control"
@@ -12,17 +14,37 @@ import (
)
func Copy(data []byte, mimeType string) error {
return CopyOpts(data, mimeType, false, false)
return CopyReader(bytes.NewReader(data), mimeType, false, false)
}
func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
if foreground {
return copyServeWithWriter(func(writer io.Writer) error {
total := 0
for total < len(data) {
n, err := writer.Write(data[total:])
total += n
if err != nil {
return err
}
}
if total != len(data) {
return io.ErrShortWrite
}
return nil
}, mimeType, pasteOnce)
}
return CopyReader(bytes.NewReader(data), mimeType, foreground, pasteOnce)
}
func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error {
if !foreground {
return copyFork(data, mimeType, pasteOnce)
}
return copyServe(data, mimeType, pasteOnce)
return copyServeReader(data, mimeType, pasteOnce)
}
func copyFork(data []byte, mimeType string, pasteOnce bool) error {
func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
args := []string{os.Args[0], "cl", "copy", "--foreground"}
if pasteOnce {
args = append(args, "--paste-once")
@@ -30,11 +52,15 @@ func copyFork(data []byte, mimeType string, pasteOnce bool) error {
args = append(args, "--type", mimeType)
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = nil
cmd.Stdout = nil
cmd.Stderr = nil
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
if stdinSource, ok := data.(*os.File); ok {
cmd.Stdin = stdinSource
return cmd.Start()
}
stdin, err := cmd.StdinPipe()
if err != nil {
return fmt.Errorf("stdin pipe: %w", err)
@@ -44,16 +70,66 @@ func copyFork(data []byte, mimeType string, pasteOnce bool) error {
return fmt.Errorf("start: %w", err)
}
if _, err := stdin.Write(data); err != nil {
if _, err := io.Copy(stdin, data); err != nil {
stdin.Close()
return fmt.Errorf("write stdin: %w", err)
}
stdin.Close()
if err := stdin.Close(); err != nil {
return fmt.Errorf("close stdin: %w", err)
}
return nil
}
func copyServe(data []byte, mimeType string, pasteOnce bool) error {
func copyServeReader(data io.Reader, mimeType string, pasteOnce bool) error {
cachedData, err := createClipboardCacheFile()
if err != nil {
return fmt.Errorf("create clipboard cache file: %w", err)
}
defer os.Remove(cachedData.Name())
if _, err := io.Copy(cachedData, data); err != nil {
return fmt.Errorf("cache clipboard data: %w", err)
}
if err := cachedData.Close(); err != nil {
return fmt.Errorf("close temp cache file: %w", err)
}
return copyServeWithWriter(func(writer io.Writer) error {
cachedFile, err := os.Open(cachedData.Name())
if err != nil {
return fmt.Errorf("open temp cache file: %w", err)
}
defer cachedFile.Close()
if _, err := io.Copy(writer, cachedFile); err != nil {
return fmt.Errorf("write clipboard data: %w", err)
}
return nil
}, mimeType, pasteOnce)
}
func createClipboardCacheFile() (*os.File, error) {
preferredDirs := []string{}
if cacheDir, err := os.UserCacheDir(); err == nil {
preferredDirs = append(preferredDirs, filepath.Join(cacheDir, "dms", "clipboard"))
}
preferredDirs = append(preferredDirs, "/var/tmp/dms/clipboard")
for _, dir := range preferredDirs {
if err := os.MkdirAll(dir, 0o700); err != nil {
continue
}
cachedData, err := os.CreateTemp(dir, "dms-clipboard-*")
if err == nil {
return cachedData, nil
}
}
return os.CreateTemp("", "dms-clipboard-*")
}
func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOnce bool) error {
display, err := wlclient.Connect("")
if err != nil {
return fmt.Errorf("wayland connect: %w", err)
@@ -139,12 +215,18 @@ func copyServe(data []byte, mimeType string, pasteOnce bool) error {
cancelled := make(chan struct{})
pasted := make(chan struct{}, 1)
sendErr := make(chan error, 1)
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
defer syscall.Close(e.Fd)
file := os.NewFile(uintptr(e.Fd), "pipe")
defer file.Close()
file.Write(data)
if err := writeTo(file); err != nil {
select {
case sendErr <- err:
default:
}
}
select {
case pasted <- struct{}{}:
default:
@@ -165,6 +247,8 @@ func copyServe(data []byte, mimeType string, pasteOnce bool) error {
select {
case <-cancelled:
return nil
case err := <-sendErr:
return err
case <-pasted:
if pasteOnce {
return nil
+18 -39
View File
@@ -440,29 +440,10 @@ func (a *ArchDistribution) installAURPackages(ctx context.Context, packages []st
a.log(fmt.Sprintf("Installing AUR packages manually: %s", strings.Join(packages, ", ")))
hasNiri := false
hasQuickshell := false
for _, pkg := range packages {
if pkg == "niri-git" {
hasNiri = true
}
if pkg == "quickshell" || pkg == "quickshell-git" {
hasQuickshell = true
}
}
// If quickshell is in the list, always reinstall google-breakpad first
if hasQuickshell {
progressChan <- InstallProgressMsg{
Phase: PhaseAURPackages,
Progress: 0.63,
Step: "Reinstalling google-breakpad for quickshell...",
IsComplete: false,
CommandInfo: "Reinstalling prerequisite AUR package for quickshell",
}
if err := a.installSingleAURPackage(ctx, "google-breakpad", sudoPassword, progressChan, 0.63, 0.65); err != nil {
return fmt.Errorf("failed to reinstall google-breakpad prerequisite for quickshell: %w", err)
}
}
// If niri is in the list, install makepkg-git-lfs-proto first if not already installed
@@ -616,10 +597,16 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
return fmt.Errorf("failed to remove optdepends from .SRCINFO for %s: %w", pkg, err)
}
// Skip dependency installation for dms-shell-git and dms-shell-bin
// since we manually manage those dependencies
if pkg != "dms-shell-git" && pkg != "dms-shell-bin" {
// Pre-install dependencies from .SRCINFO
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
if pkg == "dms-shell-bin" {
progressChan <- InstallProgressMsg{
Phase: PhaseAURPackages,
Progress: startProgress + 0.35*(endProgress-startProgress),
Step: fmt.Sprintf("Skipping dependency installation for %s (manually managed)...", pkg),
IsComplete: false,
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
}
} else {
progressChan <- InstallProgressMsg{
Phase: PhaseAURPackages,
Progress: startProgress + 0.3*(endProgress-startProgress),
@@ -628,19 +615,19 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
CommandInfo: "Installing package dependencies and makedepends",
}
// Install dependencies and makedepends explicitly
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
// Install dependencies from .SRCINFO
depFilter := ""
if pkg == "dms-shell-git" {
depFilter = ` | sed -E 's/[[:space:]]*(quickshell|dgop)[[:space:]]*/ /g' | tr -s ' '`
}
depsCmd := exec.CommandContext(ctx, "bash", "-c",
fmt.Sprintf(`
deps=$(grep "depends = " "%s" | grep -v "makedepends" | sed 's/.*depends = //' | tr '\n' ' ' | sed 's/[[:space:]]*$//')
if [[ "%s" == *"quickshell"* ]]; then
deps=$(echo "$deps" | sed 's/google-breakpad//g' | sed 's/ / /g' | sed 's/^ *//g' | sed 's/ *$//g')
fi
deps=$(grep "depends = " "%s" | grep -v "makedepends" | sed 's/.*depends = //' | tr '\n' ' ' %s | sed 's/[[:space:]]*$//')
if [ ! -z "$deps" ] && [ "$deps" != " " ]; then
echo '%s' | sudo -S pacman -S --needed --noconfirm $deps
fi
`, srcinfoPath, pkg, sudoPassword))
`, srcinfoPath, depFilter, sudoPassword))
if err := a.runWithProgress(depsCmd, progressChan, PhaseAURPackages, startProgress+0.3*(endProgress-startProgress), startProgress+0.35*(endProgress-startProgress)); err != nil {
return fmt.Errorf("FAILED to install runtime dependencies for %s: %w", pkg, err)
@@ -657,14 +644,6 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
if err := a.runWithProgress(makedepsCmd, progressChan, PhaseAURPackages, startProgress+0.35*(endProgress-startProgress), startProgress+0.4*(endProgress-startProgress)); err != nil {
return fmt.Errorf("FAILED to install make dependencies for %s: %w", pkg, err)
}
} else {
progressChan <- InstallProgressMsg{
Phase: PhaseAURPackages,
Progress: startProgress + 0.35*(endProgress-startProgress),
Step: fmt.Sprintf("Skipping dependency installation for %s (manually managed)...", pkg),
IsComplete: false,
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
}
}
progressChan <- InstallProgressMsg{
@@ -677,7 +656,7 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
buildCmd := exec.CommandContext(ctx, "makepkg", "--noconfirm")
buildCmd.Dir = packageDir
buildCmd.Env = append(os.Environ(), "PKGEXT=.pkg.tar") // Disable compression for speed
buildCmd.Env = append(os.Environ(), "PKGEXT=.pkg.tar")
if err := a.runWithProgress(buildCmd, progressChan, PhaseAURPackages, startProgress+0.4*(endProgress-startProgress), startProgress+0.7*(endProgress-startProgress)); err != nil {
return fmt.Errorf("failed to build %s: %w", pkg, err)
+62 -56
View File
@@ -72,76 +72,82 @@
"${cleanVersion}${dateSuffix}${revSuffix}";
in
{
dms-shell = pkgs.buildGoModule (
let
rootSrc = ./.;
in
dms-shell = pkgs.lib.makeOverridable (
{
inherit version;
pname = "dms-shell";
src = ./core;
vendorHash = "sha256-dEk7IOd6aQwaxZruxQclN7TGMyb8EJOl6NBWRsoZ9HQ=";
extraQtPackages ? [ ],
}:
pkgs.buildGoModule (
let
rootSrc = ./.;
qtPackages = (qmlPkgs pkgs) ++ extraQtPackages;
in
{
inherit version;
pname = "dms-shell";
src = ./core;
vendorHash = "sha256-dEk7IOd6aQwaxZruxQclN7TGMyb8EJOl6NBWRsoZ9HQ=";
subPackages = [ "cmd/dms" ];
subPackages = [ "cmd/dms" ];
ldflags = [
"-s"
"-w"
"-X 'main.Version=${version}'"
];
ldflags = [
"-s"
"-w"
"-X 'main.Version=${version}'"
];
nativeBuildInputs = with pkgs; [
installShellFiles
makeWrapper
];
nativeBuildInputs = with pkgs; [
installShellFiles
makeWrapper
];
postInstall = ''
mkdir -p $out/share/quickshell/dms
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
postInstall = ''
mkdir -p $out/share/quickshell/dms
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
chmod u+w $out/share/quickshell/dms/VERSION
echo "${version}" > $out/share/quickshell/dms/VERSION
chmod u+w $out/share/quickshell/dms/VERSION
echo "${version}" > $out/share/quickshell/dms/VERSION
# Install desktop file and icon
install -D ${rootSrc}/assets/dms-open.desktop \
$out/share/applications/dms-open.desktop
install -D ${rootSrc}/core/assets/danklogo.svg \
$out/share/hicolor/scalable/apps/danklogo.svg
# Install desktop file and icon
install -D ${rootSrc}/assets/dms-open.desktop \
$out/share/applications/dms-open.desktop
install -D ${rootSrc}/core/assets/danklogo.svg \
$out/share/hicolor/scalable/apps/danklogo.svg
wrapProgram $out/bin/dms \
--add-flags "-c $out/share/quickshell/dms" \
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs (qmlPkgs pkgs)}" \
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs (qmlPkgs pkgs)}"
wrapProgram $out/bin/dms \
--add-flags "-c $out/share/quickshell/dms" \
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs qtPackages}" \
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs qtPackages}"
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
$out/lib/systemd/user/dms.service
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
$out/lib/systemd/user/dms.service
substituteInPlace $out/lib/systemd/user/dms.service \
--replace-fail /usr/bin/dms $out/bin/dms \
--replace-fail /usr/bin/pkill ${pkgs.procps}/bin/pkill
substituteInPlace $out/lib/systemd/user/dms.service \
--replace-fail /usr/bin/dms $out/bin/dms \
--replace-fail /usr/bin/pkill ${pkgs.procps}/bin/pkill
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
substituteInPlace $out/share/quickshell/dms/assets/pam/fprint \
--replace-fail pam_fprintd.so ${pkgs.fprintd}/lib/security/pam_fprintd.so
substituteInPlace $out/share/quickshell/dms/assets/pam/fprint \
--replace-fail pam_fprintd.so ${pkgs.fprintd}/lib/security/pam_fprintd.so
installShellCompletion --cmd dms \
--bash <($out/bin/dms completion bash) \
--fish <($out/bin/dms completion fish) \
--zsh <($out/bin/dms completion zsh)
'';
installShellCompletion --cmd dms \
--bash <($out/bin/dms completion bash) \
--fish <($out/bin/dms completion fish) \
--zsh <($out/bin/dms completion zsh)
'';
meta = {
description = "Desktop shell for wayland compositors built with Quickshell & GO";
homepage = "https://danklinux.com";
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
license = pkgs.lib.licenses.mit;
mainProgram = "dms";
platforms = pkgs.lib.platforms.linux;
};
}
);
meta = {
description = "Desktop shell for wayland compositors built with Quickshell & GO";
homepage = "https://danklinux.com";
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
license = pkgs.lib.licenses.mit;
mainProgram = "dms";
platforms = pkgs.lib.platforms.linux;
};
}
)
) { };
quickshell = quickshell.packages.${system}.default;
+28 -3
View File
@@ -71,15 +71,40 @@ Singleton {
return appId;
}
function resolveIconPath(iconName: string): string {
if (!iconName) return "";
const moddedId = moddedAppId(iconName);
if (moddedId !== iconName) {
if (moddedId.startsWith("~") || moddedId.startsWith("/"))
return toFileUrl(expandTilde(moddedId));
if (moddedId.startsWith("file://"))
return moddedId;
return Quickshell.iconPath(moddedId, true);
}
return Quickshell.iconPath(iconName, true) || DesktopService.resolveIconPath(iconName);
}
function resolveIconUrl(iconName: string): string {
if (!iconName) return "";
const moddedId = moddedAppId(iconName);
if (moddedId !== iconName) {
if (moddedId.startsWith("~") || moddedId.startsWith("/"))
return toFileUrl(expandTilde(moddedId));
if (moddedId.startsWith("file://"))
return moddedId;
return "image://icon/" + moddedId;
}
return "image://icon/" + iconName;
}
function getAppIcon(appId: string, desktopEntry: var): string {
if (appId === "org.quickshell") {
return Qt.resolvedUrl("../assets/danklogo.svg");
}
const moddedId = moddedAppId(appId);
if (moddedId !== appId) {
return Quickshell.iconPath(moddedId, true);
}
if (moddedId !== appId)
return resolveIconPath(appId);
if (desktopEntry && desktopEntry.icon) {
return Quickshell.iconPath(desktopEntry.icon, true);
+5 -5
View File
@@ -154,18 +154,18 @@ Item {
property string _barLayoutStateJson: {
const configs = SettingsData.barConfigs;
const mapped = configs.map(c => ({
const mapped = configs.map((c, i) => ({
id: c.id,
position: c.position,
autoHide: c.autoHide,
visible: c.visible
visible: c.visible,
_origIndex: i
})).sort((a, b) => {
const aVertical = a.position === SettingsData.Position.Left || a.position === SettingsData.Position.Right;
const bVertical = b.position === SettingsData.Position.Left || b.position === SettingsData.Position.Right;
if (aVertical !== bVertical) {
if (aVertical !== bVertical)
return aVertical - bVertical;
}
return String(a.id).localeCompare(String(b.id));
return a._origIndex - b._origIndex;
});
return JSON.stringify(mapped);
}
+79 -31
View File
@@ -21,11 +21,37 @@ Item {
required property var workspaceRenameModalLoader
required property var windowRuleModalLoader
function getFirstBar() {
function getPreferredBar(refPropertyName) {
if (!root.dankBarRepeater || root.dankBarRepeater.count === 0)
return null;
const firstLoader = root.dankBarRepeater.itemAt(0);
return firstLoader ? firstLoader.item : null;
const focusedScreenName = BarWidgetService.getFocusedScreenName();
const loaders = Array.from({
length: root.dankBarRepeater.count
}, (_, i) => root.dankBarRepeater.itemAt(i));
let currentBar = null;
for (const loader of loaders) {
const instances = loader?.item?.barVariants?.instances || [];
for (const bar of instances) {
if (!bar)
continue;
const onFocusedScreen = focusedScreenName && bar.modelData?.name === focusedScreenName;
const hasRef = !refPropertyName || !!bar[refPropertyName];
if (hasRef) {
currentBar = bar;
if (onFocusedScreen)
break;
}
}
}
return currentBar;
}
IpcHandler {
@@ -97,9 +123,9 @@ Item {
IpcHandler {
function open(): string {
const bar = root.getFirstBar();
const bar = root.getPreferredBar("controlCenterButtonRef");
if (bar) {
bar.triggerControlCenterOnFocusedScreen();
bar.triggerControlCenter();
return "CONTROL_CENTER_OPEN_SUCCESS";
}
return "CONTROL_CENTER_OPEN_FAILED";
@@ -114,9 +140,14 @@ Item {
}
function toggle(): string {
const bar = root.getFirstBar();
if (root.controlCenterLoader.item?.shouldBeVisible) {
root.controlCenterLoader.item.close();
return "CONTROL_CENTER_TOGGLE_SUCCESS";
}
const bar = root.getPreferredBar("controlCenterButtonRef");
if (bar) {
bar.triggerControlCenterOnFocusedScreen();
bar.triggerControlCenter();
return "CONTROL_CENTER_TOGGLE_SUCCESS";
}
return "CONTROL_CENTER_TOGGLE_FAILED";
@@ -131,27 +162,37 @@ Item {
IpcHandler {
function open(tab: string): string {
root.dankDashPopoutLoader.active = true;
if (root.dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1;
break;
case "wallpaper":
root.dankDashPopoutLoader.item.currentTabIndex = 2;
break;
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 3 : 0;
break;
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0;
break;
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen);
root.dankDashPopoutLoader.item.dashVisible = true;
return "DASH_OPEN_SUCCESS";
const bar = root.getPreferredBar("clockButtonRef");
if (!bar)
return "DASH_OPEN_FAILED";
const dash = root.dankDashPopoutLoader.item;
const onSameScreen = dash && dash.shouldBeVisible && dash.triggerScreen?.name === bar.screen?.name;
if (!onSameScreen) {
bar.triggerWallpaperBrowser();
}
return "DASH_OPEN_FAILED";
if (!root.dankDashPopoutLoader.item)
return "DASH_OPEN_FAILED";
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1;
break;
case "wallpaper":
root.dankDashPopoutLoader.item.currentTabIndex = 2;
break;
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 3 : 0;
break;
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0;
break;
}
root.dankDashPopoutLoader.item.dashVisible = true;
return "DASH_OPEN_SUCCESS";
}
function close(): string {
@@ -163,8 +204,14 @@ Item {
}
function toggle(tab: string): string {
const bar = root.getFirstBar();
if (bar && bar.triggerWallpaperBrowserOnFocusedScreen()) {
if (root.dankDashPopoutLoader.item?.dashVisible) {
root.dankDashPopoutLoader.item.dashVisible = false;
return "DASH_TOGGLE_SUCCESS";
}
const bar = root.getPreferredBar("clockButtonRef");
if (bar) {
bar.triggerWallpaperBrowser();
if (root.dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
@@ -521,8 +568,9 @@ Item {
IpcHandler {
function wallpaper(): string {
const bar = root.getFirstBar();
if (bar && bar.triggerWallpaperBrowserOnFocusedScreen()) {
const bar = root.getPreferredBar("clockButtonRef");
if (bar) {
bar.triggerWallpaperBrowser();
return "SUCCESS: Toggled wallpaper browser";
}
return "ERROR: Failed to toggle wallpaper browser";
@@ -643,7 +643,7 @@ FocusScope {
Image {
width: 40
height: 40
source: editingApp?.icon ? "image://icon/" + editingApp.icon : "image://icon/application-x-executable"
source: Paths.resolveIconUrl(editingApp?.icon || "application-x-executable")
sourceSize.width: 40
sourceSize.height: 40
fillMode: Image.PreserveAspectFit
+14 -6
View File
@@ -552,8 +552,9 @@ PanelWindow {
readonly property var _leftSection: topBarContent ? (barWindow.isVertical ? topBarContent.vLeftSection : topBarContent.hLeftSection) : null
readonly property var _centerSection: topBarContent ? (barWindow.isVertical ? topBarContent.vCenterSection : topBarContent.hCenterSection) : null
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : null
readonly property real _revealProgress: topBarSlide.x + topBarSlide.y
function sectionRect(section, isCenter) {
function sectionRect(section, isCenter, _dep) {
if (!section)
return {
"x": 0,
@@ -582,7 +583,7 @@ PanelWindow {
item: clickThroughEnabled ? null : inputMask
Region {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._leftSection, false) : {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._leftSection, false, barWindow._revealProgress) : {
"x": 0,
"y": 0,
"w": 0,
@@ -595,7 +596,7 @@ PanelWindow {
}
Region {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._centerSection, true) : {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._centerSection, true, barWindow._revealProgress) : {
"x": 0,
"y": 0,
"w": 0,
@@ -608,7 +609,7 @@ PanelWindow {
}
Region {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._rightSection, false) : {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._rightSection, false, barWindow._revealProgress) : {
"x": 0,
"y": 0,
"w": 0,
@@ -619,6 +620,14 @@ PanelWindow {
width: r.w
height: r.h
}
Region {
readonly property bool active: barWindow.clickThroughEnabled && !inputMask.showing
x: active ? inputMask.x : 0
y: active ? inputMask.y : 0
width: active ? inputMask.width : 0
height: active ? inputMask.height : 0
}
}
Item {
@@ -631,7 +640,7 @@ PanelWindow {
Timer {
id: revealHold
interval: barConfig?.autoHideDelay ?? 250
interval: barWindow.clickThroughEnabled ? Math.max((barConfig?.autoHideDelay ?? 250) * 6, 1500) : (barConfig?.autoHideDelay ?? 250)
repeat: false
onTriggered: {
if (!topBarMouseArea.containsMouse && !topBarCore.hasActivePopout) {
@@ -689,7 +698,6 @@ PanelWindow {
Connections {
function onBarConfigChanged() {
topBarCore.autoHide = barConfig?.autoHide ?? false;
revealHold.interval = barConfig?.autoHideDelay ?? 250;
}
target: rootWindow
@@ -273,7 +273,7 @@ PanelWindow {
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
source: modelData.icon ? Paths.resolveIconPath(modelData.icon) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
+3 -5
View File
@@ -447,9 +447,8 @@ Variants {
height: {
if (dock.isVertical) {
if (!dock.reveal)
return Math.min(Math.max(dockBackground.height + 64, 200), screenHeight * 0.5);
return Math.min(dockBackground.height + 8 + dock.borderThickness, maxDockHeight);
// Keep the taller hit area regardless of the reveal state to prevent shrinking loop
return Math.min(Math.max(dockBackground.height + 64, 200), screenHeight * 0.5);
}
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin) : 1;
}
@@ -457,8 +456,7 @@ Variants {
if (dock.isVertical) {
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin) : 1;
}
if (!dock.reveal)
return Math.min(Math.max(dockBackground.width + 64, 200), screenWidth * 0.5);
// Keep the wider hit area regardless of the reveal state to prevent shrinking loop
return Math.min(dockBackground.width + 8 + dock.borderThickness, maxDockWidth);
}
anchors {
+1 -1
View File
@@ -329,7 +329,7 @@ PanelWindow {
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
source: modelData.icon ? Paths.resolveIconPath(modelData.icon) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
@@ -122,12 +122,12 @@ Rectangle {
return "";
const appIcon = historyItem.appIcon;
if (!appIcon)
return iconFromImage ? "image://icon/" + iconFromImage : "";
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
return appIcon;
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
return "";
return Quickshell.iconPath(appIcon, true);
return Paths.resolveIconPath(appIcon);
}
hasImage: hasNotificationImage
@@ -169,12 +169,12 @@ Rectangle {
return "";
const appIcon = notificationGroup?.latestNotification?.appIcon;
if (!appIcon)
return iconFromImage ? "image://icon/" + iconFromImage : "";
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
return appIcon;
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
return "";
return Quickshell.iconPath(appIcon, true);
return Paths.resolveIconPath(appIcon);
}
hasImage: hasNotificationImage
@@ -503,12 +503,12 @@ Rectangle {
return "";
const appIcon = modelData?.appIcon;
if (!appIcon)
return iconFromImage ? "image://icon/" + iconFromImage : "";
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
return appIcon;
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
return "";
return Quickshell.iconPath(appIcon, true);
return Paths.resolveIconPath(appIcon);
}
fallbackIcon: {
@@ -479,12 +479,12 @@ PanelWindow {
return "";
const appIcon = notificationData.appIcon;
if (!appIcon)
return iconFromImage ? "image://icon/" + iconFromImage : "";
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
return appIcon;
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
return "";
return Quickshell.iconPath(appIcon, true);
return Paths.resolveIconPath(appIcon);
}
hasImage: hasNotificationImage
+2 -2
View File
@@ -27,11 +27,11 @@ DankOSD {
let icon = "music_note";
switch (player.playbackState) {
case MprisPlaybackState.Playing:
icon = "play_arrow";
icon = "pause";
break;
case MprisPlaybackState.Paused:
case MprisPlaybackState.Stopped:
icon = "pause";
icon = "play_arrow";
break;
}
if (icon === _displayIcon)
+3 -3
View File
@@ -897,7 +897,7 @@ Item {
Image {
width: 24
height: 24
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
sourceSize.width: 24
sourceSize.height: 24
fillMode: Image.PreserveAspectFit
@@ -1008,7 +1008,7 @@ Item {
Image {
width: 24
height: 24
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
sourceSize.width: 24
sourceSize.height: 24
fillMode: Image.PreserveAspectFit
@@ -1154,7 +1154,7 @@ Item {
Image {
width: 24
height: 24
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
sourceSize.width: 24
sourceSize.height: 24
fillMode: Image.PreserveAspectFit
+1
View File
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
Singleton {
id: root
+28 -5
View File
@@ -19,6 +19,7 @@ Singleton {
readonly property string historyFile: Paths.strip(Paths.cache) + "/notification_history.json"
readonly property string imageCacheDir: Paths.strip(Paths.cache) + "/notification_images"
property bool historyLoaded: false
property int historyEntryCounter: 0
property list<NotifWrapper> notificationQueue: []
property list<NotifWrapper> visibleNotifications: []
@@ -73,6 +74,12 @@ Singleton {
onTriggered: root.performSaveHistory()
}
function _makeHistoryEntryId(sourceId, timestamp) {
historyEntryCounter += 1;
const safeSource = sourceId && sourceId !== "" ? sourceId : "notification";
return safeSource + "_" + (timestamp || Date.now()) + "_" + historyEntryCounter;
}
function getImageCachePath(wrapper) {
const ts = wrapper.time ? wrapper.time.getTime() : Date.now();
const id = wrapper.notification?.id?.toString() || "0";
@@ -80,12 +87,13 @@ Singleton {
}
function updateHistoryImage(wrapperId, imagePath) {
const idx = historyList.findIndex(n => n.id === wrapperId);
const idx = historyList.findIndex(n => n.sourceNotificationId === wrapperId || n.id === wrapperId);
if (idx < 0)
return;
const item = historyList[idx];
const updated = {
id: item.id,
sourceNotificationId: item.sourceNotificationId || item.id,
summary: item.summary,
body: item.body,
htmlBody: item.htmlBody,
@@ -113,8 +121,11 @@ Singleton {
} else if (imageUrl && !imageUrl.startsWith("image://qsimage/")) {
persistableImage = imageUrl;
}
const sourceNotificationId = wrapper.notification?.id?.toString() || "";
const timestamp = wrapper.time.getTime();
const data = {
id: wrapper.notification?.id?.toString() || Date.now().toString(),
id: _makeHistoryEntryId(sourceNotificationId, timestamp),
sourceNotificationId: sourceNotificationId,
summary: wrapper.summary || "",
body: wrapper.body || "",
htmlBody: wrapper.htmlBody || wrapper.body || "",
@@ -122,7 +133,7 @@ Singleton {
appIcon: wrapper.appIcon || "",
image: persistableImage,
urgency: urg,
timestamp: wrapper.time.getTime(),
timestamp: timestamp,
desktopEntry: wrapper.desktopEntry || ""
};
let newList = [data, ...historyList];
@@ -152,6 +163,8 @@ Singleton {
const now = Date.now();
const maxAgeMs = maxAgeDays > 0 ? maxAgeDays * 24 * 60 * 60 * 1000 : 0;
const loaded = [];
const seenIds = {};
let needsRewrite = false;
for (const item of historyAdapter.notifications || []) {
if (maxAgeMs > 0 && (now - item.timestamp) > maxAgeMs)
@@ -162,8 +175,18 @@ Singleton {
if (htmlBody) {
htmlBody = htmlBody.replace(/<img\b[^>]*>/gi, "");
}
const sourceNotificationId = (item.sourceNotificationId || item.id || "").toString();
let historyId = (item.id || "").toString();
if (!historyId || seenIds[historyId]) {
historyId = _makeHistoryEntryId(sourceNotificationId, item.timestamp || now);
needsRewrite = true;
}
if (!item.sourceNotificationId)
needsRewrite = true;
seenIds[historyId] = true;
loaded.push({
id: item.id || "",
id: historyId,
sourceNotificationId: sourceNotificationId,
summary: item.summary || "",
body: body,
htmlBody: htmlBody,
@@ -177,7 +200,7 @@ Singleton {
}
historyList = loaded;
historyLoaded = true;
if (maxAgeMs > 0 && loaded.length !== (historyAdapter.notifications || []).length)
if ((maxAgeMs > 0 && loaded.length !== (historyAdapter.notifications || []).length) || needsRewrite)
saveHistory();
} catch (e) {
console.warn("NotificationService: load history failed:", e);
+24 -21
View File
@@ -44,24 +44,26 @@ Singleton {
}
}
readonly property var archBasedPMSettings: {
"listUpdatesSettings": {
"params": ["-Qu"],
"correctExitCodes": [0, 1] // Exit code 0 = updates available, 1 = no updates
},
"upgradeSettings": {
"params": ["-Syu"],
"requiresSudo": false
},
"parserSettings": {
"lineRegex": /^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/,
"entryProducer": function (match) {
return {
"name": match[1],
"currentVersion": match[2],
"newVersion": match[3],
"description": `${match[1]} ${match[2]} ${match[3]}`
};
readonly property var archBasedPMSettings: function(requiresSudo) {
return {
"listUpdatesSettings": {
"params": ["-Qu"],
"correctExitCodes": [0, 1] // Exit code 0 = updates available, 1 = no updates
},
"upgradeSettings": {
"params": ["-Syu"],
"requiresSudo": requiresSudo
},
"parserSettings": {
"lineRegex": /^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/,
"entryProducer": function (match) {
return {
"name": match[1],
"currentVersion": match[2],
"newVersion": match[3],
"description": `${match[1]} ${match[2]} ${match[3]}`
};
}
}
}
}
@@ -92,8 +94,9 @@ Singleton {
"checkupdates": archBasedUCSettings
}
readonly property var packageManagerParams: {
"yay": archBasedPMSettings,
"paru": archBasedPMSettings,
"yay": archBasedPMSettings(false),
"paru": archBasedPMSettings(false),
"pacman": archBasedPMSettings(true),
"dnf": fedoraBasedPMSettings
}
readonly property list<string> supportedDistributions: ["arch", "artix", "cachyos", "manjaro", "endeavouros", "fedora"]
@@ -182,7 +185,7 @@ Singleton {
Process {
id: pkgManagerDetection
command: ["sh", "-c", "which paru || which yay || which dnf"]
command: ["sh", "-c", "which paru || which yay || which pacman || which dnf"]
onExited: exitCode => {
if (exitCode === 0) {
@@ -264,7 +264,7 @@ Singleton {
}
if (process) {
process.command = ["sh", "-c", `find "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
process.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
process.targetScreenName = screenName;
process.currentWallpaper = currentWallpaper;
process.goToPrevious = false;
@@ -272,7 +272,7 @@ Singleton {
}
} else {
// Use global process for fallback
cyclingProcess.command = ["sh", "-c", `find "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
cyclingProcess.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
cyclingProcess.targetScreenName = screenName || "";
cyclingProcess.currentWallpaper = currentWallpaper;
cyclingProcess.running = true;
@@ -296,7 +296,7 @@ Singleton {
}
if (process) {
process.command = ["sh", "-c", `find "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
process.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
process.targetScreenName = screenName;
process.currentWallpaper = currentWallpaper;
process.goToPrevious = true;
@@ -304,7 +304,7 @@ Singleton {
}
} else {
// Use global process for fallback
prevCyclingProcess.command = ["sh", "-c", `find "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
prevCyclingProcess.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
prevCyclingProcess.targetScreenName = screenName || "";
prevCyclingProcess.currentWallpaper = currentWallpaper;
prevCyclingProcess.running = true;
+1 -1
View File
@@ -49,7 +49,7 @@ Item {
readonly property string iconPath: {
if (hasSpecialPrefix || !iconValue)
return "";
return Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue);
return Paths.resolveIconPath(iconValue);
}
visible: iconValue !== undefined && iconValue !== ""
+1 -1
View File
@@ -1,4 +1,4 @@
[colors]
[colors-dark]
foreground={{colors.on_surface.default.hex_stripped}}
background={{colors.background.default.hex_stripped}}
selection-foreground={{colors.on_surface.default.hex_stripped}}
@@ -92,3 +92,21 @@ toolbar .toolbarbutton-1 {
#zen-appcontent-navbar-container {
background-color: {{colors.background.default.hex}} !important;
}
#PanelUI-menu-button .toolbarbutton-icon,
#downloads-button .toolbarbutton-icon,
#unified-extensions-button .toolbarbutton-icon {
fill: {{colors.primary.default.hex}} !important;
color: {{colors.primary.default.hex}} !important;
}
#PanelUI-menu-button .toolbarbutton-badge-stack,
#downloads-button .toolbarbutton-badge-stack,
#unified-extensions-button .toolbarbutton-badge-stack {
fill: {{colors.primary.default.hex}} !important;
color: {{colors.primary.default.hex}} !important;
}
toolbar .toolbarbutton-1 > .toolbarbutton-icon {
fill: {{colors.primary.default.hex}} !important;
}