Merge pull request #2046 from tibssy/feat/bar-behavior-actions

Feat/bar behavior actions
This commit is contained in:
Lysec
2026-03-04 12:40:17 +01:00
committed by GitHub
11 changed files with 313 additions and 52 deletions
+16
View File
@@ -845,6 +845,22 @@
"behavior-wheel-wrap-label": "Wrap around",
"behavior-workspace-scroll-description": "Choose what the mouse wheel does on empty areas of the bar.",
"behavior-workspace-scroll-label": "Bar mouse wheel action",
"behavior-workspace-scroll-option-content": "Content",
"behavior-workspace-scroll-option-workspace": "Workspace",
"behavior-middle-click-description": "Choose what middle click does on empty areas of the bar.",
"behavior-middle-click-label": "Bar middle click action",
"behavior-middle-click-follow-mouse-description": "Open the selected middle-click panel at the cursor position.",
"behavior-middle-click-follow-mouse-label": "Middle click follow mouse",
"behavior-middle-click-command-description": "Command to execute on middle click.",
"behavior-middle-click-command-label": "Middle click command",
"behavior-middle-click-command-placeholder": "niri msg action toggle-overview",
"behavior-right-click-description": "Choose what right click does on empty areas of the bar.",
"behavior-right-click-label": "Bar right click action",
"behavior-right-click-follow-mouse-description": "Open the selected right-click panel at the cursor position.",
"behavior-right-click-follow-mouse-label": "Right click follow mouse",
"behavior-right-click-command-description": "Command to execute on right click.",
"behavior-right-click-command-label": "Right click command",
"behavior-right-click-command-placeholder": "notify-send \"Right click\"",
"monitor-configure-widgets": "Configure widgets",
"monitor-override-settings": "Override global settings",
"monitor-override-settings-description": "Use custom settings for this monitor.",
+6 -1
View File
@@ -70,6 +70,12 @@
]
},
"mouseWheelAction": "none",
"middleClickAction": "none",
"middleClickFollowMouse": false,
"middleClickCommand": "",
"rightClickAction": "controlCenter",
"rightClickFollowMouse": true,
"rightClickCommand": "",
"reverseScroll": false,
"mouseWheelWrap": true,
"screenOverrides": []
@@ -244,7 +250,6 @@
},
"controlCenter": {
"position": "close_to_bar_button",
"openAtMouseOnBarRightClick": true,
"diskPath": "/",
"shortcuts": {
"left": [
+54 -9
View File
@@ -377,6 +377,60 @@
"subTab": 2,
"subTabLabel": "common.behavior"
},
{
"labelKey": "panels.bar.behavior-middle-click-label",
"descriptionKey": "panels.bar.behavior-middle-click-description",
"widget": "NComboBox",
"tab": 4,
"tabLabel": "panels.bar.title",
"subTab": 2,
"subTabLabel": "common.behavior"
},
{
"labelKey": "panels.bar.behavior-middle-click-follow-mouse-label",
"descriptionKey": "panels.bar.behavior-middle-click-follow-mouse-description",
"widget": "NToggle",
"tab": 4,
"tabLabel": "panels.bar.title",
"subTab": 2,
"subTabLabel": "common.behavior"
},
{
"labelKey": "panels.bar.behavior-middle-click-command-label",
"descriptionKey": "panels.bar.behavior-middle-click-command-description",
"widget": "NTextInput",
"tab": 4,
"tabLabel": "panels.bar.title",
"subTab": 2,
"subTabLabel": "common.behavior"
},
{
"labelKey": "panels.bar.behavior-right-click-label",
"descriptionKey": "panels.bar.behavior-right-click-description",
"widget": "NComboBox",
"tab": 4,
"tabLabel": "panels.bar.title",
"subTab": 2,
"subTabLabel": "common.behavior"
},
{
"labelKey": "panels.bar.behavior-right-click-follow-mouse-label",
"descriptionKey": "panels.bar.behavior-right-click-follow-mouse-description",
"widget": "NToggle",
"tab": 4,
"tabLabel": "panels.bar.title",
"subTab": 2,
"subTabLabel": "common.behavior"
},
{
"labelKey": "panels.bar.behavior-right-click-command-label",
"descriptionKey": "panels.bar.behavior-right-click-command-description",
"widget": "NTextInput",
"tab": 4,
"tabLabel": "panels.bar.title",
"subTab": 2,
"subTabLabel": "common.behavior"
},
{
"labelKey": "panels.bar.monitor-override-settings",
"descriptionKey": "panels.bar.monitor-override-settings-description",
@@ -557,15 +611,6 @@
"subTab": 0,
"subTabLabel": "common.appearance"
},
{
"labelKey": "panels.control-center.open-at-mouse-label",
"descriptionKey": "panels.control-center.open-at-mouse-description",
"widget": "NToggle",
"tab": 7,
"tabLabel": "panels.control-center.title",
"subTab": 0,
"subTabLabel": "common.appearance"
},
{
"labelKey": "panels.control-center.system-monitor-disk-path-label",
"descriptionKey": "panels.control-center.system-monitor-disk-path-description",
+22
View File
@@ -0,0 +1,22 @@
import QtQuick
QtObject {
id: root
function migrate(adapter, logger, rawJson) {
logger.i("Settings", "Migrating settings to v55");
// Check if the old setting exists
if (rawJson.controlCenter && rawJson.controlCenter.openAtMouseOnBarRightClick !== undefined) {
if (!rawJson.bar) rawJson.bar = {};
rawJson.bar.rightClickFollowMouse = rawJson.controlCenter.openAtMouseOnBarRightClick;
delete rawJson.controlCenter.openAtMouseOnBarRightClick;
logger.i("Settings", "Successfully moved openAtMouseOnBarRightClick to bar.rightClickFollowMouse");
}
return true;
}
}
+3 -1
View File
@@ -26,7 +26,8 @@ QtObject {
49: migration49Component,
50: migration50Component,
53: migration53Component,
54: migration54Component
54: migration54Component,
55: migration55Component
})
// Migration components
@@ -50,4 +51,5 @@ QtObject {
property Component migration50Component: Migration50 {}
property Component migration53Component: Migration53 {}
property Component migration54Component: Migration54 {}
property Component migration55Component: Migration55 {}
}
+7 -2
View File
@@ -25,7 +25,7 @@ Singleton {
- Default cache directory: ~/.cache/noctalia
*/
readonly property alias data: adapter // Used to access via Settings.data.xxx.yyy
readonly property int settingsVersion: 54
readonly property int settingsVersion: 55
property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1"
readonly property string shellName: "noctalia"
readonly property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
@@ -259,6 +259,12 @@ Singleton {
property string mouseWheelAction: "none"
property bool reverseScroll: false
property bool mouseWheelWrap: true
property string middleClickAction: "none"
property bool middleClickFollowMouse: false
property string middleClickCommand: ""
property string rightClickAction: "controlCenter"
property bool rightClickFollowMouse: true
property string rightClickCommand: ""
// Per-screen overrides for position and widgets
// Format: [{ "name": "HDMI-1", "position": "left" }, { "name": "DP-1", "position": "bottom", "widgets": {...} }]
property list<var> screenOverrides: []
@@ -438,7 +444,6 @@ Singleton {
property JsonObject controlCenter: JsonObject {
// Position: close_to_bar_button, center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center
property string position: "close_to_bar_button"
property bool openAtMouseOnBarRightClick: true
property string diskPath: "/"
property JsonObject shortcuts
shortcuts: JsonObject {
+74 -14
View File
@@ -7,6 +7,7 @@ import Quickshell.Wayland
import qs.Commons
import qs.Modules.Bar.Extras
import qs.Modules.Notification
import qs.Modules.Panels.Settings
import qs.Services.Compositor
import qs.Services.UI
import qs.Widgets
@@ -187,6 +188,7 @@ Item {
readonly property string barWheelAction: {
return Settings.data.bar.mouseWheelAction || "none";
}
readonly property string barRightClickAction: Settings.data.bar.rightClickAction || "controlCenter"
// Position and size the bar content based on orientation
x: (root.barPosition === "right") ? (parent.width - root.barHeight) : 0
@@ -334,25 +336,83 @@ Item {
CompositorService.switchToWorkspace(candidates[next]);
}
function handleEmptyBarClick(action, followMouse, command, mouse) {
if (action === "none")
return;
if (action === "controlCenter") {
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen);
controlCenterPanel?.toggle(null, followMouse ? mapToItem(null, mouse.x, mouse.y) : "ControlCenter");
mouse.accepted = true;
} else if (action === "settings") {
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
settingsPanel?.toggle(null, followMouse ? mapToItem(null, mouse.x, mouse.y) : null);
mouse.accepted = true;
} else if (action === "launcherPanel") {
var launcherPanel = PanelService.getPanel("launcherPanel", screen);
launcherPanel?.toggle(null, followMouse ? mapToItem(null, mouse.x, mouse.y) : null);
mouse.accepted = true;
} else if (action === "command") {
runCustomCommand(command);
mouse.accepted = true;
}
}
function runCustomCommand(command) {
if (!command || command.trim() === "")
return;
const processString = "import QtQuick; import Quickshell.Io; Process { command: [\"sh\", \"-lc\", \"\"] }";
try {
const processObj = Qt.createQmlObject(processString, root, "BarCommandProcess_" + Date.now());
processObj.command = ["sh", "-lc", command];
processObj.exited.connect(function (exitCode) {
if (exitCode !== 0) {
ToastService.showError(
I18n.tr("toast.custom-command-failed.title"),
I18n.tr("toast.custom-command-failed.description", {
command: command,
code: exitCode
})
);
}
processObj.destroy();
});
processObj.running = true;
} catch (e) {
Logger.e("Bar", "Failed to start custom command:", e);
ToastService.showError(
I18n.tr("toast.custom-command-failed.title"),
I18n.tr("toast.custom-command-failed.description", {
command: command,
code: "start_error"
})
);
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
acceptedButtons: Qt.RightButton | Qt.MiddleButton
enabled: bar.barRightClickAction !== "none" || Settings.data.bar.middleClickAction !== "none"
hoverEnabled: false
preventStealing: true
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
if (bar.isPointOverWidget(mouse.x, mouse.y))
return;
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen);
if (Settings.data.controlCenter.openAtMouseOnBarRightClick) {
var screenRelativePos = mapToItem(null, mouse.x, mouse.y);
controlCenterPanel?.toggle(null, screenRelativePos);
} else {
controlCenterPanel?.toggle();
}
mouse.accepted = true;
}
}
if (mouse.button === Qt.RightButton) {
if (bar.isPointOverWidget(mouse.x, mouse.y))
return;
bar.handleEmptyBarClick(bar.barRightClickAction, Settings.data.bar.rightClickFollowMouse, Settings.data.bar.rightClickCommand, mouse);
return;
}
if (mouse.button === Qt.MiddleButton) {
if (bar.isPointOverWidget(mouse.x, mouse.y))
return;
bar.handleEmptyBarClick(Settings.data.bar.middleClickAction || "none", Settings.data.bar.middleClickFollowMouse, Settings.data.bar.middleClickCommand, mouse);
return;
}
}
}
// Debounce timer for wheel interactions
+6 -6
View File
@@ -66,12 +66,12 @@ SmartPanel {
return Settings.data.appLauncher.position;
}
}
panelAnchorHorizontalCenter: panelPosition === "center" || panelPosition.endsWith("_center")
panelAnchorVerticalCenter: panelPosition === "center"
panelAnchorLeft: panelPosition !== "center" && panelPosition.endsWith("_left")
panelAnchorRight: panelPosition !== "center" && panelPosition.endsWith("_right")
panelAnchorBottom: panelPosition.startsWith("bottom_")
panelAnchorTop: panelPosition.startsWith("top_")
panelAnchorHorizontalCenter: !root.useButtonPosition && (panelPosition === "center" || panelPosition.endsWith("_center"))
panelAnchorVerticalCenter: !root.useButtonPosition && panelPosition === "center"
panelAnchorLeft: !root.useButtonPosition && panelPosition !== "center" && panelPosition.endsWith("_left")
panelAnchorRight: !root.useButtonPosition && panelPosition !== "center" && panelPosition.endsWith("_right")
panelAnchorBottom: !root.useButtonPosition && panelPosition.startsWith("bottom_")
panelAnchorTop: !root.useButtonPosition && panelPosition.startsWith("top_")
panelContent: Rectangle {
id: ui
+16 -8
View File
@@ -25,12 +25,12 @@ SmartPanel {
readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0
forceAttachToBar: attachToBar
panelAnchorHorizontalCenter: attachToBar ? (barPosition === "top" || barPosition === "bottom") : true
panelAnchorVerticalCenter: attachToBar ? (barPosition === "left" || barPosition === "right") : true
panelAnchorTop: attachToBar && barPosition === "top"
panelAnchorBottom: attachToBar && barPosition === "bottom"
panelAnchorLeft: attachToBar && barPosition === "left"
panelAnchorRight: attachToBar && barPosition === "right"
panelAnchorHorizontalCenter: !root.useButtonPosition && (attachToBar ? (barPosition === "top" || barPosition === "bottom") : true)
panelAnchorVerticalCenter: !root.useButtonPosition && (attachToBar ? (barPosition === "left" || barPosition === "right") : true)
panelAnchorTop: !root.useButtonPosition && attachToBar && barPosition === "top"
panelAnchorBottom: !root.useButtonPosition && attachToBar && barPosition === "bottom"
panelAnchorLeft: !root.useButtonPosition && attachToBar && barPosition === "left"
panelAnchorRight: !root.useButtonPosition && attachToBar && barPosition === "right"
onAttachToBarChanged: {
if (isPanelOpen) {
@@ -129,7 +129,15 @@ SmartPanel {
// Panel mode: replicate SmartPanel.open() logic
if (!buttonItem && buttonName) {
buttonItem = BarService.lookupWidget(buttonName, screen.name);
if (typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined) {
root.buttonItem = null;
root.buttonPosition = buttonName;
root.buttonWidth = 0;
root.buttonHeight = 0;
root.useButtonPosition = true;
} else {
buttonItem = BarService.lookupWidget(buttonName, screen.name);
}
}
if (buttonItem) {
@@ -139,7 +147,7 @@ SmartPanel {
root.buttonWidth = buttonItem.width;
root.buttonHeight = buttonItem.height;
root.useButtonPosition = true;
} else {
} else if (!(buttonName && typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined)) {
root.buttonItem = null;
root.useButtonPosition = false;
}
@@ -11,6 +11,8 @@ ColumnLayout {
Layout.fillWidth: true
readonly property string effectiveWheelAction: Settings.data.bar.mouseWheelAction || "none"
readonly property string effectiveMiddleClickAction: Settings.data.bar.middleClickAction || "none"
readonly property string effectiveRightClickAction: Settings.data.bar.rightClickAction || "controlCenter"
NComboBox {
Layout.fillWidth: true
@@ -20,17 +22,17 @@ ColumnLayout {
var items = [
{
"key": "none",
"name": "Nothing"
"name": I18n.tr("common.none")
},
{
"key": "workspace",
"name": "Workspace"
"name": I18n.tr("panels.bar.behavior-workspace-scroll-option-workspace")
}
];
if (CompositorService.isNiri) {
items.push({
"key": "content",
"name": "Content"
"name": I18n.tr("panels.bar.behavior-workspace-scroll-option-content")
});
}
return items;
@@ -59,4 +61,108 @@ ColumnLayout {
onToggled: checked => Settings.data.bar.mouseWheelWrap = checked
visible: Settings.data.bar.mouseWheelAction === "workspace"
}
NComboBox {
Layout.fillWidth: true
label: I18n.tr("panels.bar.behavior-middle-click-label")
description: I18n.tr("panels.bar.behavior-middle-click-description")
model: [
{
"key": "none",
"name": I18n.tr("common.none")
},
{
"key": "controlCenter",
"name": I18n.tr("tooltips.open-control-center")
},
{
"key": "settings",
"name": I18n.tr("tooltips.open-settings")
},
{
"key": "launcherPanel",
"name": I18n.tr("actions.open-launcher")
},
{
"key": "command",
"name": I18n.tr("actions.run-custom-command")
}
]
currentKey: root.effectiveMiddleClickAction
defaultValue: Settings.getDefaultValue("bar.middleClickAction")
onSelected: key => Settings.data.bar.middleClickAction = key
}
NTextInput {
Layout.fillWidth: true
label: I18n.tr("panels.bar.behavior-middle-click-command-label")
description: I18n.tr("panels.bar.behavior-middle-click-command-description")
placeholderText: I18n.tr("panels.bar.behavior-middle-click-command-placeholder")
text: Settings.data.bar.middleClickCommand
fontFamily: Settings.data.ui.fontFixed
onTextChanged: Settings.data.bar.middleClickCommand = text
visible: Settings.data.bar.middleClickAction === "command"
}
NToggle {
Layout.fillWidth: true
label: I18n.tr("panels.bar.behavior-middle-click-follow-mouse-label")
description: I18n.tr("panels.bar.behavior-middle-click-follow-mouse-description")
checked: Settings.data.bar.middleClickFollowMouse
defaultValue: Settings.getDefaultValue("bar.middleClickFollowMouse")
onToggled: checked => Settings.data.bar.middleClickFollowMouse = checked
visible: Settings.data.bar.middleClickAction !== "none" && Settings.data.bar.middleClickAction !== "command" && !(Settings.data.bar.middleClickAction === "settings" && Settings.data.ui.settingsPanelMode === "window")
}
NComboBox {
Layout.fillWidth: true
label: I18n.tr("panels.bar.behavior-right-click-label")
description: I18n.tr("panels.bar.behavior-right-click-description")
model: [
{
"key": "none",
"name": I18n.tr("common.none")
},
{
"key": "controlCenter",
"name": I18n.tr("tooltips.open-control-center")
},
{
"key": "settings",
"name": I18n.tr("tooltips.open-settings")
},
{
"key": "launcherPanel",
"name": I18n.tr("actions.open-launcher")
},
{
"key": "command",
"name": I18n.tr("actions.run-custom-command")
}
]
currentKey: root.effectiveRightClickAction
defaultValue: Settings.getDefaultValue("bar.rightClickAction")
onSelected: key => Settings.data.bar.rightClickAction = key
}
NTextInput {
Layout.fillWidth: true
label: I18n.tr("panels.bar.behavior-right-click-command-label")
description: I18n.tr("panels.bar.behavior-right-click-command-description")
placeholderText: I18n.tr("panels.bar.behavior-right-click-command-placeholder")
text: Settings.data.bar.rightClickCommand
fontFamily: Settings.data.ui.fontFixed
onTextChanged: Settings.data.bar.rightClickCommand = text
visible: Settings.data.bar.rightClickAction === "command"
}
NToggle {
Layout.fillWidth: true
label: I18n.tr("panels.bar.behavior-right-click-follow-mouse-label")
description: I18n.tr("panels.bar.behavior-right-click-follow-mouse-description")
checked: Settings.data.bar.rightClickFollowMouse
defaultValue: Settings.getDefaultValue("bar.rightClickFollowMouse")
onToggled: checked => Settings.data.bar.rightClickFollowMouse = checked
visible: Settings.data.bar.rightClickAction !== "none" && Settings.data.bar.rightClickAction !== "command" && !(Settings.data.bar.rightClickAction === "settings" && Settings.data.ui.settingsPanelMode === "window")
}
}
@@ -63,14 +63,6 @@ ColumnLayout {
defaultValue: Settings.getDefaultValue("controlCenter.position")
}
NToggle {
label: I18n.tr("panels.control-center.open-at-mouse-label")
description: I18n.tr("panels.control-center.open-at-mouse-description")
checked: Settings.data.controlCenter.openAtMouseOnBarRightClick
onToggled: checked => Settings.data.controlCenter.openAtMouseOnBarRightClick = checked
defaultValue: Settings.getDefaultValue("controlCenter.openAtMouseOnBarRightClick")
}
NComboBox {
id: diskPathComboBox
Layout.fillWidth: true