1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

Compare commits

...

8 Commits

Author SHA1 Message Date
NikSne
7d761c4c9a feat(distro/nix/niri): add a hack for config includes with niri flake (#1239)
It works fine but needs all dms-generated config files to be present
2026-01-03 00:43:39 +01:00
Phil Jackson
4cb90c5367 Bar (mediaplayer): Mouse wheel options for media player widget (#1248)
* Add different options for scroll on media widget.

* Nicer lookup code.

* Remove some checks I didn't need.

* Update the search tags.

* EOF.
2026-01-02 17:08:42 -05:00
Ryan Bateman
1c7d15db0b util: add flatpak introspection utilities (#1234)
ci: run apt as sudo

ci: fix flatpak remote in runner

ci: flatpak install steps in runner

ci: specific version of freedesktop

ci: freedesktop install perms
2026-01-02 16:07:32 -05:00
vha
7268a3fe7f feat: Add group workspace apps toggle (#1238)
* Add group workspace apps toggle

* wording

* fix pre-commit

---------

Co-authored-by: bbedward <bbedward@gmail.com>
2026-01-02 15:55:51 -05:00
pcortellezzi
d2c4391514 feat: Persistent Plugins & Async Updates (#1231)
- PluginService: maintain persistent instances for Launcher plugins
- AppSearchService: reuse persistent instances for queries
- Added requestLauncherUpdate signal for async UI refreshes
2026-01-02 15:49:04 -05:00
sweenu
69b1d0c2da bar(ws): add option to show name (#1223) 2026-01-02 15:47:33 -05:00
sweenu
ba28767492 bar(clock): respect compact mode on vertical bar (#1222) 2026-01-02 15:46:33 -05:00
bbedward
6cff5f1146 settings: prevent overwrites if parse called with null object 2026-01-02 15:45:31 -05:00
17 changed files with 677 additions and 96 deletions

View File

@@ -28,6 +28,15 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install flatpak
run: sudo apt update && sudo apt install -y flatpak
- name: Add flathub
run: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
- name: Add a flatpak that mutagen could support
run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen
- name: Set up Go
uses: actions/setup-go@v5
with:

View File

@@ -0,0 +1,78 @@
package utils
import (
"bytes"
"errors"
"os/exec"
"strings"
)
func FlatpakInPath() bool {
_, err := exec.LookPath("flatpak")
return err == nil
}
func FlatpakExists(name string) bool {
if !FlatpakInPath() {
return false
}
cmd := exec.Command("flatpak", "info", name)
err := cmd.Run()
return err == nil
}
func FlatpakSearchBySubstring(substring string) bool {
if !FlatpakInPath() {
return false
}
cmd := exec.Command("flatpak", "list", "--app")
var stdout bytes.Buffer
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return false
}
out := stdout.String()
for line := range strings.SplitSeq(out, "\n") {
fields := strings.Fields(line)
if len(fields) > 1 {
id := fields[1]
idParts := strings.Split(id, ".")
// We are assuming that the last part of the ID is
// the package name we're looking for. This might
// not always be true, some developers use arbitrary
// suffixes.
if len(idParts) > 0 && idParts[len(idParts)-1] == substring {
cmd := exec.Command("flatpak", "info", id)
err := cmd.Run()
return err == nil
}
}
}
return false
}
func FlatpakInstallationDir(name string) (string, error) {
if !FlatpakInPath() {
return "", errors.New("flatpak not found in PATH")
}
cmd := exec.Command("flatpak", "info", "--show-location", name)
var stdout bytes.Buffer
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return "", errors.New("flatpak not installed: " + name)
}
location := strings.TrimSpace(stdout.String())
if location == "" {
return "", errors.New("installation directory not found for: " + name)
}
return location, nil
}

View File

@@ -0,0 +1,210 @@
package utils
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestFlatpakInPathAvailable(t *testing.T) {
result := FlatpakInPath()
if !result {
t.Skip("flatpak not in PATH")
}
if !result {
t.Errorf("expected true when flatpak is in PATH")
}
}
func TestFlatpakInPathUnavailable(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PATH", tempDir)
result := FlatpakInPath()
if result {
t.Errorf("expected false when flatpak not in PATH, got true")
}
}
func TestFlatpakExistsValidPackage(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}
result := FlatpakExists("com.nonexistent.package.test")
if result {
t.Logf("package exists (unexpected but not an error)")
}
}
func TestFlatpakExistsNoFlatpak(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PATH", tempDir)
result := FlatpakExists("any.package.name")
if result {
t.Errorf("expected false when flatpak not in PATH, got true")
}
}
func TestFlatpakSearchBySubstringNoFlatpak(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PATH", tempDir)
result := FlatpakSearchBySubstring("test")
if result {
t.Errorf("expected false when flatpak not in PATH, got true")
}
}
func TestFlatpakSearchBySubstringNonexistent(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}
result := FlatpakSearchBySubstring("ThisIsAVeryUnlikelyPackageName12345")
if result {
t.Errorf("expected false for nonexistent package substring")
}
}
func TestFlatpakInstallationDirNoFlatpak(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PATH", tempDir)
_, err := FlatpakInstallationDir("any.package.name")
if err == nil {
t.Errorf("expected error when flatpak not in PATH")
}
if err != nil && !strings.Contains(err.Error(), "not found in PATH") {
t.Errorf("expected 'not found in PATH' error, got: %v", err)
}
}
func TestFlatpakInstallationDirNonexistent(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}
_, err := FlatpakInstallationDir("com.nonexistent.package.test")
if err == nil {
t.Errorf("expected error for nonexistent package")
}
if err != nil && !strings.Contains(err.Error(), "not installed") {
t.Errorf("expected 'not installed' error, got: %v", err)
}
}
func TestFlatpakInstallationDirValid(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}
// This test requires a known installed flatpak
// We can't guarantee any specific flatpak is installed,
// so we'll skip if we can't find a common one
commonFlatpaks := []string{
"org.mozilla.firefox",
"org.gnome.Calculator",
"org.freedesktop.Platform",
}
var testPackage string
for _, pkg := range commonFlatpaks {
if FlatpakExists(pkg) {
testPackage = pkg
break
}
}
if testPackage == "" {
t.Skip("no common flatpak packages found for testing")
}
result, err := FlatpakInstallationDir(testPackage)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == "" {
t.Errorf("expected non-empty installation directory")
}
if !strings.Contains(result, testPackage) {
t.Logf("installation directory %s doesn't contain package name (may be expected)", result)
}
}
func TestFlatpakExistsCommandFailure(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}
// Mock a failing flatpak command through PATH interception
tempDir := t.TempDir()
fakeFlatpak := filepath.Join(tempDir, "flatpak")
script := "#!/bin/sh\nexit 1\n"
err := os.WriteFile(fakeFlatpak, []byte(script), 0755)
if err != nil {
t.Fatalf("failed to create fake flatpak: %v", err)
}
originalPath := os.Getenv("PATH")
t.Setenv("PATH", tempDir+":"+originalPath)
result := FlatpakExists("test.package")
if result {
t.Errorf("expected false when flatpak command fails, got true")
}
}
func TestFlatpakSearchBySubstringCommandFailure(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}
// Mock a failing flatpak command through PATH interception
tempDir := t.TempDir()
fakeFlatpak := filepath.Join(tempDir, "flatpak")
script := "#!/bin/sh\nexit 1\n"
err := os.WriteFile(fakeFlatpak, []byte(script), 0755)
if err != nil {
t.Fatalf("failed to create fake flatpak: %v", err)
}
originalPath := os.Getenv("PATH")
t.Setenv("PATH", tempDir+":"+originalPath)
result := FlatpakSearchBySubstring("test")
if result {
t.Errorf("expected false when flatpak command fails, got true")
}
}
func TestFlatpakInstallationDirCommandFailure(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}
// Mock a failing flatpak command through PATH interception
tempDir := t.TempDir()
fakeFlatpak := filepath.Join(tempDir, "flatpak")
script := "#!/bin/sh\nexit 1\n"
err := os.WriteFile(fakeFlatpak, []byte(script), 0755)
if err != nil {
t.Fatalf("failed to create fake flatpak: %v", err)
}
originalPath := os.Getenv("PATH")
t.Setenv("PATH", tempDir+":"+originalPath)
_, err = FlatpakInstallationDir("test.package")
if err == nil {
t.Errorf("expected error when flatpak command fails")
}
if err != nil && !strings.Contains(err.Error(), "not installed") {
t.Errorf("expected 'not installed' error, got: %v", err)
}
}

