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

launcher: support for plugins to define context menus

fixes #1279
This commit is contained in:
bbedward
2026-01-06 10:07:46 -05:00
parent 1c8ce46f25
commit 45818b202f
6 changed files with 179 additions and 45 deletions

View File

@@ -57,6 +57,7 @@ var keybindsRemoveCmd = &cobra.Command{
} }
func init() { func init() {
keybindsListCmd.Flags().BoolP("json", "j", false, "Output as JSON")
keybindsShowCmd.Flags().String("path", "", "Override config path for the provider") keybindsShowCmd.Flags().String("path", "", "Override config path for the provider")
keybindsSetCmd.Flags().String("desc", "", "Description for hotkey overlay") keybindsSetCmd.Flags().String("desc", "", "Description for hotkey overlay")
keybindsSetCmd.Flags().Bool("allow-when-locked", false, "Allow when screen is locked") keybindsSetCmd.Flags().Bool("allow-when-locked", false, "Allow when screen is locked")
@@ -110,12 +111,21 @@ func initializeProviders() {
} }
} }
func runKeybindsList(_ *cobra.Command, _ []string) { func runKeybindsList(cmd *cobra.Command, _ []string) {
providerList := keybinds.GetDefaultRegistry().List() providerList := keybinds.GetDefaultRegistry().List()
asJSON, _ := cmd.Flags().GetBool("json")
if asJSON {
output, _ := json.Marshal(providerList)
fmt.Fprintln(os.Stdout, string(output))
return
}
if len(providerList) == 0 { if len(providerList) == 0 {
fmt.Fprintln(os.Stdout, "No providers available") fmt.Fprintln(os.Stdout, "No providers available")
return return
} }
fmt.Fprintln(os.Stdout, "Available providers:") fmt.Fprintln(os.Stdout, "Available providers:")
for _, name := range providerList { for _, name := range providerList {
fmt.Fprintf(os.Stdout, " - %s\n", name) fmt.Fprintf(os.Stdout, " - %s\n", name)

View File

@@ -151,7 +151,7 @@ Item {
fileSearchController.openSelected(); fileSearchController.openSelected();
} }
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Menu || event.key == Qt.Key_F10) { } else if (event.key === Qt.Key_Menu || event.key == Qt.Key_F10 || (event.key === Qt.Key_Right && searchMode === "apps" && appLauncher.viewMode === "list")) {
if (searchMode === "apps" && appLauncher.model.count > 0) { if (searchMode === "apps" && appLauncher.model.count > 0) {
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex); const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item; const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;

View File

@@ -11,12 +11,88 @@ Item {
property int selectedMenuIndex: 0 property int selectedMenuIndex: 0
property bool keyboardNavigation: false property bool keyboardNavigation: false
signal hideRequested() signal hideRequested
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
readonly property var actualItem: (currentApp && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
function getPluginContextMenuActions() {
if (!currentApp || !currentApp.isPlugin || !actualItem)
return [];
const pluginId = appLauncher.getPluginIdForItem(actualItem);
if (!pluginId) {
console.log("[ContextMenu] No pluginId found for item:", JSON.stringify(actualItem.categories));
return [];
}
const instance = PluginService.pluginInstances[pluginId];
if (!instance) {
console.log("[ContextMenu] No instance for pluginId:", pluginId);
return [];
}
if (typeof instance.getContextMenuActions !== "function") {
console.log("[ContextMenu] Instance has no getContextMenuActions:", pluginId);
return [];
}
const actions = instance.getContextMenuActions(actualItem);
if (!Array.isArray(actions))
return [];
return actions;
}
function executePluginAction(actionData) {
if (!currentApp || !actualItem)
return;
const pluginId = appLauncher.getPluginIdForItem(actualItem);
if (!pluginId)
return;
const instance = PluginService.pluginInstances[pluginId];
if (!instance)
return;
if (typeof actionData === "function") {
actionData();
} else if (typeof instance.executeContextMenuAction === "function") {
instance.executeContextMenuAction(actualItem, actionData);
}
if (appLauncher)
appLauncher.updateFilteredModel();
hideRequested();
}
readonly property var menuItems: { readonly property var menuItems: {
const items = []; const items = [];
if (currentApp && currentApp.isPlugin) {
const pluginActions = getPluginContextMenuActions();
for (let i = 0; i < pluginActions.length; i++) {
const act = pluginActions[i];
items.push({
type: "item",
icon: act.icon || "",
text: act.text || act.name || "",
action: () => executePluginAction(act.action)
});
}
if (items.length === 0) {
items.push({
type: "item",
icon: "content_copy",
text: I18n.tr("Copy"),
action: launchCurrentApp
});
}
return items;
}
const appId = desktopEntry ? (desktopEntry.id || desktopEntry.execString || "") : ""; const appId = desktopEntry ? (desktopEntry.id || desktopEntry.execString || "") : "";
const isPinned = SessionData.isPinnedApp(appId); const isPinned = SessionData.isPinnedApp(appId);
@@ -172,18 +248,25 @@ Item {
focus: keyboardNavigation focus: keyboardNavigation
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.key === Qt.Key_Down) { switch (event.key) {
case Qt.Key_Down:
selectNext(); selectNext();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Up) { break;
case Qt.Key_Up:
selectPrevious(); selectPrevious();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { break;
case Qt.Key_Return:
case Qt.Key_Enter:
activateSelected(); activateSelected();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Escape) { break;
case Qt.Key_Escape:
case Qt.Key_Left:
hideRequested(); hideRequested();
event.accepted = true; event.accepted = true;
break;
} }
} }

View File

@@ -205,7 +205,8 @@ Item {
"isPlugin": isPluginItem, "isPlugin": isPluginItem,
"isCore": app.isCore === true, "isCore": app.isCore === true,
"isBuiltInLauncher": app.isBuiltInLauncher === true, "isBuiltInLauncher": app.isBuiltInLauncher === true,
"appIndex": uniqueApps.length - 1 "appIndex": uniqueApps.length - 1,
"pinned": app._pinned === true
}); });
} }
}); });
@@ -350,7 +351,11 @@ Item {
function checkPluginTriggers(query) { function checkPluginTriggers(query) {
if (!query) if (!query)
return { triggered: false, pluginCategory: "", query: "" }; return {
triggered: false,
pluginCategory: "",
query: ""
};
const builtInTriggers = AppSearchService.getBuiltInLauncherTriggers(); const builtInTriggers = AppSearchService.getBuiltInLauncherTriggers();
for (const trigger in builtInTriggers) { for (const trigger in builtInTriggers) {
@@ -371,7 +376,11 @@ Item {
} }
if (typeof PluginService === "undefined") if (typeof PluginService === "undefined")
return { triggered: false, pluginCategory: "", query: "" }; return {
triggered: false,
pluginCategory: "",
query: ""
};
const triggers = PluginService.getAllPluginTriggers(); const triggers = PluginService.getAllPluginTriggers();
for (const trigger in triggers) { for (const trigger in triggers) {
@@ -391,7 +400,11 @@ Item {
}; };
} }
return { triggered: false, pluginCategory: "", query: "" }; return {
triggered: false,
pluginCategory: "",
query: ""
};
} }
function getPluginIdForItem(item) { function getPluginIdForItem(item) {

View File

@@ -39,22 +39,39 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
AppIconRenderer { Item {
property int computedIconSize: Math.min(root.maxIconSize, Math.max(root.minIconSize, root.cellWidth * root.iconSizeRatio)) width: iconRenderer.computedIconSize
height: iconRenderer.computedIconSize
width: computedIconSize
height: computedIconSize
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
iconValue: (model.icon && model.icon !== "") ? model.icon : ""
iconSize: computedIconSize AppIconRenderer {
fallbackText: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A" id: iconRenderer
materialIconSizeAdjustment: root.iconMaterialSizeAdjustment property int computedIconSize: Math.min(root.maxIconSize, Math.max(root.minIconSize, root.cellWidth * root.iconSizeRatio))
unicodeIconScale: root.iconUnicodeScale
fallbackTextScale: Math.min(28, computedIconSize * 0.5) / computedIconSize width: computedIconSize
iconMargins: 0 height: computedIconSize
fallbackLeftMargin: root.iconFallbackLeftMargin iconValue: (model.icon && model.icon !== "") ? model.icon : ""
fallbackRightMargin: root.iconFallbackRightMargin iconSize: computedIconSize
fallbackBottomMargin: root.iconFallbackBottomMargin fallbackText: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
materialIconSizeAdjustment: root.iconMaterialSizeAdjustment
unicodeIconScale: root.iconUnicodeScale
fallbackTextScale: Math.min(28, computedIconSize * 0.5) / computedIconSize
iconMargins: 0
fallbackLeftMargin: root.iconFallbackLeftMargin
fallbackRightMargin: root.iconFallbackRightMargin
fallbackBottomMargin: root.iconFallbackBottomMargin
}
DankIcon {
visible: model.pinned === true
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: -4
anchors.bottomMargin: -4
name: "push_pin"
size: 14
color: Theme.primary
}
} }
StyledText { StyledText {
@@ -95,13 +112,11 @@ Rectangle {
} }
} }
onPressAndHold: mouse => { onPressAndHold: mouse => {
if (!root.isPlugin) { const globalPos = mapToItem(null, mouse.x, mouse.y);
const globalPos = mapToItem(null, mouse.x, mouse.y); root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
}
} }
onPressed: mouse => { onPressed: mouse => {
if (mouse.button === Qt.RightButton && !root.isPlugin) { if (mouse.button === Qt.RightButton) {
const globalPos = mapToItem(null, mouse.x, mouse.y); const globalPos = mapToItem(null, mouse.x, mouse.y);
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y); root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
mouse.accepted = true; mouse.accepted = true;

View File

@@ -62,16 +62,31 @@ Rectangle {
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - root.iconSize - Theme.spacingL) : parent.width width: (model.icon !== undefined && model.icon !== "") ? (parent.width - root.iconSize - Theme.spacingL) : parent.width
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { Row {
width: parent.width width: parent.width
text: model.name || "" spacing: Theme.spacingXS
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText StyledText {
font.weight: Font.Medium width: parent.width - (pinIcon.visible ? pinIcon.width + Theme.spacingXS : 0)
elide: Text.ElideRight text: model.name || ""
horizontalAlignment: Text.AlignLeft font.pixelSize: Theme.fontSizeLarge
wrapMode: Text.NoWrap color: Theme.surfaceText
maximumLineCount: 1 font.weight: Font.Medium
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
wrapMode: Text.NoWrap
maximumLineCount: 1
anchors.verticalCenter: parent.verticalCenter
}
DankIcon {
id: pinIcon
visible: model.pinned === true
name: "push_pin"
size: Theme.fontSizeMedium
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
} }
StyledText { StyledText {
@@ -111,13 +126,11 @@ Rectangle {
} }
} }
onPressAndHold: mouse => { onPressAndHold: mouse => {
if (!root.isPlugin) { const globalPos = mapToItem(null, mouse.x, mouse.y);
const globalPos = mapToItem(null, mouse.x, mouse.y); root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
}
} }
onPressed: mouse => { onPressed: mouse => {
if (mouse.button === Qt.RightButton && !root.isPlugin) { if (mouse.button === Qt.RightButton) {
const globalPos = mapToItem(null, mouse.x, mouse.y); const globalPos = mapToItem(null, mouse.x, mouse.y);
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y); root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
mouse.accepted = true; mouse.accepted = true;