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() {
keybindsListCmd.Flags().BoolP("json", "j", false, "Output as JSON")
keybindsShowCmd.Flags().String("path", "", "Override config path for the provider")
keybindsSetCmd.Flags().String("desc", "", "Description for hotkey overlay")
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()
asJSON, _ := cmd.Flags().GetBool("json")
if asJSON {
output, _ := json.Marshal(providerList)
fmt.Fprintln(os.Stdout, string(output))
return
}
if len(providerList) == 0 {
fmt.Fprintln(os.Stdout, "No providers available")
return
}
fmt.Fprintln(os.Stdout, "Available providers:")
for _, name := range providerList {
fmt.Fprintf(os.Stdout, " - %s\n", name)

View File

@@ -151,7 +151,7 @@ Item {
fileSearchController.openSelected();
}
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) {
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;

View File

@@ -11,12 +11,88 @@ Item {
property int selectedMenuIndex: 0
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 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: {
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 isPinned = SessionData.isPinnedApp(appId);
@@ -172,18 +248,25 @@ Item {
focus: keyboardNavigation
Keys.onPressed: event => {
if (event.key === Qt.Key_Down) {
switch (event.key) {
case Qt.Key_Down:
selectNext();
event.accepted = true;
} else if (event.key === Qt.Key_Up) {
break;
case Qt.Key_Up:
selectPrevious();
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();
event.accepted = true;
} else if (event.key === Qt.Key_Escape) {
break;
case Qt.Key_Escape:
case Qt.Key_Left:
hideRequested();
event.accepted = true;
break;
}
}

View File

@@ -205,7 +205,8 @@ Item {
"isPlugin": isPluginItem,
"isCore": app.isCore === 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) {
if (!query)
return { triggered: false, pluginCategory: "", query: "" };
return {
triggered: false,
pluginCategory: "",
query: ""
};
const builtInTriggers = AppSearchService.getBuiltInLauncherTriggers();
for (const trigger in builtInTriggers) {
@@ -371,7 +376,11 @@ Item {
}
if (typeof PluginService === "undefined")
return { triggered: false, pluginCategory: "", query: "" };
return {
triggered: false,
pluginCategory: "",
query: ""
};
const triggers = PluginService.getAllPluginTriggers();
for (const trigger in triggers) {
@@ -391,7 +400,11 @@ Item {
};
}
return { triggered: false, pluginCategory: "", query: "" };
return {
triggered: false,
pluginCategory: "",
query: ""
};
}
function getPluginIdForItem(item) {

View File

@@ -39,22 +39,39 @@ Rectangle {
anchors.centerIn: parent
spacing: Theme.spacingS
AppIconRenderer {
property int computedIconSize: Math.min(root.maxIconSize, Math.max(root.minIconSize, root.cellWidth * root.iconSizeRatio))
width: computedIconSize
height: computedIconSize
Item {
width: iconRenderer.computedIconSize
height: iconRenderer.computedIconSize
anchors.horizontalCenter: parent.horizontalCenter
iconValue: (model.icon && model.icon !== "") ? model.icon : ""
iconSize: computedIconSize
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
AppIconRenderer {
id: iconRenderer
property int computedIconSize: Math.min(root.maxIconSize, Math.max(root.minIconSize, root.cellWidth * root.iconSizeRatio))
width: computedIconSize
height: computedIconSize
iconValue: (model.icon && model.icon !== "") ? model.icon : ""
iconSize: computedIconSize
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 {
@@ -95,13 +112,11 @@ Rectangle {
}
}
onPressAndHold: mouse => {
if (!root.isPlugin) {
const globalPos = mapToItem(null, mouse.x, mouse.y);
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
}
const globalPos = mapToItem(null, mouse.x, mouse.y);
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
}
onPressed: mouse => {
if (mouse.button === Qt.RightButton && !root.isPlugin) {
if (mouse.button === Qt.RightButton) {
const globalPos = mapToItem(null, mouse.x, mouse.y);
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
mouse.accepted = true;

View File

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