Skip to content

Widget Actions

What a bar widget does when you click or scroll it is configuration, not something baked into the widget. Every widget accepts an actions table that binds a gesture to a command:

[widget.media.actions]
right = "media toggle"
scroll_up = "volume-up"
middle = "exec notify-send 'Media' 'Middle click'"

Widgets ship with sensible defaults, so you only declare the gestures you want to change. Clearing a binding in the settings editor restores the default.

The Actions section in Settings is shown only while the widget is interactive. With interactive = false, pointer gestures pass through and the section is hidden (bindings in config are left alone).

The vocabulary is fixed. Any other key is a config error.

KeyGesture
leftLeft click
rightRight click
middleMiddle click
backThumb button, back
forwardThumb button, forward
scroll_upWheel or touchpad, up
scroll_downWheel or touchpad, down
scroll_leftWheel or touchpad, left
scroll_rightWheel or touchpad, right

Scroll input is quantized into whole wheel-detent steps. The widget’s scroll_repeat setting decides whether each step fires its action or one gesture fires it once.

Set scroll_repeat under the widget itself:

[widget.workspaces]
scroll_repeat = "steps"
ValueBehavior
autoCycle commands such as workspace, taskbar, media, and power-profile navigation run once per gesture. Ramp commands such as volume and brightness run for every step.
gestureRun any bound scroll action once per gesture.
stepsRun any bound scroll action for every quantized scroll step.

auto is the default. Use steps when one longer touchpad movement or several quick wheel detents should cross multiple workspaces or tasks. Use gesture when noisy hardware should never produce more than one action per movement. Reversing direction always starts a new gesture.

There are no modifier combinations such as ctrl+left. The bar never takes keyboard focus, so it cannot know which modifiers are held.

An action is one of three things:

FormMeaning
<command> [arguments]Run an IPC command, exactly as noctalia msg would
exec <command line>Run a shell command
noneUnbind the gesture
[widget.clock.actions]
left = "panel-toggle control-center calendar" # IPC command with arguments
right = "exec gsimplecal" # shell command
middle = "none" # nothing happens

The command vocabulary is the same one noctalia msg uses, so anything you can bind to a keyboard shortcut you can bind to a widget. Run noctalia msg --help for the full list. The settings editor offers the same commands in a searchable picker, with the argument hint shown in the field beside it.

Reach for exec only when nothing in that list covers what you want. A built-in command is faster, it reports its own errors, and it stays correct when the shell changes underneath it - media toggle is the right way to pause playback, not exec playerctl play-pause.

A misspelled command is reported once in the log, naming the exact config path, and the gesture does nothing. It is never silently treated as a shell command - that is what exec is for.

Bindings resolve in four layers. Later layers win, one gesture at a time:

  1. Built-in defaults - middle opens the widget’s settings, on every widget
  2. Widget defaults - what the widget type does out of the box, e.g. left opens its panel
  3. [bar.<name>.actions] - every widget on that bar
  4. [widget.<name>.actions] - one widget instance

So a bar-wide rule can turn something off everywhere while a single widget keeps its own binding:

[bar.default.actions]
middle = "none" # no middle-click-opens-settings anywhere on this bar
[widget.clock.actions]
middle = "exec gnome-calendar" # …except on the clock

Some widgets handle a gesture on their individual items rather than as a whole, and those cannot be rebound:

WidgetReserved
workspacesleft - activates the workspace you clicked
taskbarleft, middle - activates and closes that window
trayleft, right - activates the item and opens its menu
screenshotright - opens the capture menu anchored to the widget

Every other gesture on those widgets is bindable as usual, and the settings editor simply omits the reserved rows. A bar-wide binding skips them silently; binding one explicitly per widget is reported as a config error.

The parts of the bar no widget covers take the same bindings, under the bar’s dead_zone table:

[bar.default.dead_zone.actions]
left = "panel-toggle launcher"
scroll_up = "volume-up"
back = "media previous"

Right click opens the control center at the pointer unless you bind it to something else. Panel actions here anchor at the pointer rather than at a widget, since there is no widget to anchor to. A monitor override may carry its own dead_zone.actions table, which replaces the bar’s rather than merging with it.

Make the volume widget mute on middle click instead of opening settings, and adjust in bigger steps:

[widget.volume.actions]
middle = "volume-mute"
scroll_up = "volume-up 10%"
scroll_down = "volume-down 10%"

Give the clock a second panel on right click and a calendar app on the thumb button:

[widget.clock.actions]
right = "panel-toggle control-center weather"
forward = "exec gnome-calendar"

Turn a spacer into a hidden hot zone. Spacers are non-interactive by default, so turn Interactive on and bind the gestures you want:

[widget.spacer]
interactive = true
[widget.spacer.actions]
left = "panel-toggle launcher"