1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-30 16:32:50 -05:00

Compare commits

...

3 Commits

Author SHA1 Message Date
bbedward
f762f9ae49 theme: fix gtk apply button on empty file
fixes #1280
2026-01-05 21:56:50 -05:00
bbedward
4484f6bd61 launcher: built-in plugins, add settings search plugin with ? default
trigger
2026-01-05 21:46:12 -05:00
purian23
0076c45496 shell: dmsCoreApp updates 2026-01-05 20:31:06 -05:00
11 changed files with 488 additions and 97 deletions

View File

@@ -62,6 +62,22 @@ Singleton {
property bool _hasUnsavedChanges: false
property var _loadedSettingsSnapshot: null
property var pluginSettings: ({})
property var builtInPluginSettings: ({})
function getBuiltInPluginSetting(pluginId, key, defaultValue) {
if (!builtInPluginSettings[pluginId])
return defaultValue;
return builtInPluginSettings[pluginId][key] !== undefined ? builtInPluginSettings[pluginId][key] : defaultValue;
}
function setBuiltInPluginSetting(pluginId, key, value) {
const updated = JSON.parse(JSON.stringify(builtInPluginSettings));
if (!updated[pluginId])
updated[pluginId] = {};
updated[pluginId][key] = value;
builtInPluginSettings = updated;
saveSettings();
}
property alias dankBarLeftWidgetsModel: leftWidgetsModel
property alias dankBarCenterWidgetsModel: centerWidgetsModel

View File

@@ -382,7 +382,9 @@ var SPEC = {
desktopWidgetPositions: { def: {} },
desktopWidgetGridSettings: { def: {} },
desktopWidgetInstances: { def: [] }
desktopWidgetInstances: { def: [] },
builtInPluginSettings: { def: {} }
};
function getValidKeys() {

View File

@@ -606,6 +606,8 @@ Item {
active: false
Component.onCompleted: PopoutService.processListModalLoader = processListModalLoader
ProcessListModal {
id: processListModal
@@ -658,6 +660,9 @@ Item {
}
}
}
onInstancesChanged: PopoutService.notepadSlideouts = instances
Component.onCompleted: PopoutService.notepadSlideouts = instances
}
LazyLoader {

View File

@@ -22,11 +22,8 @@ DankModal {
function resetContent() {
if (!spotlightContent)
return;
if (spotlightContent.appLauncher) {
spotlightContent.appLauncher.searchQuery = "";
spotlightContent.appLauncher.selectedIndex = 0;
spotlightContent.appLauncher.setCategory(I18n.tr("All"));
}
if (spotlightContent.appLauncher)
spotlightContent.appLauncher.reset();
if (spotlightContent.fileSearchController)
spotlightContent.fileSearchController.reset();
if (spotlightContent.resetScroll)

View File

@@ -96,7 +96,11 @@ Item {
_updatingFromTrigger = true;
selectedCategory = triggerResult.pluginCategory;
_updatingFromTrigger = false;
apps = AppSearchService.getPluginItems(triggerResult.pluginCategory, triggerResult.query);
if (triggerResult.isBuiltIn) {
apps = AppSearchService.getBuiltInLauncherItems(triggerResult.pluginId, triggerResult.query);
} else {
apps = AppSearchService.getPluginItems(triggerResult.pluginCategory, triggerResult.query);
}
} else {
if (_isTriggered) {
_updatingFromTrigger = true;
@@ -114,7 +118,11 @@ Item {
const items = AppSearchService.getPluginItems(pluginCategory, "");
emptyTriggerItems = emptyTriggerItems.concat(items);
});
// Add Core Apps
const builtInEmptyTrigger = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger();
builtInEmptyTrigger.forEach(pluginId => {
const items = AppSearchService.getBuiltInLauncherItems(pluginId, "");
emptyTriggerItems = emptyTriggerItems.concat(items);
});
const coreItems = AppSearchService.getCoreApps("");
apps = AppSearchService.applications.concat(emptyTriggerItems).concat(coreItems);
} else {
@@ -133,6 +141,11 @@ Item {
const items = AppSearchService.getPluginItems(pluginCategory, searchQuery);
emptyTriggerItems = emptyTriggerItems.concat(items);
});
const builtInEmptyTrigger = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger();
builtInEmptyTrigger.forEach(pluginId => {
const items = AppSearchService.getBuiltInLauncherItems(pluginId, searchQuery);
emptyTriggerItems = emptyTriggerItems.concat(items);
});
const coreItems = AppSearchService.getCoreApps(searchQuery);
apps = apps.concat(emptyTriggerItems).concat(coreItems);
@@ -191,6 +204,7 @@ Item {
"categories": app.categories || [],
"isPlugin": isPluginItem,
"isCore": app.isCore === true,
"isBuiltInLauncher": app.isBuiltInLauncher === true,
"appIndex": uniqueApps.length - 1
});
}
@@ -240,13 +254,18 @@ Item {
}
function launchApp(appData) {
if (!appData || typeof appData.appIndex === "undefined" || appData.appIndex < 0 || appData.appIndex >= _uniqueApps.length) {
if (!appData || typeof appData.appIndex === "undefined" || appData.appIndex < 0 || appData.appIndex >= _uniqueApps.length)
return;
}
suppressUpdatesWhileLaunching = true;
const actualApp = _uniqueApps[appData.appIndex];
if (appData.isBuiltInLauncher) {
AppSearchService.executeBuiltInLauncherItem(actualApp);
appLaunched(appData);
return;
}
if (appData.isCore) {
AppSearchService.executeCoreApp(actualApp);
appLaunched(appData);
@@ -260,11 +279,20 @@ Item {
appLaunched(appData);
return;
}
} else {
SessionService.launchDesktopEntry(actualApp);
appLaunched(appData);
AppUsageHistoryData.addAppUsage(actualApp);
return;
}
SessionService.launchDesktopEntry(actualApp);
appLaunched(appData);
AppUsageHistoryData.addAppUsage(actualApp);
}
function reset() {
suppressUpdatesWhileLaunching = false;
searchQuery = "";
selectedIndex = 0;
setCategory(I18n.tr("All"));
updateFilteredModel();
}
function setCategory(category) {
@@ -320,42 +348,50 @@ Item {
onTriggered: updateFilteredModel()
}
// Plugin trigger system functions
function checkPluginTriggers(query) {
if (!query || typeof PluginService === "undefined") {
if (!query)
return { triggered: false, pluginCategory: "", query: "" };
const builtInTriggers = AppSearchService.getBuiltInLauncherTriggers();
for (const trigger in builtInTriggers) {
if (!query.startsWith(trigger))
continue;
const pluginId = builtInTriggers[trigger];
const plugin = AppSearchService.builtInPlugins[pluginId];
if (!plugin)
continue;
return {
triggered: false,
pluginCategory: "",
query: ""
triggered: true,
pluginId: pluginId,
pluginCategory: plugin.name,
query: query.substring(trigger.length).trim(),
trigger: trigger,
isBuiltIn: true
};
}
if (typeof PluginService === "undefined")
return { triggered: false, pluginCategory: "", query: "" };
const triggers = PluginService.getAllPluginTriggers();
for (const trigger in triggers) {
if (query.startsWith(trigger)) {
const pluginId = triggers[trigger];
const plugin = PluginService.getLauncherPlugin(pluginId);
if (plugin) {
const remainingQuery = query.substring(trigger.length).trim();
const result = {
triggered: true,
pluginId: pluginId,
pluginCategory: plugin.name || pluginId,
query: remainingQuery,
trigger: trigger
};
return result;
}
}
if (!query.startsWith(trigger))
continue;
const pluginId = triggers[trigger];
const plugin = PluginService.getLauncherPlugin(pluginId);
if (!plugin)
continue;
return {
triggered: true,
pluginId: pluginId,
pluginCategory: plugin.name || pluginId,
query: query.substring(trigger.length).trim(),
trigger: trigger,
isBuiltIn: false
};
}
return {
triggered: false,
pluginCategory: "",
query: ""
};
return { triggered: false, pluginCategory: "", query: "" };
}
function getPluginIdForItem(item) {

View File

@@ -378,6 +378,90 @@ Item {
}
}
SettingsCard {
id: builtInPluginsCard
width: parent.width
iconName: "extension"
title: "DMS"
settingKey: "builtInPlugins"
Column {
width: parent.width
spacing: Theme.spacingS
Repeater {
model: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_settings_search"]
delegate: Rectangle {
id: pluginDelegate
required property string modelData
required property int index
readonly property var plugin: AppSearchService.builtInPlugins[modelData]
width: parent.width
height: 56
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: pluginDelegate.plugin?.cornerIcon ?? "extension"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
StyledText {
text: pluginDelegate.plugin?.name ?? pluginDelegate.modelData
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
}
StyledText {
text: pluginDelegate.plugin?.comment ?? ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
Row {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankTextField {
id: triggerField
width: 60
visible: pluginDelegate.plugin?.isLauncher === true
anchors.verticalCenter: parent.verticalCenter
placeholderText: I18n.tr("Trigger")
onTextEdited: SettingsData.setBuiltInPluginSetting(pluginDelegate.modelData, "trigger", text)
Component.onCompleted: text = SettingsData.getBuiltInPluginSetting(pluginDelegate.modelData, "trigger", pluginDelegate.plugin?.defaultTrigger ?? "")
}
DankToggle {
id: enableToggle
anchors.verticalCenter: parent.verticalCenter
checked: SettingsData.getBuiltInPluginSetting(pluginDelegate.modelData, "enabled", true)
onToggled: SettingsData.setBuiltInPluginSetting(pluginDelegate.modelData, "enabled", checked)
}
}
}
}
}
}
SettingsCard {
id: recentAppsCard
width: parent.width

View File

@@ -42,64 +42,179 @@ Singleton {
_cachedCategories = null;
}
readonly property var coreApps: [
{
name: "DMS Settings",
icon: Qt.resolvedUrl("../assets/danklogo2.svg"),
comment: "Manage DMS configuration",
action: "ipc:settings",
categories: ["Settings", "System"],
isCore: true
},
{
name: "DMS Notepad",
icon: "material:description",
comment: "Quick notes",
action: "ipc:notepad",
categories: ["Office", "Utility"],
isCore: true
},
{
name: "DMS System Monitor",
icon: "material:monitor_heart",
comment: "System monitor and process list",
action: "ipc:processlist",
categories: ["System", "Monitor"],
isCore: true
readonly property string dmsLogoPath: Qt.resolvedUrl("../assets/danklogo2.svg")
readonly property var builtInPlugins: ({
"dms_settings": {
id: "dms_settings",
name: I18n.tr("Settings", "settings window title"),
icon: "svg+corner:" + dmsLogoPath + "|settings",
cornerIcon: "settings",
comment: "DMS",
action: "ipc:settings",
categories: ["Settings", "System"],
defaultTrigger: "",
isLauncher: false
},
"dms_notepad": {
id: "dms_notepad",
name: I18n.tr("Notepad", "Notepad"),
icon: "svg+corner:" + dmsLogoPath + "|description",
cornerIcon: "description",
comment: "DMS",
action: "ipc:notepad",
categories: ["Office", "Utility"],
defaultTrigger: "",
isLauncher: false
},
"dms_sysmon": {
id: "dms_sysmon",
name: I18n.tr("System Monitor", "sysmon window title"),
icon: "svg+corner:" + dmsLogoPath + "|monitor_heart",
cornerIcon: "monitor_heart",
comment: "DMS",
action: "ipc:processlist",
categories: ["System", "Monitor"],
defaultTrigger: "",
isLauncher: false
},
"dms_settings_search": {
id: "dms_settings_search",
name: I18n.tr("Settings", "settings window title"),
cornerIcon: "search",
comment: "DMS",
defaultTrigger: "?",
isLauncher: true
}
})
function getBuiltInPluginTrigger(pluginId) {
const plugin = builtInPlugins[pluginId];
if (!plugin)
return null;
return SettingsData.getBuiltInPluginSetting(pluginId, "trigger", plugin.defaultTrigger);
}
readonly property var coreApps: {
SettingsData.builtInPluginSettings;
const apps = [];
for (const pluginId in builtInPlugins) {
if (!SettingsData.getBuiltInPluginSetting(pluginId, "enabled", true))
continue;
const plugin = builtInPlugins[pluginId];
if (plugin.isLauncher)
continue;
apps.push({
name: plugin.name,
icon: plugin.icon,
comment: plugin.comment,
action: plugin.action,
categories: plugin.categories,
isCore: true,
builtInPluginId: pluginId
});
}
]
return apps;
}
function getBuiltInLauncherPlugins() {
const result = {};
for (const pluginId in builtInPlugins) {
const plugin = builtInPlugins[pluginId];
if (!plugin.isLauncher)
continue;
if (!SettingsData.getBuiltInPluginSetting(pluginId, "enabled", true))
continue;
result[pluginId] = plugin;
}
return result;
}
function getBuiltInLauncherTriggers() {
const triggers = {};
const launchers = getBuiltInLauncherPlugins();
for (const pluginId in launchers) {
const trigger = getBuiltInPluginTrigger(pluginId);
if (trigger && trigger.trim() !== "")
triggers[trigger] = pluginId;
}
return triggers;
}
function getBuiltInLauncherPluginsWithEmptyTrigger() {
const result = [];
const launchers = getBuiltInLauncherPlugins();
for (const pluginId in launchers) {
const trigger = getBuiltInPluginTrigger(pluginId);
if (!trigger || trigger.trim() === "")
result.push(pluginId);
}
return result;
}
function getBuiltInLauncherItems(pluginId, query) {
if (pluginId !== "dms_settings_search")
return [];
SettingsSearchService.search(query);
const results = SettingsSearchService.results;
const items = [];
for (let i = 0; i < results.length; i++) {
const r = results[i];
items.push({
name: r.label,
icon: "material:" + r.icon,
comment: r.category,
action: "settings_nav:" + r.tabIndex + ":" + r.section,
categories: ["Settings"],
isCore: true,
isBuiltInLauncher: true,
builtInPluginId: pluginId
});
}
return items;
}
function executeBuiltInLauncherItem(item) {
if (!item?.action)
return false;
const parts = item.action.split(":");
if (parts[0] !== "settings_nav")
return false;
const tabIndex = parseInt(parts[1]);
const section = parts.slice(2).join(":");
SettingsSearchService.navigateToSection(section);
PopoutService.openSettingsWithTabIndex(tabIndex);
return true;
}
function getCoreApps(query) {
if (!query || query.length === 0) {
if (!query || query.length === 0)
return coreApps;
}
const lowerQuery = query.toLowerCase();
return coreApps.filter(app => {
return app.name.toLowerCase().includes(lowerQuery) || app.comment.toLowerCase().includes(lowerQuery);
});
return coreApps.filter(app => app.name.toLowerCase().includes(lowerQuery) || app.comment.toLowerCase().includes(lowerQuery));
}
function executeCoreApp(app) {
if (!app || !app.action) {
if (!app?.action)
return false;
}
const actionParts = app.action.split(":");
const actionType = actionParts[0];
const actionTarget = actionParts[1];
const parts = app.action.split(":");
if (parts[0] !== "ipc")
return false;
if (actionType === "ipc") {
if (actionTarget === "settings") {
Quickshell.execDetached(["dms", "ipc", "call", "settings", "toggle"]);
return true;
} else if (actionTarget === "notepad") {
Quickshell.execDetached(["dms", "ipc", "call", "notepad", "toggle"]);
return true;
} else if (actionTarget === "processlist") {
Quickshell.execDetached(["dms", "ipc", "call", "processlist", "focusOrToggle"]);
return true;
}
switch (parts[1]) {
case "settings":
PopoutService.focusOrToggleSettings();
return true;
case "notepad":
PopoutService.openNotepad();
return true;
case "processlist":
PopoutService.showProcessListModal();
return true;
}
return false;
}

View File

@@ -23,6 +23,7 @@ Singleton {
property var spotlightModal: null
property var powerMenuModal: null
property var processListModal: null
property var processListModalLoader: null
property var colorPickerModal: null
property var notificationModal: null
property var wifiPasswordModal: null
@@ -201,6 +202,7 @@ Singleton {
property bool _settingsWantsToggle: false
property string _settingsPendingTab: ""
property int _settingsPendingTabIndex: -1
function openSettings() {
if (settingsModal) {
@@ -225,6 +227,19 @@ Singleton {
}
}
function openSettingsWithTabIndex(tabIndex: int) {
if (settingsModal) {
settingsModal.showWithTab(tabIndex);
return;
}
if (settingsModalLoader) {
_settingsPendingTabIndex = tabIndex;
_settingsWantsOpen = true;
_settingsWantsToggle = false;
settingsModalLoader.activeAsync = true;
}
}
function closeSettings() {
settingsModal?.close();
}
@@ -302,7 +317,10 @@ Singleton {
function _onSettingsModalLoaded() {
if (_settingsWantsOpen) {
_settingsWantsOpen = false;
if (_settingsPendingTab) {
if (_settingsPendingTabIndex >= 0) {
settingsModal?.showWithTab(_settingsPendingTabIndex);
_settingsPendingTabIndex = -1;
} else if (_settingsPendingTab) {
settingsModal?.showWithTabName(_settingsPendingTab);
_settingsPendingTab = "";
} else {
@@ -312,7 +330,10 @@ Singleton {
}
if (_settingsWantsToggle) {
_settingsWantsToggle = false;
if (_settingsPendingTab) {
if (_settingsPendingTabIndex >= 0) {
settingsModal.currentTabIndex = _settingsPendingTabIndex;
_settingsPendingTabIndex = -1;
} else if (_settingsPendingTab) {
var idx = settingsModal?.resolveTabIndex(_settingsPendingTab) ?? -1;
if (idx >= 0)
settingsModal.currentTabIndex = idx;
@@ -357,7 +378,12 @@ Singleton {
}
function showProcessListModal() {
processListModal?.show();
if (processListModal) {
processListModal.show();
} else if (processListModalLoader) {
processListModalLoader.active = true;
Qt.callLater(() => processListModal?.show());
}
}
function hideProcessListModal() {
@@ -365,7 +391,12 @@ Singleton {
}
function toggleProcessListModal() {
processListModal?.toggle();
if (processListModal) {
processListModal.toggle();
} else if (processListModalLoader) {
processListModalLoader.active = true;
Qt.callLater(() => processListModal?.show());
}
}
function showColorPicker() {

View File

@@ -24,9 +24,21 @@ Item {
readonly property bool isMaterial: iconValue.startsWith("material:")
readonly property bool isUnicode: iconValue.startsWith("unicode:")
readonly property bool isSvgCorner: iconValue.startsWith("svg+corner:")
readonly property bool isSvg: !isSvgCorner && iconValue.startsWith("svg:")
readonly property string materialName: isMaterial ? iconValue.substring(9) : ""
readonly property string unicodeChar: isUnicode ? iconValue.substring(8) : ""
readonly property string iconPath: isMaterial || isUnicode ? "" : Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue)
readonly property string svgSource: {
if (isSvgCorner) {
const parts = iconValue.substring(11).split("|");
return parts[0] || "";
}
if (isSvg)
return iconValue.substring(4);
return "";
}
readonly property string svgCornerIcon: isSvgCorner ? (iconValue.substring(11).split("|")[1] || "") : ""
readonly property string iconPath: isMaterial || isUnicode || isSvg || isSvgCorner ? "" : Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue)
visible: iconValue !== undefined && iconValue !== ""
@@ -46,6 +58,14 @@ Item {
visible: root.isUnicode
}
DankSVGIcon {
anchors.centerIn: parent
source: root.svgSource
size: root.iconSize
cornerIcon: root.svgCornerIcon
visible: root.isSvg || root.isSvgCorner
}
IconImage {
id: iconImg
@@ -53,7 +73,7 @@ Item {
source: root.iconPath
smooth: true
asynchronous: true
visible: !root.isMaterial && !root.isUnicode && root.iconPath !== "" && status === Image.Ready
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && root.iconPath !== "" && status === Image.Ready
}
Rectangle {
@@ -64,7 +84,7 @@ Item {
anchors.rightMargin: root.fallbackRightMargin
anchors.topMargin: root.fallbackTopMargin
anchors.bottomMargin: root.fallbackBottomMargin
visible: !root.isMaterial && !root.isUnicode && (root.iconPath === "" || iconImg.status !== Image.Ready)
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && (root.iconPath === "" || iconImg.status !== Image.Ready)
color: root.fallbackBackgroundColor
radius: Theme.cornerRadius
border.width: 0

View File

@@ -0,0 +1,81 @@
import QtQuick
import QtQuick.Effects
import Quickshell.Widgets
import qs.Common
Item {
id: root
required property string source
property int size: 24
property string cornerIcon: ""
property int cornerIconSize: Math.max(10, size * 0.4)
property color cornerIconColor: Theme.surfaceText
property color cornerIconBackground: Theme.surface
property color colorOverride: "transparent"
property real brightnessOverride: 0.0
property real contrastOverride: 0.0
property real saturationOverride: 0.0
readonly property bool hasCornerIcon: cornerIcon !== ""
readonly property bool hasColorOverride: colorOverride.a > 0
readonly property bool hasColorEffect: hasColorOverride || brightnessOverride !== 0.0 || contrastOverride !== 0.0 || saturationOverride !== 0.0
readonly property string resolvedSource: {
if (!source)
return "";
if (source.startsWith("file://"))
return source;
if (source.startsWith("/"))
return "file://" + source;
if (source.startsWith("qrc:"))
return source;
return source;
}
implicitWidth: size
implicitHeight: size
IconImage {
id: iconImage
anchors.fill: parent
source: root.resolvedSource
smooth: true
mipmap: true
asynchronous: true
implicitSize: root.size * 2
backer.sourceSize.width: root.size * 2
backer.sourceSize.height: root.size * 2
backer.cache: true
layer.enabled: root.hasColorEffect
layer.smooth: true
layer.mipmap: true
layer.effect: MultiEffect {
saturation: root.saturationOverride
colorization: root.hasColorOverride ? 1 : 0
colorizationColor: root.colorOverride
brightness: root.brightnessOverride
contrast: root.contrastOverride
}
}
Rectangle {
visible: root.hasCornerIcon
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: -2
anchors.bottomMargin: -2
width: root.cornerIconSize + 4
height: root.cornerIconSize + 4
radius: width / 2
color: root.cornerIconBackground
border.width: 1
border.color: Theme.surfaceLight
DankIcon {
anchors.centerIn: parent
name: root.cornerIcon
size: root.cornerIconSize
color: root.cornerIconColor
}
}
}

View File

@@ -45,8 +45,12 @@ apply_gtk4_colors() {
exit 1
fi
if [ -f "$gtk_css" ]; then
sed -i '/^@import url.*dank-colors\.css.*);$/d' "$gtk_css"
if [ -f "$gtk_css" ] && grep -q '^@import url.*dank-colors\.css.*);$' "$gtk_css"; then
echo "GTK4 import already exists"
return
fi
if [ -f "$gtk_css" ] && [ -s "$gtk_css" ]; then
sed -i "1i\\$gtk4_import" "$gtk_css"
else
echo "$gtk4_import" >"$gtk_css"