View File

@@ -15,10 +15,93 @@ in
niri = {
enableKeybinds = lib.mkEnableOption "DankMaterialShell niri keybinds";
enableSpawn = lib.mkEnableOption "DankMaterialShell niri spawn-at-startup";
includes = {
enable = (lib.mkEnableOption "includes for niri-flake") // {
default = true;
};
override = lib.mkOption {
type = lib.types.bool;
description = ''
Whether DMS settings will be prioritized over settings defined in niri-flake or not
'';
default = true;
example = false;
};
originalFileName = lib.mkOption {
type = lib.types.str;
description = ''
A new name for the config file generated by niri-flake
'';
default = "hm";
example = "niri-flake";
};
filesToInclude = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
A list of dms-generated files to include
'';
default = [
"alttab"
"binds"
"colors"
"layout"
"outputs"
"wpblur"
];
example = [
"outputs"
"wpblur"
];
};
};
};
};
config = lib.mkIf cfg.enable {
warnings = (
lib.optional (cfg.niri.enableKeybinds && cfg.niri.includes.enable) ''
It is not recommended to use both `enableKeybinds` and `includes.enable` at the same time.
''
);
# HACK: niri-flake does not support config includes yet, but we can "fix" that
# TODO: replace with proper config includes after https://github.com/sodiboo/niri-flake/pull/1548 merge
xdg.configFile = lib.mkIf cfg.niri.includes.enable (
let
cfg' = cfg.niri.includes;
withOriginalConfig =
dmsFiles:
if cfg'.override then
[ cfg'.originalFileName ] ++ dmsFiles
else
dmsFiles ++ [ cfg'.originalFileName ];
fixes = map (fix: "\n${fix}") (
lib.optional (cfg'.enable && config.programs.niri.settings.layout.border.enable)
# kdl
''
// Border fix
// See https://yalter.github.io/niri/Configuration%3A-Include.html#border-special-case for details
layout { border { on; }; }
''
);
in
{
niri-config.target = lib.mkForce "niri/${cfg'.originalFileName}.kdl";
niri-config-dms = {
target = "niri/config.kdl";
text = lib.pipe cfg'.filesToInclude [
(map (filename: "dms/${filename}"))
withOriginalConfig
(map (filename: "include \"${filename}.kdl\""))
(files: files ++ fixes)
(builtins.concatStringsSep "\n")
];
};
}
);
programs.niri.settings = lib.mkMerge [
(lib.mkIf cfg.niri.enableKeybinds {
binds =

View File

@@ -171,9 +171,11 @@ Singleton {
]
property bool showWorkspaceIndex: false
property bool showWorkspaceName: false
property bool showWorkspacePadding: false
property bool workspaceScrolling: false
property bool showWorkspaceApps: false
property bool groupWorkspaceApps: true
property int maxWorkspaceIcons: 3
property bool workspacesPerMonitor: true
property bool showOccupiedWorkspacesOnly: false
@@ -183,7 +185,7 @@ Singleton {
property bool waveProgressEnabled: true
property bool scrollTitleEnabled: true
property bool audioVisualizerEnabled: true
property bool audioScrollEnabled: true
property string audioScrollMode: "volume"
property bool clockCompactMode: false
property bool focusedWindowCompactMode: false
property bool runningAppsCompactMode: true
@@ -798,9 +800,9 @@ Singleton {
Store.parse(root, obj);
if (obj.weatherLocation !== undefined)
if (obj?.weatherLocation !== undefined)
_legacyWeatherLocation = obj.weatherLocation;
if (obj.weatherCoordinates !== undefined)
if (obj?.weatherCoordinates !== undefined)
_legacyWeatherCoordinates = obj.weatherCoordinates;
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));

View File

@@ -4,12 +4,15 @@
function parse(root, jsonObj) {
var SPEC = SpecModule.SPEC;
for (var k in SPEC) {
root[k] = SPEC[k].def;
}
if (!jsonObj) return;
for (var k in SPEC) {
if (!(k in jsonObj)) {
root[k] = SPEC[k].def;
}
}
for (var k in jsonObj) {
if (!SPEC[k]) continue;
var raw = jsonObj[k];

View File

@@ -81,10 +81,12 @@ var SPEC = {
]},
showWorkspaceIndex: { def: false },
showWorkspaceName: { def: false },
showWorkspacePadding: { def: false },
workspaceScrolling: { def: false },
showWorkspaceApps: { def: false },
maxWorkspaceIcons: { def: 3 },
groupWorkspaceApps: { def: true },
workspacesPerMonitor: { def: true },
showOccupiedWorkspacesOnly: { def: false },
reverseScrolling: { def: false },
@@ -93,7 +95,7 @@ var SPEC = {
waveProgressEnabled: { def: true },
scrollTitleEnabled: { def: true },
audioVisualizerEnabled: { def: true },
audioScrollEnabled: { def: true },
audioScrollMode: { def: "volume" },
clockCompactMode: { def: false },
focusedWindowCompactMode: { def: false },
runningAppsCompactMode: { def: true },

View File

@@ -4,14 +4,16 @@
function parse(root, jsonObj) {
var SPEC = SpecModule.SPEC;
for (var k in SPEC) {
if (k === "pluginSettings") continue;
var spec = SPEC[k];
root[k] = spec.def;
}
if (!jsonObj) return;
for (var k in SPEC) {
if (k === "pluginSettings") continue;
if (!(k in jsonObj)) {
root[k] = SPEC[k].def;
}
}
for (var k in jsonObj) {
if (!SPEC[k]) continue;
if (k === "pluginSettings") continue;

View File

@@ -51,6 +51,10 @@ Item {
function onPluginLoaded() { updateCategories() }
function onPluginUnloaded() { updateCategories() }
function onPluginListUpdated() { updateCategories() }
function onRequestLauncherUpdate(pluginId) {
// Only update if we are actually looking at this plugin or in All category
updateFilteredModel()
}
}
Connections {

View File

@@ -16,6 +16,8 @@ BasePill {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : clockRow.implicitWidth
implicitHeight: root.isVerticalOrientation ? clockColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
readonly property bool compact: widgetData?.clockCompactMode !== undefined ? widgetData.clockCompactMode : SettingsData.clockCompactMode
Column {
id: clockColumn
visible: root.isVerticalOrientation
@@ -106,6 +108,7 @@ BasePill {
width: parent.width
height: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
visible: !compact
Rectangle {
width: parent.width * 0.6
@@ -118,6 +121,7 @@ BasePill {
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
visible: !compact
StyledText {
text: {
@@ -151,6 +155,7 @@ BasePill {
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
visible: !compact
StyledText {
text: {
@@ -204,7 +209,7 @@ BasePill {
font.pixelSize: Theme.fontSizeSmall
color: Theme.outlineButton
anchors.baseline: dateText.baseline
visible: !(widgetData?.clockCompactMode !== undefined ? widgetData.clockCompactMode : SettingsData.clockCompactMode)
visible: !compact
}
StyledText {
@@ -218,7 +223,7 @@ BasePill {
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
visible: !(widgetData?.clockCompactMode !== undefined ? widgetData.clockCompactMode : SettingsData.clockCompactMode)
visible: !compact
}
}

View File

@@ -54,38 +54,67 @@ BasePill {
property real touchpadThreshold: 100
onWheel: function (wheelEvent) {
if (!usePlayerVolume)
return;
if (!SettingsData.audioScrollEnabled)
if (SettingsData.audioScrollMode === "nothing")
return;
wheelEvent.accepted = true;
if (SettingsData.audioScrollMode === "volume") {
if (!usePlayerVolume)
return;
const deltaY = wheelEvent.angleDelta.y;
const isMouseWheelY = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
wheelEvent.accepted = true;
const currentVolume = activePlayer.volume * 100;
const deltaY = wheelEvent.angleDelta.y;
const isMouseWheelY = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
let newVolume = currentVolume;
if (isMouseWheelY) {
if (deltaY > 0) {
newVolume = Math.min(100, currentVolume + 5);
} else if (deltaY < 0) {
newVolume = Math.max(0, currentVolume - 5);
}
} else {
scrollAccumulatorY += deltaY;
if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) {
if (scrollAccumulatorY > 0) {
newVolume = Math.min(100, currentVolume + 1);
} else {
newVolume = Math.max(0, currentVolume - 1);
const currentVolume = activePlayer.volume * 100;
let newVolume = currentVolume;
if (isMouseWheelY) {
if (deltaY > 0) {
newVolume = Math.min(100, currentVolume + 5);
} else if (deltaY < 0) {
newVolume = Math.max(0, currentVolume - 5);
}
} else {
scrollAccumulatorY += deltaY;
if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) {
if (scrollAccumulatorY > 0) {
newVolume = Math.min(100, currentVolume + 1);
} else {
newVolume = Math.max(0, currentVolume - 1);
}
scrollAccumulatorY = 0;
}
}
activePlayer.volume = newVolume / 100;
} else if (SettingsData.audioScrollMode === "song") {
if (!activePlayer)
return;
wheelEvent.accepted = true;
const deltaY = wheelEvent.angleDelta.y;
const isMouseWheelY = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
if (isMouseWheelY) {
if (deltaY > 0) {
activePlayer.previous();
} else {
activePlayer.next();
}
} else {
scrollAccumulatorY += deltaY;
if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) {
if (scrollAccumulatorY > 0) {
activePlayer.previous();
} else {
activePlayer.next();
}
scrollAccumulatorY = 0;
}
scrollAccumulatorY = 0;
}
}
activePlayer.volume = newVolume / 100;
}
content: Component {

View File

@@ -241,7 +241,7 @@ Item {
}
const keyBase = (w.app_id || w.appId || w.class || w.windowClass || "unknown");
const key = isActiveWs ? `${keyBase}_${i}` : keyBase;
const key = isActiveWs || !SettingsData.groupWorkspaceApps ? `${keyBase}_${i}` : keyBase;
if (!byApp[key]) {
const moddedId = Paths.moddedAppId(keyBase);
@@ -565,6 +565,17 @@ Item {
if (isPlaceholder)
return index + 1;
if (SettingsData.showWorkspaceName) {
let workspaceName = modelData?.name;
if (workspaceName && workspaceName !== "") {
if (root.isVertical) {
return workspaceName.charAt(0);
}
return workspaceName;
}
}
if (root.useExtWorkspace)
return index + 1;
if (CompositorService.isHyprland)
@@ -942,7 +953,7 @@ Item {
id: rowLayout
Row {
spacing: 4
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || loadedHasIcon
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
Item {
visible: loadedHasIcon && loadedIconData?.type === "icon"
@@ -975,7 +986,7 @@ Item {
}
Item {
visible: SettingsData.showWorkspaceIndex && !loadedHasIcon
visible: (SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon
width: wsIndexText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0)
height: root.appIconSize
@@ -1072,7 +1083,7 @@ Item {
id: columnLayout
Column {
spacing: 4
visible: loadedIcons.length > 0 || loadedHasIcon
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
DankIcon {
visible: loadedHasIcon && loadedIconData?.type === "icon"
@@ -1209,7 +1220,7 @@ Item {
Loader {
id: indexLoader
anchors.fill: parent
active: SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps
active: (SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon && !SettingsData.showWorkspaceApps
sourceComponent: Item {
StyledText {
anchors.centerIn: parent

View File

@@ -46,11 +46,24 @@ Item {
onToggled: checked => SettingsData.set("audioVisualizerEnabled", checked)
}
SettingsToggleRow {
SettingsDropdownRow {
property var scrollOpts: {
"Change Volume": "volume",
"Change Song": "song",
"Nothing": "nothing"
}
text: I18n.tr("Scroll Wheel")
description: I18n.tr("Scroll on widget changes media volume")
checked: SettingsData.audioScrollEnabled
onToggled: checked => SettingsData.set("audioScrollEnabled", checked)
description: I18n.tr("Scroll wheel behavior on media widget")
settingKey: "audioScrollMode"
tags: ["media", "music", "scroll"]
options: Object.keys(scrollOpts).sort()
currentValue: {
Object.keys(scrollOpts).find(key => scrollOpts[key] === SettingsData.audioScrollMode) ?? "volume"
}
onValueChanged: value => {
SettingsData.set("audioScrollMode", scrollOpts[value])
}
}
}
}

View File

@@ -35,6 +35,15 @@ Item {
onToggled: checked => SettingsData.set("showWorkspaceIndex", checked)
}
SettingsToggleRow {
settingKey: "showWorkspaceName"
tags: ["workspace", "name", "labels"]
text: I18n.tr("Workspace Names")
description: I18n.tr("Show workspace name on horizontal bars, and first letter on vertical bars")
checked: SettingsData.showWorkspaceName
onToggled: checked => SettingsData.set("showWorkspaceName", checked)
}
SettingsToggleRow {
settingKey: "showWorkspacePadding"
tags: ["workspace", "padding", "minimum"]
@@ -94,6 +103,16 @@ Item {
}
}
SettingsToggleRow {
settingKey: "groupWorkspaceApps"
tags: ["workspace", "apps", "icons", "group", "grouped", "collapse"]
text: I18n.tr("Group Workspace Apps")
description: I18n.tr("Group repeated application icons in unfocused workspaces")
checked: SettingsData.groupWorkspaceApps
visible: SettingsData.showWorkspaceApps
onToggled: checked => SettingsData.set("groupWorkspaceApps", checked)
}
SettingsToggleRow {
settingKey: "workspacesPerMonitor"
tags: ["workspace", "per-monitor", "multi-monitor"]

View File

@@ -411,26 +411,43 @@ Singleton {
return []
}
const component = PluginService.pluginLauncherComponents[pluginId]
if (!component)
let instance = PluginService.pluginInstances[pluginId]
let isPersistent = true
if (!instance) {
const component = PluginService.pluginLauncherComponents[pluginId]
if (!component)
return []
try {
instance = component.createObject(root, {
"pluginService": PluginService
})
isPersistent = false
} catch (e) {
console.warn("AppSearchService: Error creating temporary plugin instance", pluginId, ":", e)
return []
}
}
if (!instance)
return []
try {
const instance = component.createObject(root, {
"pluginService": PluginService
})
if (instance && typeof instance.getItems === "function") {
if (typeof instance.getItems === "function") {
const items = instance.getItems(query || "")
instance.destroy()
if (!isPersistent)
instance.destroy()
return items || []
}
if (instance) {
if (!isPersistent) {
instance.destroy()
}
} catch (e) {
console.warn("AppSearchService: Error getting items from plugin", pluginId, ":", e)
if (!isPersistent)
instance.destroy()
}
return []
@@ -440,26 +457,43 @@ Singleton {
if (typeof PluginService === "undefined")
return false
const component = PluginService.pluginLauncherComponents[pluginId]
if (!component)
let instance = PluginService.pluginInstances[pluginId]
let isPersistent = true
if (!instance) {
const component = PluginService.pluginLauncherComponents[pluginId]
if (!component)
return false
try {
instance = component.createObject(root, {
"pluginService": PluginService
})
isPersistent = false
} catch (e) {
console.warn("AppSearchService: Error creating temporary plugin instance for execution", pluginId, ":", e)
return false
}
}
if (!instance)
return false
try {
const instance = component.createObject(root, {
"pluginService": PluginService
})
if (instance && typeof instance.executeItem === "function") {
if (typeof instance.executeItem === "function") {
instance.executeItem(item)
instance.destroy()
if (!isPersistent)
instance.destroy()
return true
}
if (instance) {
if (!isPersistent) {
instance.destroy()
}
} catch (e) {
console.warn("AppSearchService: Error executing item from plugin", pluginId, ":", e)
if (!isPersistent)
instance.destroy()
}
return false

View File

@@ -39,6 +39,7 @@ Singleton {
signal pluginDataChanged(string pluginId)
signal pluginListUpdated
signal globalVarChanged(string pluginId, string varName)
signal requestLauncherUpdate(string pluginId)
Timer {
id: resyncDebounce
@@ -286,12 +287,14 @@ Singleton {
return false;
}
if (isDaemon) {
// MODIFICATION: Treat Launchers as persistent instances like Daemons
if (isDaemon || isLauncher) {
const instance = comp.createObject(root, {
"pluginId": pluginId
"pluginId": pluginId,
"pluginService": root // Inject PluginService
});
if (!instance) {
console.error("PluginService: failed to instantiate daemon:", pluginId, comp.errorString());
console.error("PluginService: failed to instantiate plugin:", pluginId, comp.errorString());
pluginLoadFailed(pluginId, comp.errorString());
return false;
}
@@ -299,13 +302,15 @@ Singleton {
newInstances[pluginId] = instance;
pluginInstances = newInstances;
const newDaemons = Object.assign({}, pluginDaemonComponents);
newDaemons[pluginId] = comp;
pluginDaemonComponents = newDaemons;
} else if (isLauncher) {
const newLaunchers = Object.assign({}, pluginLauncherComponents);
newLaunchers[pluginId] = comp;
pluginLauncherComponents = newLaunchers;
if (isDaemon) {
const newDaemons = Object.assign({}, pluginDaemonComponents);
newDaemons[pluginId] = comp;
pluginDaemonComponents = newDaemons;
} else {
const newLaunchers = Object.assign({}, pluginLauncherComponents);
newLaunchers[pluginId] = comp;
pluginLauncherComponents = newLaunchers;
}
} else if (isDesktop) {
const newDesktop = Object.assign({}, pluginDesktopComponents);
newDesktop[pluginId] = comp;

View File

@@ -708,6 +708,31 @@
],
"icon": "visibility_off"
},
{
"section": "groupWorkspaceApps",
"label": "Group Workspace Apps",
"tabIndex": 4,
"category": "Workspaces",
"keywords": [
"app",
"application",
"apps",
"collapse",
"desktop",
"group",
"grouped",
"icons",
"program",
"repeated",
"same",
"spaces",
"virtual",
"virtual desktops",
"workspace",
"workspaces"
],
"description": "Group repeated application icons in the same workspace"
},
{
"section": "workspaceIcons",
"label": "Named Workspace Icons",
@@ -871,6 +896,34 @@
],
"description": "Show workspace index numbers in the top bar workspace switcher"
},
{
"section": "showWorkspaceName",
"label": "Workspace Names",
"tabIndex": 4,
"category": "Workspaces",
"keywords": [
"bars",
"desktop",
"first",
"horizontal",
"labels",
"letter",
"name",
"names",
"panel",
"show",
"spaces",
"statusbar",
"taskbar",
"topbar",
"vertical",
"virtual",
"virtual desktops",
"workspace",
"workspaces"
],
"description": "Show workspace name on horizontal bars, and first letter on vertical bars"
},
{
"section": "showWorkspacePadding",
"label": "Workspace Padding",
@@ -2496,6 +2549,27 @@
"icon": "lock",
"description": "If the field is hidden, it will appear as soon as a key is pressed."
},
{
"section": "lockBeforeSuspend",
"label": "Lock before suspend",
"tabIndex": 11,
"category": "Lock Screen",
"keywords": [
"automatic",
"automatically",
"before",
"lock",
"login",
"password",
"prepares",
"screen",
"security",
"sleep",
"suspend",
"system"
],
"description": "Automatically lock the screen when the system prepares to suspend"
},
{
"section": "lockScreenShowPasswordField",
"label": "Show Password Field",
@@ -2928,6 +3002,7 @@
"playback",
"player",
"progress",
"scroll",
"settings",
"spotify",
"statusbar",
@@ -2938,6 +3013,24 @@
"icon": "music_note",
"description": "Use animated wave progress bars for media playback"
},
{
"section": "audioScrollMode",
"label": "Scroll Wheel",
"tabIndex": 16,
"category": "Media Player",
"keywords": [
"behavior",
"media",
"mpris",
"music",
"player",
"scroll",
"spotify",
"wheel",
"widget"
],
"description": "Scroll wheel behavior on media widget"
},
{
"section": "notificationTimeoutCritical",
"label": "Critical Priority",
@@ -3413,27 +3506,6 @@
"icon": "schedule",
"description": "Gradually fade the screen before locking with a configurable grace period"
},
{
"section": "lockBeforeSuspend",
"label": "Lock before suspend",
"tabIndex": 21,
"category": "Power & Sleep",
"keywords": [
"automatically",
"before",
"energy",
"lock",
"power",
"prepares",
"screen",
"security",
"shutdown",
"sleep",
"suspend",
"system"
],
"description": "Automatically lock the screen when the system prepares to suspend"
},
{
"section": "powerConfirmation",
"label": "Power Action Confirmation",