Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Execute custom user commands or scripts on a variety of rofi events #2053

Open
wants to merge 5 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
38 changes: 37 additions & 1 deletion source/xrmoptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,50 @@ static XrmOption xrmOptions[] = {
NULL,
"Whether to load and show icons",
CONFIG_DEFAULT},

{xrm_String,
"preview-cmd",
{.str = &config.preview_cmd},
NULL,
"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
Loading