Skip to content

Commit

Permalink
[WIP] Execute custom user commands or scripts on a variety of rofi ev…
Browse files Browse the repository at this point in the history
…ents (#2053)

* Implemented custom user command execution on the following menu events: entry selected, entry accepted, menu canceled, menu error, mode changed, screenshot taken

* fixed different signedness comparison warning and compare unfiltered entry index in selection_changed_user_callback

* track previously selected line in RofiViewState

* added documentation about custom scripts to run on certain actions

---------

Co-authored-by: Matteo <[email protected]>
  • Loading branch information
giomatfois62 and Matteo authored Jan 24, 2025
1 parent 5870040 commit bc67d8a
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 2 deletions.
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ if FOUND_PANDOC
generate-manpage: doc/rofi.1\
doc/rofi-sensible-terminal.1\
doc/rofi-theme-selector.1\
doc/rofi-actions.5\
doc/rofi-debugging.5\
doc/rofi-dmenu.5\
doc/rofi-keys.5\
Expand Down
12 changes: 12 additions & 0 deletions config/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ Settings config = {
/** Custom command to generate preview icons */
.preview_cmd = NULL,

/** Custom command to call when menu selection changes */
.on_selection_changed = NULL,
/** Custom command to call when menu mode changes */
.on_mode_changed = NULL,
/** Custom command to call when menu entry is accepted */
.on_entry_accepted = NULL,
/** Custom command to call when menu is canceled */
.on_menu_canceled = NULL,
/** Custom command to call when menu finds errors */
.on_menu_error = NULL,
/** Custom command to call when menu screenshot is taken */
.on_screenshot_taken = NULL,
/** Terminal to use. (for ssh and open in terminal) */
.terminal_emulator = "rofi-sensible-terminal",
.ssh_client = "ssh",
Expand Down
1 change: 1 addition & 0 deletions doc/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ man_files = [
'rofi.1',
'rofi-sensible-terminal.1',
'rofi-theme-selector.1',
'rofi-actions.5',
'rofi-debugging.5',
'rofi-dmenu.5',
'rofi-keys.5',
Expand Down
89 changes: 89 additions & 0 deletions doc/rofi-actions.5.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# rofi-actions(5)

## NAME

**rofi-actions** - Custom commands following interaction with rofi menus

## DESCRIPTION

**rofi** allows to set custom commands or scripts to be executed when some actions are performed in the menu, such as changing selection, accepting an entry or canceling.

This makes it possible for example to play sound effects or read aloud menu entries on selection.

## USAGE

Following is the list of rofi flags for specifying custom commands or scripts to execute on supported actions:

`-on-selection-changed` *cmd*

Command or script to run when the current selection changes. Selected text is forwarded to the command replacing the pattern *{entry}*.

`-on-entry-accepted` *cmd*

Command or script to run when a menu entry is accepted. Accepted text is forwarded to the command replacing the pattern *{entry}*.

`-on-mode-changed` *cmd*

Command or script to run when the menu mode (e.g. drun,window,ssh...) is changed.

`-on-menu-canceled` *cmd*

Command or script to run when the menu is canceled.

`-on-menu-error` *cmd*

Command or script to run when an error menu is shown (e.g. `rofi -e "error message"`). Error text is forwarded to the command replacing the pattern *{error}*.

`-on-screenshot-taken` *cmd*

Command or script to run when a screenshot of rofi is taken. Screenshot path is forwarded to the command replacing the pattern *{path}*.

### Example usage

Rofi command line:

```bash
rofi -on-selection-changed "/path/to/select.sh {entry}" \
-on-entry-accepted "/path/to/accept.sh {entry}" \
-on-menu-canceled "/path/to/exit.sh" \
-on-mode-changed "/path/to/change.sh" \
-on-menu-error "/path/to/error.sh {error}" \
-on-screenshot-taken "/path/to/camera.sh {path}" \
-show drun
```

Rofi config file:

```css
configuration {
on-selection-changed: "/path/to/select.sh {entry}";
on-entry-accepted: "/path/to/accept.sh {entry}";
on-menu-canceled: "/path/to/exit.sh";
on-mode-changed: "/path/to/change.sh";
on-menu-error: "/path/to/error.sh {error}";
on-screenshot-taken: "/path/to/camera.sh {path}";
}
```

### Play sound effects

Here's an example bash script that plays a sound effect using `aplay` when the current selection is changed:

```bash
#!/bin/bash

coproc aplay -q $HOME/Music/selecting_an_item.wav
```

The use of `coproc` for playing sounds is suggested, otherwise the rofi process will wait for sounds to end playback before exiting.

### Read aloud

Here's an example bash script that reads aloud currently selected entries using `espeak`:

```bash
#!/bin/bash

killall espeak
echo "selected: $@" | espeak
```
12 changes: 12 additions & 0 deletions include/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ typedef struct {
/** Custom command to generate preview icons */
char *preview_cmd;

/** Custom command to call when menu selection changes */
char *on_selection_changed;
/** Custom command to call when menu mode changes */
char *on_mode_changed;
/** Custom command to call when menu entry is accepted */
char *on_entry_accepted;
/** Custom command to call when menu is canceled */
char *on_menu_canceled;
/** Custom command to call when menu finds errors */
char *on_menu_error;
/** Custom command to call when menu screenshot is taken */
char *on_screenshot_taken;
/** Terminal to use */
char *terminal_emulator;
/** SSH client to use */
Expand Down
2 changes: 2 additions & 0 deletions include/view-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ struct RofiViewState {
int skip_absorb;
/** The selected line (in the unfiltered list) */
unsigned int selected_line;
/** The previously selected line (in the unfiltered list) */
unsigned int previous_line;
/** The return state of the view */
MenuReturn retv;
/** Monitor #workarea the view is displayed on */
Expand Down
87 changes: 85 additions & 2 deletions source/view.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,18 @@ static int lev_sort(const void *p1, const void *p2, void *arg) {
return distances[*a] - distances[*b];
}

static void screenshot_taken_user_callback(const char *path) {
if (config.on_screenshot_taken == NULL)
return;

char **args = NULL;
int argv = 0;
helper_parse_setup(config.on_screenshot_taken, &args, &argv, "{path}",
path, (char *)0);
if (args != NULL)
helper_execute(NULL, args, "", config.on_screenshot_taken, NULL);
}

/**
* Stores a screenshot of Rofi at that point in time.
*/
Expand Down Expand Up @@ -271,6 +283,7 @@ void rofi_capture_screenshot(void) {
g_warning("Failed to produce screenshot '%s', got error: '%s'", fpath,
cairo_status_to_string(status));
}
screenshot_taken_user_callback(fpath);
}
cairo_destroy(draw);
}
Expand Down Expand Up @@ -1285,17 +1298,37 @@ inline static void rofi_view_nav_last(RofiViewState *state) {
// state->selected = state->filtered_lines - 1;
listview_set_selected(state->list_view, -1);
}
static void selection_changed_user_callback(unsigned int index, RofiViewState *state) {
if (config.on_selection_changed == NULL)
return;

int fstate = 0;
char *text = mode_get_display_value(state->sw, state->line_map[index],
&fstate, NULL, TRUE);
char **args = NULL;
int argv = 0;
helper_parse_setup(config.on_selection_changed, &args, &argv, "{entry}",
text, (char *)0);
if (args != NULL)
helper_execute(NULL, args, "", config.on_selection_changed, NULL);
g_free(text);
}
static void selection_changed_callback(G_GNUC_UNUSED listview *lv,
unsigned int index, void *udata) {
RofiViewState *state = (RofiViewState *)udata;
if (index < state->filtered_lines) {
if (state->previous_line != state->line_map[index]) {
selection_changed_user_callback(index, state);
state->previous_line = state->line_map[index];
}
}
if (state->tb_current_entry) {
if (index < state->filtered_lines) {
int fstate = 0;
char *text = mode_get_display_value(state->sw, state->line_map[index],
&fstate, NULL, TRUE);
textbox_text(state->tb_current_entry, text);
g_free(text);

} else {
textbox_text(state->tb_current_entry, "");
}
Expand Down Expand Up @@ -1882,7 +1915,6 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
// Nothing entered and nothing selected.
state->retv = MENU_CUSTOM_INPUT;
}

state->quit = TRUE;
break;
}
Expand Down Expand Up @@ -2086,8 +2118,43 @@ void rofi_view_handle_mouse_motion(RofiViewState *state, gint x, gint y,
}
}

static void rofi_quit_user_callback(RofiViewState *state) {
if (state->retv & MENU_OK) {
if (config.on_entry_accepted == NULL)
return;
int fstate = 0;
unsigned int selected = listview_get_selected(state->list_view);
// TODO: handle custom text
if (selected >= state->filtered_lines)
return;
// Pass selected text to custom command
char *text = mode_get_display_value(state->sw, state->line_map[selected],
&fstate, NULL, TRUE);
char **args = NULL;
int argv = 0;
helper_parse_setup(config.on_entry_accepted, &args, &argv, "{entry}",
text, (char *)0);
if (args != NULL)
helper_execute(NULL, args, "", config.on_entry_accepted, NULL);
g_free(text);
} else if(state->retv & MENU_CANCEL) {
if (config.on_menu_canceled == NULL)
return;
helper_execute_command(NULL, config.on_menu_canceled, FALSE, NULL);
} else if (state->retv & MENU_NEXT ||
state->retv & MENU_PREVIOUS ||
state->retv & MENU_QUICK_SWITCH ||
state->retv & MENU_COMPLETE) {
if (config.on_mode_changed == NULL)
return;
// TODO: pass mode name to custom command
helper_execute_command(NULL, config.on_mode_changed, FALSE, NULL);
}
}
void rofi_view_maybe_update(RofiViewState *state) {
if (rofi_view_get_completed(state)) {
// Exec custom user commands
rofi_quit_user_callback(state);
// This menu is done.
rofi_view_finalize(state);
// If there a state. (for example error) reload it.
Expand Down Expand Up @@ -2482,6 +2549,7 @@ RofiViewState *rofi_view_create(Mode *sw, const char *input,
state->menu_flags = menu_flags;
state->sw = sw;
state->selected_line = UINT32_MAX;
state->previous_line = UINT32_MAX;
state->retv = MENU_CANCEL;
state->distance = NULL;
state->quit = FALSE;
Expand Down Expand Up @@ -2571,6 +2639,18 @@ RofiViewState *rofi_view_create(Mode *sw, const char *input,
return state;
}

static void rofi_error_user_callback(const char *msg) {
if (config.on_menu_error == NULL)
return;

char **args = NULL;
int argv = 0;
helper_parse_setup(config.on_menu_error, &args, &argv, "{error}",
msg, (char *)0);
if (args != NULL)
helper_execute(NULL, args, "", config.on_menu_error, NULL);
}

int rofi_view_error_dialog(const char *msg, int markup) {
RofiViewState *state = __rofi_view_state_create();
state->retv = MENU_CANCEL;
Expand Down Expand Up @@ -2608,6 +2688,9 @@ int rofi_view_error_dialog(const char *msg, int markup) {
sn_launchee_context_complete(xcb->sncontext);
}

// Exec custom command
rofi_error_user_callback(msg);

// Set it as current window.
rofi_view_set_active(state);
return TRUE;
Expand Down
37 changes: 37 additions & 0 deletions source/xrmoptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,43 @@ static XrmOption xrmOptions[] = {
"Custom command to generate preview icons",
CONFIG_DEFAULT},

{xrm_String,
"on-selection-changed",
{.str = &config.on_selection_changed},
NULL,
"Custom command to call when menu selection changes",
CONFIG_DEFAULT},
{xrm_String,
"on-mode-changed",
{.str = &config.on_mode_changed},
NULL,
"Custom command to call when menu mode changes",
CONFIG_DEFAULT},
{xrm_String,
"on-entry-accepted",
{.str = &config.on_entry_accepted},
NULL,
"Custom command to call when menu entry is accepted",
CONFIG_DEFAULT},
{xrm_String,
"on-menu-canceled",
{.str = &config.on_menu_canceled},
NULL,
"Custom command to call when menu is canceled",
CONFIG_DEFAULT},
{xrm_String,
"on-menu-error",
{.str = &config.on_menu_error},
NULL,
"Custom command to call when menu finds errors",
CONFIG_DEFAULT},
{xrm_String,
"on-screenshot-taken",
{.str = &config.on_screenshot_taken},
NULL,
"Custom command to call when menu screenshot is taken",
CONFIG_DEFAULT},

{xrm_String,
"terminal",
{.str = &config.terminal_emulator},
Expand Down

0 comments on commit bc67d8a

Please sign in to comment.