Merge remote-tracking branch 'upstream/next' into wayland

This commit is contained in:
lbonn
2025-02-13 09:35:15 +01:00
38 changed files with 732 additions and 214 deletions

View File

@@ -37,7 +37,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -52,6 +52,6 @@ jobs:
cc: gcc
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@@ -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\

View File

@@ -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",
@@ -92,6 +104,8 @@ Settings config = {
.sorting_method = "normal",
/** Case sensitivity of the search */
.case_sensitive = FALSE,
/** Case smart of the search */
.case_smart = FALSE,
/** Cycle through in the element list */
.cycle = TRUE,
/** Height of an element in #chars */

View File

@@ -1,4 +1,4 @@
AC_INIT([rofi], [1.7.8], [https://github.com/davatorium/rofi/],[],[https://github.com/davatorium/rofi/discussions])
AC_INIT([rofi], [1.7.8-dev], [https://github.com/davatorium/rofi/],[],[https://github.com/davatorium/rofi/discussions])
AC_CONFIG_SRCDIR([source/rofi.c])
AC_CONFIG_HEADER([config.h])

View File

@@ -141,10 +141,17 @@ textbox-num-sep {
str: "/";
}
inputbar {
padding: 1px ;
spacing: 0px ;
padding: 1px;
spacing: 0px;
text-color: var(normal-foreground);
children: [ prompt,textbox-prompt-colon,entry, num-filtered-rows, textbox-num-sep, num-rows, case-indicator ];
children: [ prompt,textbox-prompt-colon,entry, overlay,num-filtered-rows, textbox-num-sep, num-rows, case-indicator ];
}
overlay {
background-color: var(normal-foreground);
foreground-color: var(normal-background);
text-color: var(normal-background);
padding: 0 0.2em;
margin: 0 0.2em;
}
case-indicator {
spacing: 0;

View File

@@ -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',

View File

@@ -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
```

View File

@@ -159,7 +159,7 @@ Hide the input text. This should not be considered secure!
`-markup-rows`
Tell **rofi** that DMenu input is Pango markup encoded, and should be rendered.
See [here](https://developer.gnome.org/pygtk/stable/pango-markup-language.html)
See [here](https://docs.gtk.org/Pango/pango_markup.html)
for details about Pango markup.
`-multi-select`

View File

@@ -495,6 +495,18 @@ Go down in the entry history.
Default: Control+Down
`kb-matcher-up`
Select the next matcher.
Default: Super+equal
`kb-matcher-down`
Select the previous matcher.
Default: Super+minus
## Mouse Bindings
`ml-row-left`

View File

@@ -59,6 +59,7 @@ An integer number with the current state:
- **0**: Initial call of script.
- **1**: Selected an entry.
- **2**: Selected a custom entry.
- **3**: Deleted an entry.
- **10-28**: Custom keybinding 1-19 ( need to be explicitly enabled by script ).
### `ROFI_INFO`
@@ -109,7 +110,7 @@ The following extra options exists:
- **keep-selection**: If set, the selection is not moved to the first entry,
but the current position is maintained. The filter is cleared.
- **keep-filter**: If set, the filter is not cleared.
- **keep-filter**: If set, the filter is not cleared.
- **new-selection**: If `keep-selection` is set, this allows you to override
the selected entry (absolute position).

View File

@@ -1118,6 +1118,10 @@ The following properties are currently supported:
- **require-input**: boolean Listview requires user input to be unhidden.
The list is still present and hitting accept will activate the first entry.
### Overlay widget
- **timeout**: The time the widget is visible when showing a temporary message.
## Listview widget
The listview widget is special container widget.
@@ -1658,6 +1662,14 @@ If a filename is provided, it will try to resolve it in the following order:
A name is resolved (if it has no valid extension) as a filename by appending the `.rasi` and the `.rasinc` extension.
It will first look for files with `.rasi`, then for files with `.rasinc`.
If you want to do an optional import, e.g. no error when the file does not exists, you can do:
```css
?import "myfile"
```
This still throws an error on syntax error, but won't abort parsing if file does not exists.
## Examples
Several examples are installed together with **rofi**. These can be found in

View File

@@ -246,6 +246,12 @@ exec command. For that case, `#` can be used as a separator.
Start in case-sensitive mode. This option can be changed at run-time using the
`-kb-toggle-case-sensitivity` key binding.
`-case-smart`
Start in case-smart mode behave like vim's `smartcase`, which determines
case-sensitivity by input. When enabled, this will suppress `-case-sensitive`
config.
`-cycle`
Cycle through the result list. Default is 'true'.
@@ -355,6 +361,9 @@ Currently, the following methods are supported:
Default: *normal*
Multiple matching methods can be specified in a comma separated list.
The matching up/down keybinding allows cycling through at runtime.
Note: glob matching might be slow for larger lists
`-tokenize`

View File

@@ -200,13 +200,15 @@ char *rofi_expand_path(const char *input);
* @param needlelen The length of the needle
* @param haystack The string to match against
* @param haystacklen The length of the haystack
* @param case_sensitive Whether case is significant.
*
* UTF-8 aware levenshtein distance calculation
*
* @returns the levenshtein distance between needle and haystack
*/
unsigned int levenshtein(const char *needle, const glong needlelen,
const char *haystack, const glong haystacklen);
const char *haystack, const glong haystacklen,
const int case_sensitive);
/**
* @param data the unvalidated character array holding possible UTF-8 data
@@ -234,6 +236,7 @@ char *rofi_latin_to_utf8_strdup(const char *input, gssize length);
* @param plen Pattern length.
* @param str The input to match against pattern.
* @param slen Length of str.
* @param case_sensitive Whether case is significant.
*
* rofi_scorer_fuzzy_evaluate implements a global sequence alignment algorithm
* to find the maximum accumulated score by aligning `pattern` to `str`. It
@@ -263,7 +266,7 @@ char *rofi_latin_to_utf8_strdup(const char *input, gssize length);
* @returns the sorting weight.
*/
int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
glong slen);
glong slen, const int case_sensitive);
/*@}*/
/**
@@ -353,6 +356,13 @@ cairo_surface_t *cairo_image_surface_create_from_svg(const gchar *file,
*/
void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length);
/**
* @param input String to parse
*
* @returns String matching should be case sensitive or insensitive
*/
int parse_case_sensitivity(const char *input);
/**
* @param format The format string used. See below for possible syntax.
* @param string The selected entry.
@@ -432,6 +442,19 @@ ConfigEntry *rofi_config_find_widget(const char *name, const char *state,
*/
Property *rofi_theme_find_property(ConfigEntry *widget, PropertyType type,
const char *property, gboolean exact);
/**
* @returns get a human readable string with the current matching method.
*/
const char *helper_get_matching_mode_str(void);
/**
* Switch to the next matching method.
*/
void helper_select_next_matching_mode(void);
/**
* Switch to the previous matching method.
*/
void helper_select_previous_matching_mode(void);
G_END_DECLS
/**@} */

View File

@@ -145,6 +145,8 @@ typedef enum {
SELECT_ELEMENT_10,
ENTRY_HISTORY_UP,
ENTRY_HISTORY_DOWN,
MATCHER_UP,
MATCHER_DOWN
} KeyBindingAction;
/**

View File

@@ -31,9 +31,6 @@
#include <gmodule.h>
G_BEGIN_DECLS
/** ABI version to check if loaded plugin is compatible. */
#define ABI_VERSION 7u
/**
* Indicator what type of mode this is.
* For now it can be the classic switcher, or also implement a completer.

View File

@@ -29,7 +29,12 @@
#define ROFI_MODE_H
#include "rofi-types.h"
#include <cairo.h>
#include <gmodule.h>
G_BEGIN_DECLS
/** ABI version to check if loaded plugin is compatible. */
#define ABI_VERSION 7u
/**
* @defgroup MODE Mode
*
@@ -277,6 +282,28 @@ ModeMode mode_completer_result(Mode *sw, int menu_retv, char **input,
* @returns TRUE if mode can be used as completer.
*/
gboolean mode_is_completer(const Mode *sw);
/**
* @param mode The mode to query
*
* @returns the modes ABI version.
*/
int mode_get_abi_version(Mode *const mode);
/**
* @param mode The mode to query
* @param mod The GModule used to load the mode
*
* Set GModule used to load this plugin, this is used to
* unload it on shutdown.
*/
void mode_plugin_set_module(Mode *mode, GModule *mod);
/**
* @param mode The mode to query
*
* @returns the GModule used to load this plugin. NULL if not a plugin.
*/
GModule *mode_plugin_get_module(Mode *mode);
/**@}*/
G_END_DECLS
#endif

View File

@@ -40,7 +40,8 @@ typedef enum {
MM_REGEX = 1,
MM_GLOB = 2,
MM_FUZZY = 3,
MM_PREFIX = 4
MM_PREFIX = 4,
MM_NUM_MATCHERS = 5
} MatchingMethod;
/**
@@ -69,6 +70,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 */
@@ -123,6 +136,8 @@ typedef struct {
/** Search case sensitivity */
unsigned int case_sensitive;
/** Smart case sensitivity like vim */
unsigned int case_smart;
/** Cycle through in the element list */
unsigned int cycle;
/** Height of an element in number of rows */

View File

@@ -101,6 +101,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 */
@@ -159,12 +161,13 @@ struct RofiViewState {
/** Regexs used for matching */
rofi_int_matcher **tokens;
/** For case-sensitivity */
gboolean case_sensitive;
};
/** @} */
typedef struct _view_proxy {
void (*update)(struct RofiViewState *state, gboolean qr);
void (*maybe_update)(struct RofiViewState *state);
void (*temp_configure_notify)(struct RofiViewState *state,
xcb_configure_notify_event_t *xce);
void (*temp_click_to_exit)(struct RofiViewState *state, xcb_window_t target);
@@ -222,6 +225,8 @@ struct _rofi_view_cache_state {
gboolean delayed_mode;
/** timeout handling */
guint user_timeout;
/** timeout overlay */
guint overlay_timeout;
/** Entry box */
gboolean entry_history_enable;
/** Array with history entriy input. */

View File

@@ -287,6 +287,14 @@ void rofi_view_switch_mode(RofiViewState *state, Mode *mode);
* Overlays text over the current view. Passing NULL for text hides the overlay.
*/
void rofi_view_set_overlay(RofiViewState *state, const char *text);
/**
* @param state The handle to the view
* @param text An UTF-8 encoded character array with the text to overlay.
*
* Overlays text over the current view. Passing NULL for text hides the overlay.
* This message is automatically removed after X seconds.
*/
void rofi_view_set_overlay_timeout (RofiViewState *state, const char *text);
/**
* @param state The handle to the view.

View File

@@ -80,7 +80,9 @@ typedef struct {
TBFontConfig *tbfc;
PangoEllipsizeMode emode;
//
const char *password_mask_char;
const char *theme_name;
} textbox;

View File

@@ -51,6 +51,8 @@
int last_state = 0;
extern int rofi_is_in_dmenu_mode;
gboolean import_optional = FALSE;
const char *rasi_theme_file_extensions[] = {".rasi", ".rasinc", NULL};
/**
* Type of Object to parse.
@@ -289,6 +291,7 @@ C_COMMENT_OPEN "/*"
INCLUDE "@import"
OPT_INCLUDE "?import"
THEME "@theme"
DEFAULT (?i:\"default\"?)
@@ -378,6 +381,12 @@ if ( queue == NULL ) {
*/
<INITIAL>{INCLUDE} {
g_queue_push_head ( queue, GINT_TO_POINTER (YY_START) );
import_optional = FALSE;
BEGIN(INCLUDE);
}
<INITIAL>{OPT_INCLUDE} {
g_queue_push_head ( queue, GINT_TO_POINTER (YY_START) );
import_optional = TRUE;
BEGIN(INCLUDE);
}
<INITIAL>{THEME} {
@@ -437,10 +446,15 @@ if ( queue == NULL ) {
yylloc->first_column = yylloc->last_column = 1;
yylloc->filename = current->filename;
} else {
char *str = g_markup_printf_escaped ( "Failed to open theme: <i>%s</i>\nError: <b>%s</b>",
filename, strerror ( errno ) );
rofi_add_warning_message ( g_string_new ( str ) );
g_free ( str );
if ( !import_optional ) {
char *str = g_markup_printf_escaped ( "Failed to open theme: <i>%s</i>\nError: <b>%s</b>",
filename, strerror ( errno ) );
rofi_add_warning_message ( g_string_new ( str ) );
g_free ( str );
} else {
g_warning("Trying to parse optional theme: '%s', Error: %s",
filename, strerror(errno));
}
g_free(filename);
}
// Pop out of include. */
@@ -844,7 +858,7 @@ if ( queue == NULL ) {
/**
* Media defaults.
*/
<MEDIA,MEDIA_CONTENT,MEDIA_ENV_VAR,MEDIA_ENV_VAR_DEFAULT,MEDIA_ENV_VAR_CONTENT>{WHITESPACE}+ ; // ignore all whitespace
<MEDIA,MEDIA_CONTENT,MEDIA_ENV_VAR,MEDIA_ENV_VAR_DEFAULT,MEDIA_ENV_VAR_CONTENT>{WHITESPACE}+ ; // ignore all whitespace
<MEDIA,MEDIA_CONTENT,MEDIA_ENV_VAR,MEDIA_ENV_VAR_DEFAULT,MEDIA_ENV_VAR_CONTENT>. {
yytext[yyleng-1] = '\0';

View File

@@ -1,5 +1,5 @@
project('rofi', 'c',
version: '1.7.8+wayland1',
version: '1.7.8+wayland1-dev',
meson_version: '>=0.59.0',
license: [ 'MIT' ],
default_options: [

View File

@@ -3,55 +3,49 @@
THEMES=../../../themes/*.rasi
ROFI_BIN=../../../build/rofi
function generate_options()
{
echo -en "rofi\0icon\x1frofi\n"
echo -en "help browser\0icon\x1fhelp-browser\n"
echo -en "thunderbird\0icon\x1fthunderbird\n"
echo -en "Urgent\0icon\x1femblem-urgent\n"
echo -en "Active\0icon\x1fface-wink\n"
echo -en "folder\0icon\x1ffolder\n"
echo -en "Icon font 🐢 🥳\n"
echo -en "Font icon\0icon\x1f<span size='x-large' color='red'>:-)</span>\n"
echo -en "Quit\0icon\x1fapplication-exit\n"
function generate_options() {
echo -en "rofi\0icon\x1frofi\n"
echo -en "help browser\0icon\x1fhelp-browser\n"
echo -en "thunderbird\0icon\x1fthunderbird\n"
echo -en "Urgent\0icon\x1femblem-urgent\n"
echo -en "Active\0icon\x1fface-wink\n"
echo -en "folder\0icon\x1ffolder\n"
echo -en "Icon font 🐢 🥳\n"
echo -en "Font icon\0icon\x1f<span size='x-large' color='red'>:-)</span>\n"
echo -en "Quit\0icon\x1fapplication-exit\n"
}
function run_theme
{
theme=$1
BASE=$(basename ${theme})
NAME=${BASE%.rasi}
export ROFI_PNG_OUTPUT="${NAME}.png"
if [ ${NAME} = "default" ]
then
echo "# Default theme" >> themes.md
else
echo "# [${NAME}](https://github.com/davatorium/rofi/blob/next/themes/${BASE})" >> themes.md
fi
echo "" >> themes.md
generate_options | ${ROFI_BIN} -theme-str "@theme \"${theme}\"" \
-no-config -dmenu -p "mode" -show-icons \
-u 3 -a 4 -mesg "Message box for extra information" \
-take-screenshot-quit 1500
function run_theme {
theme=$1
BASE="$(basename ${theme})"
NAME=${BASE%.rasi}
export ROFI_PNG_OUTPUT="${NAME}.png"
if [ "${NAME}" = "default" ]; then
echo "# Default theme" >>themes.md
else
echo "# [${NAME}](https://github.com/davatorium/rofi/blob/next/themes/${BASE})" >>themes.md
fi
echo "" >>themes.md
generate_options | ${ROFI_BIN} -theme-str "@theme \"${theme}\"" \
-no-config -dmenu -p "mode" -show-icons \
-u 3 -a 4 -mesg "Message box for extra information" \
-take-screenshot-quit 1500
echo "![${NAME}](${NAME}.png)" >> themes.md
echo "" >> themes.md
echo "![${NAME}](${NAME}.png)" >>themes.md
echo "" >>themes.md
}
echo "# Included Themes" > themes.md
echo "Below is a list of themes shipped with rofi." >> themes.md
echo "Use \`rofi-theme-selector\` to select and use one of these themes." >> themes.md
echo "# Included Themes" >themes.md
echo "Below is a list of themes shipped with rofi." >>themes.md
echo "Use \`rofi-theme-selector\` to select and use one of these themes." >>themes.md
Xvfb :1234 -screen 0 1920x1080x24 &
XEPHYR_PID=$!
export DISPLAY=:1234
sleep 0.5;
sleep 0.5
run_theme "default"
for theme in ${THEMES}
do
run_theme ${theme}
for theme in ${THEMES}; do
run_theme "${theme}"
done
kill ${XEPHYR_PID}

View File

@@ -54,6 +54,16 @@
#include <sys/types.h>
#include <unistd.h>
const char *const MatchingMethodStr[MM_NUM_MATCHERS] = {
"Normal", "Regex", "Glob", "Fuzzy", "Prefix"};
static int MatchingMethodEnabled[MM_NUM_MATCHERS] = {
MM_NORMAL,
-1,
};
static int NUMMatchingMethodEnabled = 1;
static int CurrentMatchingMethod = 0;
/**
* Textual description of positioning rofi.
*/
@@ -67,6 +77,23 @@ char **stored_argv = NULL;
char *helper_string_replace_if_exists_v(char *string, GHashTable *h);
const char *helper_get_matching_mode_str(void) {
return MatchingMethodStr[config.matching_method];
}
void helper_select_next_matching_mode(void) {
CurrentMatchingMethod++;
CurrentMatchingMethod %= NUMMatchingMethodEnabled;
config.matching_method = MatchingMethodEnabled[CurrentMatchingMethod];
}
void helper_select_previous_matching_mode(void) {
CurrentMatchingMethod--;
if (CurrentMatchingMethod < 0) {
CurrentMatchingMethod = NUMMatchingMethodEnabled - 1;
}
config.matching_method = MatchingMethodEnabled[CurrentMatchingMethod];
}
void cmd_set_arguments(int argc, char **argv) {
stored_argc = argc;
stored_argv = argv;
@@ -582,7 +609,7 @@ int create_pid_file(const char *pidfile, gboolean kill_running) {
char buffer[64] = {
0,
};
ssize_t l = read(fd, &buffer, 63);
ssize_t l = read(fd, &(buffer[0]), 63);
if (l > 1) {
buffer[l] = 0;
pid_t pid = g_ascii_strtoll(buffer, NULL, 0);
@@ -664,24 +691,40 @@ int config_sanity_check(void) {
}
if (config.matching) {
if (g_strcmp0(config.matching, "regex") == 0) {
config.matching_method = MM_REGEX;
} else if (g_strcmp0(config.matching, "glob") == 0) {
config.matching_method = MM_GLOB;
} else if (g_strcmp0(config.matching, "fuzzy") == 0) {
config.matching_method = MM_FUZZY;
} else if (g_strcmp0(config.matching, "normal") == 0) {
config.matching_method = MM_NORMAL;
;
} else if (g_strcmp0(config.matching, "prefix") == 0) {
config.matching_method = MM_PREFIX;
} else {
g_string_append_printf(msg,
"\t<b>config.matching</b>=%s is not a valid "
"matching strategy.\nValid options are: glob, "
"regex, fuzzy, prefix or normal.\n",
config.matching);
found_error = 1;
char **strv = g_strsplit(config.matching, ",", 0);
if (strv) {
int matching_method_index = 0;
for (char **str = strv; *str && matching_method_index < MM_NUM_MATCHERS;
str++) {
gboolean found = FALSE;
for (unsigned i = 0;
i < MM_NUM_MATCHERS && matching_method_index < MM_NUM_MATCHERS;
i++) {
if (g_ascii_strcasecmp(*str, MatchingMethodStr[i]) == 0) {
MatchingMethodEnabled[matching_method_index] = i;
matching_method_index++;
NUMMatchingMethodEnabled = matching_method_index;
if (matching_method_index == MM_NUM_MATCHERS) {
found_error = 1;
g_string_append_printf(msg,
"\t<b>config.matching</b> = %s to many "
"matching options enabled.\n",
config.matching);
}
found = TRUE;
}
}
if (!found) {
g_string_append_printf(msg,
"\t<b>config.matching</b>=%s is not a valid "
"matching strategy.\nValid options are: glob, "
"regex, fuzzy, prefix or normal.\n",
*str);
found_error = 1;
}
}
config.matching_method = MatchingMethodEnabled[0];
g_strfreev(strv);
}
}
@@ -769,7 +812,8 @@ char *rofi_expand_path(const char *input) {
((a) < (b) ? ((a) < (c) ? (a) : (c)) : ((b) < (c) ? (b) : (c)))
unsigned int levenshtein(const char *needle, const glong needlelen,
const char *haystack, const glong haystacklen) {
const char *haystack, const glong haystacklen,
int case_sensitive) {
if (needlelen == G_MAXLONG) {
// String to long, we cannot handle this.
return UINT_MAX;
@@ -785,12 +829,12 @@ unsigned int levenshtein(const char *needle, const glong needlelen,
const char *needles = needle;
column[0] = x;
gunichar haystackc = g_utf8_get_char(haystack);
if (!config.case_sensitive) {
if (!case_sensitive) {
haystackc = g_unichar_tolower(haystackc);
}
for (glong y = 1, lastdiag = x - 1; y <= needlelen; y++) {
gunichar needlec = g_utf8_get_char(needles);
if (!config.case_sensitive) {
if (!case_sensitive) {
needlec = g_unichar_tolower(needlec);
}
unsigned int olddiag = column[y];
@@ -917,7 +961,7 @@ static int rofi_scorer_get_score_for(enum CharClass prev, enum CharClass curr) {
}
int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
glong slen) {
glong slen, int case_sensitive) {
if (slen > FUZZY_SCORER_MAX_LENGTH) {
return -MIN_SCORE;
}
@@ -952,9 +996,8 @@ int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
left = dp[si];
lefts = MAX(lefts + GAP_SCORE, left);
sc = g_utf8_get_char(sit);
if (config.case_sensitive
? pc == sc
: g_unichar_tolower(pc) == g_unichar_tolower(sc)) {
if (case_sensitive ? pc == sc
: g_unichar_tolower(pc) == g_unichar_tolower(sc)) {
int t = score[si] * (pstart ? PATTERN_START_MULTIPLIER
: PATTERN_NON_START_MULTIPLIER);
dp[si] = pfirst ? LEADING_GAP_SCORE * si + t
@@ -1248,6 +1291,28 @@ void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length) {
}
}
}
int parse_case_sensitivity(const char *input) {
int case_sensitive = config.case_sensitive;
if (config.case_smart) {
// By default case is false, unless the search query has a
// uppercase in it?
case_sensitive = FALSE;
const char *end;
if (g_utf8_validate(input, -1, &end)) {
for (const char *c = (input); !case_sensitive && c != NULL && *c;
c = g_utf8_next_char(c)) {
gunichar uc = g_utf8_get_char(c);
if (g_unichar_isupper(uc)) {
case_sensitive = TRUE;
}
}
}
}
return case_sensitive;
}
void rofi_output_formatted_line(const char *format, const char *string,
int selected_line, const char *filter) {
for (int i = 0; format && format[i]; i++) {

View File

@@ -332,6 +332,14 @@ ActionBindingEntry rofi_bindings[] = {
.name = "kb-entry-history-down",
.binding = "Control+Down",
.comment = "Go down in the history of the entry box"},
{.id = MATCHER_UP,
.name = "kb-matcher-up",
.binding = "Super+equal",
.comment = "Switch to the previous matcher"},
{.id = MATCHER_DOWN,
.name = "kb-mather-down",
.binding = "Super+minus",
.comment = "Switch to the next matcher"},
/* Mouse-aware bindings */

View File

@@ -159,6 +159,11 @@ const char *mode_get_name(const Mode *mode) {
return mode->name;
}
int mode_get_abi_version(Mode *const mode) {
g_assert(mode != NULL);
return mode->abi_version;
}
void mode_free(Mode **mode) {
g_assert(mode != NULL);
g_assert((*mode) != NULL);
@@ -245,4 +250,10 @@ gboolean mode_is_completer(const Mode *mode) {
return FALSE;
}
void mode_plugin_set_module(Mode *mode, GModule *mod){
mode->module = mod;
}
GModule *mode_plugin_get_module(Mode *mode){
return mode->module;
}
/**@}*/

View File

@@ -289,8 +289,8 @@ static gpointer read_input_thread(gpointer userdata) {
if (FD_ISSET(fd, &rfds)) {
ssize_t readbytes = 0;
if ((nread + 1024) > len) {
line = g_realloc(line, (nread + 1024));
len = nread + 1024;
line = g_realloc(line, (len + 2048));
len = len + 2048;
}
readbytes = read(fd, &line[nread], 1023);
if (readbytes > 0) {
@@ -959,7 +959,8 @@ int dmenu_mode_dialog(void) {
char *select = NULL;
find_arg_str("-select", &select);
if (select != NULL) {
rofi_int_matcher **tokens = helper_tokenize(select, config.case_sensitive);
rofi_int_matcher **tokens =
helper_tokenize(select, parse_case_sensitivity(select));
unsigned int i = 0;
for (i = 0; i < cmd_list_length; i++) {
if (helper_token_match(tokens, cmd_list[i].entry)) {
@@ -970,8 +971,9 @@ int dmenu_mode_dialog(void) {
helper_tokenize_free(tokens);
}
if (find_arg("-dump") >= 0) {
rofi_int_matcher **tokens = helper_tokenize(
config.filter ? config.filter : "", config.case_sensitive);
char *filter = config.filter ? config.filter : "";
rofi_int_matcher **tokens =
helper_tokenize(filter, parse_case_sensitivity(filter));
unsigned int i = 0;
for (i = 0; i < cmd_list_length; i++) {
if (tokens == NULL || helper_token_match(tokens, cmd_list[i].entry)) {

View File

@@ -353,7 +353,8 @@ static gboolean recursive_browser_async_read_proc(gint fd,
}
} else if (command == 'q') {
if (pd->loading) {
rofi_view_set_overlay(rofi_view_get_active(), NULL);
// TODO: add enable.
//rofi_view_set_overlay(rofi_view_get_active(), NULL);
}
}
}

View File

@@ -350,6 +350,10 @@ static ModeMode script_mode_result(Mode *sw, int mretv, char **input,
retv = (mretv & MENU_LOWER_MASK);
return retv;
}
} else if ((mretv & MENU_ENTRY_DELETE) && selected_line != UINT32_MAX) {
script_mode_reset_highlight(sw);
new_list = execute_executor(sw, rmpd->cmd_list[selected_line].entry, &new_length,
3, &(rmpd->cmd_list[selected_line]));
} else if ((mretv & MENU_OK) && rmpd->cmd_list[selected_line].entry != NULL) {
if (rmpd->cmd_list[selected_line].nonselectable) {
return RELOAD_DIALOG;

View File

@@ -71,10 +71,6 @@
#include "timings.h"
// Plugin abi version.
// TODO: move this check to mode.c
#include "mode-private.h"
/** Location of pidfile for this instance. */
char *pidfile = NULL;
/** Location of Cache directory. */
@@ -202,7 +198,7 @@ static void run_mode_index(ModeMode mode) {
for (unsigned int i = 0; i < num_modes; i++) {
if (!mode_init(modes[i])) {
GString *str = g_string_new("Failed to initialize the mode: ");
g_string_append(str, modes[i]->name);
g_string_append(str, mode_get_name(modes[i]));
g_string_append(str, "\n");
rofi_view_error_dialog(str->str, ERROR_MSG_MARKUP);
@@ -303,7 +299,7 @@ static void print_list_of_modes(int is_term) {
}
printf(" • %s%s%s%s\n", active ? "+" : "",
is_term ? (active ? color_green : color_red) : "",
available_modes[i]->name, is_term ? color_reset : "");
mode_get_name(available_modes[i]), is_term ? color_reset : "");
}
}
static void print_main_application_options(int is_term) {
@@ -475,7 +471,7 @@ static void help_print_mode_not_found(const char *mode) {
}
}
g_string_append_printf(str, " * %s%s\n", active ? "+" : "",
available_modes[i]->name);
mode_get_name(available_modes[i]));
}
rofi_add_error_message(str);
}
@@ -489,7 +485,7 @@ static void help_print_no_arguments(void) {
g_string_append(emesg, "The following modes are enabled:\n");
for (unsigned int j = 0; j < num_modes; j++) {
g_string_append_printf(emesg, " • <span color=\"green\">%s</span>\n",
modes[j]->name);
mode_get_name(modes[j]));
}
g_string_append(emesg, "\nThe following modes can be enabled:\n");
for (unsigned int i = 0; i < num_available_modes; i++) {
@@ -502,7 +498,7 @@ static void help_print_no_arguments(void) {
}
if (!active) {
g_string_append_printf(emesg, " • <span color=\"red\">%s</span>\n",
available_modes[i]->name);
mode_get_name(available_modes[i]));
}
}
g_string_append(emesg, "\nTo activate a mode, add it to the list in "
@@ -565,7 +561,7 @@ static void cleanup(void) {
Mode *rofi_collect_modes_search(const char *name) {
for (unsigned int i = 0; i < num_available_modes; i++) {
if (g_strcmp0(name, available_modes[i]->name) == 0) {
if (g_strcmp0(name, mode_get_name(available_modes[i])) == 0) {
return available_modes[i];
}
}
@@ -577,7 +573,7 @@ Mode *rofi_collect_modes_search(const char *name) {
* @returns TRUE when success.
*/
static gboolean rofi_collectmodes_add(Mode *mode) {
Mode *m = rofi_collect_modes_search(mode->name);
Mode *m = rofi_collect_modes_search(mode_get_name(mode));
if (m == NULL) {
available_modes =
g_realloc(available_modes, sizeof(Mode *) * (num_available_modes + 1));
@@ -605,13 +601,13 @@ static void rofi_collectmodes_dir(const char *base_dir) {
if (mod) {
Mode *m = NULL;
if (g_module_symbol(mod, "mode", (gpointer *)&m)) {
if (m->abi_version != ABI_VERSION) {
if (mode_get_abi_version(m) != ABI_VERSION) {
g_warning("ABI version of plugin: '%s' does not match: %08X "
"expecting: %08X",
dn, m->abi_version, ABI_VERSION);
dn, mode_get_abi_version(m), ABI_VERSION);
g_module_close(mod);
} else {
m->module = mod;
mode_plugin_set_module(m, mod);
if (!rofi_collectmodes_add(m)) {
g_module_close(mod);
}
@@ -684,8 +680,8 @@ static void rofi_collectmodes_setup(void) {
}
static void rofi_collectmodes_destroy(void) {
for (unsigned int i = 0; i < num_available_modes; i++) {
if (available_modes[i]->module) {
GModule *mod = available_modes[i]->module;
if (mode_plugin_get_module(available_modes[i])) {
GModule *mod = mode_plugin_get_module(available_modes[i]);
available_modes[i] = NULL;
g_module_close(mod);
}
@@ -1107,7 +1103,9 @@ int main(int argc, char *argv[]) {
extern const char *rasi_theme_file_extensions[];
char *file2 =
helper_get_theme_path(config_path, rasi_theme_file_extensions, NULL);
char *filename = rofi_theme_parse_prepare_file(file2);
GFile *gf = g_file_new_for_path(file2);
char *filename = g_file_get_path(gf);
g_object_unref(gf);
g_free(file2);
if (filename && g_file_test(filename, G_FILE_TEST_EXISTS)) {
if (rofi_theme_parse_file(filename)) {

View File

@@ -81,14 +81,15 @@ struct _rofi_view_cache_state CacheState = {
.max_refilter_time = 0.0,
.delayed_mode = FALSE,
.user_timeout = 0,
.overlay_timeout = 0,
.entry_history_enable = TRUE,
.entry_history = NULL,
.entry_history_length = 0,
.entry_history_index = 0,
};
static char *get_matching_state(void) {
if (config.case_sensitive) {
static char *get_matching_state(RofiViewState* state) {
if (state->case_sensitive) {
if (config.sort) {
return "±";
} else {
@@ -113,6 +114,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.
*/
@@ -172,6 +185,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);
}
@@ -444,12 +458,13 @@ static void filter_elements(thread_state *ts,
glong slen = g_utf8_strlen(str, -1);
switch (config.sorting_method_enum) {
case SORT_FZF:
t->state->distance[i] =
rofi_scorer_fuzzy_evaluate(t->pattern, t->plen, str, slen);
t->state->distance[i] = rofi_scorer_fuzzy_evaluate(
t->pattern, t->plen, str, slen, t->state->case_sensitive);
break;
case SORT_NORMAL:
default:
t->state->distance[i] = levenshtein(t->pattern, t->plen, str, slen);
t->state->distance[i] = levenshtein(t->pattern, t->plen, str, slen,
t->state->case_sensitive);
break;
}
g_free(str);
@@ -620,9 +635,31 @@ 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;
@@ -630,7 +667,6 @@ static void selection_changed_callback(G_GNUC_UNUSED listview *lv,
&fstate, NULL, TRUE);
textbox_text(state->tb_current_entry, text);
g_free(text);
} else {
textbox_text(state->tb_current_entry, "");
}
@@ -742,7 +778,12 @@ static gboolean rofi_view_refilter_real(RofiViewState *state) {
unsigned int j = 0;
gchar *pattern = mode_preprocess_input(state->sw, state->text->text);
glong plen = pattern ? g_utf8_strlen(pattern, -1) : 0;
state->tokens = helper_tokenize(pattern, config.case_sensitive);
state->case_sensitive = parse_case_sensitivity(state->text->text);
state->tokens = helper_tokenize(pattern, state->case_sensitive);
if (config.case_smart && state->case_indicator) {
textbox_text(state->case_indicator, get_matching_state(state));
}
/**
* On long lists it can be beneficial to parallelize.
* If number of threads is 1, no thread is spawn.
@@ -1008,7 +1049,7 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
if (state->case_indicator != NULL) {
config.sort = !config.sort;
state->refilter = TRUE;
textbox_text(state->case_indicator, get_matching_state());
textbox_text(state->case_indicator, get_matching_state(state));
}
break;
case MODE_PREVIOUS:
@@ -1038,7 +1079,7 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
config.case_sensitive = !config.case_sensitive;
(state->selected_line) = 0;
state->refilter = TRUE;
textbox_text(state->case_indicator, get_matching_state());
textbox_text(state->case_indicator, get_matching_state(state));
}
break;
// Special delete entry command.
@@ -1214,7 +1255,6 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
// Nothing entered and nothing selected.
state->retv = MENU_CUSTOM_INPUT;
}
state->quit = TRUE;
break;
}
@@ -1273,6 +1313,16 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
}
break;
}
case MATCHER_UP:
helper_select_next_matching_mode();
rofi_view_refilter(state);
rofi_view_set_overlay_timeout(state, helper_get_matching_mode_str());
break;
case MATCHER_DOWN:
helper_select_previous_matching_mode();
rofi_view_refilter(state);
rofi_view_set_overlay_timeout(state, helper_get_matching_mode_str());
break;
}
}
@@ -1410,6 +1460,62 @@ 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.
state = rofi_view_get_active();
// cleanup, if no more state to display.
if (state == NULL) {
// Quit main-loop.
rofi_quit_main_loop();
return;
}
}
// Update if requested.
if (state->refilter) {
rofi_view_refilter(state);
}
rofi_view_update(state, TRUE);
return;
}
WidgetTriggerActionResult textbox_button_trigger_action(
widget *wid, MouseBindingMouseDefaultAction action, G_GNUC_UNUSED gint x,
G_GNUC_UNUSED gint y, G_GNUC_UNUSED void *user_data) {
@@ -1558,7 +1664,7 @@ static void rofi_view_add_widget(RofiViewState *state, widget *parent_widget,
TB_AUTOWIDTH | TB_AUTOHEIGHT, NORMAL, "*", 0, 0);
// Add small separator between case indicator and text box.
box_add((box *)parent_widget, WIDGET(state->case_indicator), FALSE);
textbox_text(state->case_indicator, get_matching_state());
textbox_text(state->case_indicator, get_matching_state(state));
}
/**
* ENTRY BOX
@@ -1694,6 +1800,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;
@@ -1793,6 +1900,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;
@@ -1836,6 +1955,9 @@ int rofi_view_error_dialog(const char *msg, int markup) {
}
#endif
// Exec custom command
rofi_error_user_callback(msg);
// Set it as current window.
rofi_view_set_active(state);
return TRUE;
@@ -1905,10 +2027,38 @@ void rofi_view_workers_finalize(void) {
}
Mode *rofi_view_get_mode(RofiViewState *state) { return state->sw; }
static gboolean rofi_view_overlay_timeout(G_GNUC_UNUSED gpointer user_data) {
RofiViewState *state = rofi_view_get_active();
if (state) {
widget_disable(WIDGET(state->overlay));
}
CacheState.overlay_timeout = 0;
rofi_view_queue_redraw();
return G_SOURCE_REMOVE;
}
void rofi_view_set_overlay_timeout(RofiViewState *state, const char *text) {
if (state->overlay == NULL || state->list_view == NULL) {
return;
}
if (text == NULL) {
widget_disable(WIDGET(state->overlay));
return;
}
rofi_view_set_overlay(state, text);
int timeout = rofi_theme_get_integer(WIDGET(state->overlay), "timeout", 3);
CacheState.overlay_timeout =
g_timeout_add_seconds(timeout, rofi_view_overlay_timeout, state);
}
void rofi_view_set_overlay(RofiViewState *state, const char *text) {
if (state->overlay == NULL || state->list_view == NULL) {
return;
}
if (CacheState.overlay_timeout > 0) {
g_source_remove(CacheState.overlay_timeout);
CacheState.overlay_timeout = 0;
}
if (text == NULL) {
widget_disable(WIDGET(state->overlay));
return;
@@ -1965,10 +2115,6 @@ void rofi_view_update(RofiViewState *state, gboolean qr) {
proxy->update(state, qr);
}
void rofi_view_maybe_update(RofiViewState *state) {
proxy->maybe_update(state);
}
void rofi_view_temp_configure_notify(RofiViewState *state,
xcb_configure_notify_event_t *xce) {
proxy->temp_configure_notify(state, xce);

View File

@@ -64,8 +64,6 @@
*/
static void wayland_rofi_view_update(RofiViewState *state, gboolean qr);
static void wayland_rofi_view_maybe_update(RofiViewState *state);
/**
* Structure holding some state
*/
@@ -119,7 +117,7 @@ static gboolean wayland_rofi_view_repaint(G_GNUC_UNUSED void *data) {
// Repaint the view (if needed).
// After a resize the edit_pixmap surface might not contain anything
// anymore. If we already re-painted, this does nothing.
wayland_rofi_view_maybe_update(state);
rofi_view_maybe_update(state);
WlState.repaint_source = 0;
}
return G_SOURCE_REMOVE;
@@ -191,7 +189,7 @@ static gboolean wayland_rofi_view_reload_idle(G_GNUC_UNUSED gpointer data) {
state->reload = TRUE;
state->refilter = TRUE;
wayland_rofi_view_maybe_update(state);
rofi_view_maybe_update(state);
}
WlState.idle_timeout = 0;
return G_SOURCE_REMOVE;
@@ -369,29 +367,6 @@ static void wayland_rofi_view_update(RofiViewState *state, gboolean qr) {
*/
void process_result(RofiViewState *state);
static void wayland_rofi_view_maybe_update(RofiViewState *state) {
if (rofi_view_get_completed(state)) {
// This menu is done.
rofi_view_finalize(state);
// If there a state. (for example error) reload it.
state = rofi_view_get_active();
// cleanup, if no more state to display.
if (state == NULL) {
// Quit main-loop.
rofi_quit_main_loop();
return;
}
}
// Update if requested.
if (state->refilter) {
rofi_view_refilter(state);
}
wayland_rofi_view_update(state, TRUE);
return;
}
static void wayland_rofi_view_frame_callback(void) {
if (WlState.repaint_source == 0) {
WlState.repaint_source = g_idle_add_full(
@@ -427,14 +402,18 @@ static void wayland_rofi_view_cleanup(void) {
g_source_remove(WlState.idle_timeout);
WlState.idle_timeout = 0;
}
if (CacheState.user_timeout > 0) {
g_source_remove(CacheState.user_timeout);
CacheState.user_timeout = 0;
}
if (CacheState.refilter_timeout > 0) {
g_source_remove(CacheState.refilter_timeout);
CacheState.refilter_timeout = 0;
}
if (CacheState.overlay_timeout) {
g_source_remove(CacheState.overlay_timeout);
CacheState.overlay_timeout = 0;
}
if (CacheState.user_timeout > 0) {
g_source_remove(CacheState.user_timeout);
CacheState.user_timeout = 0;
}
if (WlState.repaint_source > 0) {
g_source_remove(WlState.repaint_source);
WlState.repaint_source = 0;
@@ -458,7 +437,6 @@ static void wayland_rofi_view_pool_refresh(void) {
static view_proxy view_ = {
.update = wayland_rofi_view_update,
.maybe_update = wayland_rofi_view_maybe_update,
.temp_configure_notify = NULL,
.temp_click_to_exit = NULL,
.frame_callback = wayland_rofi_view_frame_callback,

View File

@@ -250,6 +250,15 @@ textbox *textbox_create(widget *parent, WidgetType type, const char *name,
tb->placeholder = g_markup_escape_text(placeholder, -1);
}
}
const char *password_mask_char =
rofi_theme_get_string(WIDGET(tb), "password-mask", NULL);
if (password_mask_char == NULL || (*password_mask_char) == '\0') {
tb->password_mask_char = "*";
} else {
tb->password_mask_char = password_mask_char;
}
textbox_text(tb, txt ? txt : "");
textbox_cursor_end(tb);
@@ -336,11 +345,14 @@ static void __textbox_update_pango_text(textbox *tb) {
}
tb->show_placeholder = FALSE;
if ((tb->flags & TB_PASSWORD) == TB_PASSWORD) {
size_t l = g_utf8_strlen(tb->text, -1);
char string[l + 1];
memset(string, '*', l);
string[l] = '\0';
pango_layout_set_text(tb->layout, string, l);
size_t text_len = g_utf8_strlen(tb->text, -1);
size_t mask_len = strlen(tb->password_mask_char);
char string[text_len * mask_len + 1];
for (size_t offset = 0; offset < text_len * mask_len; offset += mask_len) {
memcpy(string + offset, tb->password_mask_char, mask_len);
}
string[text_len * mask_len] = '\0';
pango_layout_set_text(tb->layout, string, -1);
} else if (tb->flags & TB_MARKUP || tb->tbft & MARKUP) {
pango_layout_set_markup(tb->layout, tb->text, -1);
} else {
@@ -488,7 +500,6 @@ static void textbox_draw(widget *wid, cairo_t *draw) {
return;
}
textbox *tb = (textbox *)wid;
int dot_offset = 0;
if (tb->changed) {
__textbox_update_pango_text(tb);
@@ -521,7 +532,7 @@ static void textbox_draw(widget *wid, cairo_t *draw) {
{
int rem =
MAX(0, tb->widget.w - widget_padding_get_padding_width(WIDGET(tb)) -
line_width - dot_offset);
line_width);
switch (pango_layout_get_alignment(tb->layout)) {
case PANGO_ALIGN_CENTER:
x = rem * (tb->xalign - 0.5);
@@ -530,7 +541,7 @@ static void textbox_draw(widget *wid, cairo_t *draw) {
x = rem * (tb->xalign - 1.0);
break;
default:
x = rem * tb->xalign + dot_offset;
x = rem * tb->xalign;
break;
}
x += widget_padding_get_left(WIDGET(tb));
@@ -541,11 +552,20 @@ static void textbox_draw(widget *wid, cairo_t *draw) {
// We want to place the cursor based on the text shown.
const char *text = pango_layout_get_text(tb->layout);
// Clamp the position, should not be needed, but we are paranoid.
int cursor_offset = MIN(tb->cursor, g_utf8_strlen(text, -1));
size_t cursor_offset;
if ((tb->flags & TB_PASSWORD) == TB_PASSWORD) {
// Calculate cursor position based on mask length
size_t mask_len = strlen(tb->password_mask_char);
cursor_offset = MIN(tb->cursor * mask_len, strlen(text));
} else {
cursor_offset = MIN(tb->cursor, g_utf8_strlen(text, -1));
// convert to byte location.
char *offset = g_utf8_offset_to_pointer(text, cursor_offset);
cursor_offset = offset - text;
}
PangoRectangle pos;
// convert to byte location.
char *offset = g_utf8_offset_to_pointer(text, cursor_offset);
pango_layout_get_cursor_pos(tb->layout, offset - text, &pos, NULL);
pango_layout_get_cursor_pos(tb->layout, cursor_offset, &pos, NULL);
int cursor_x = pos.x / PANGO_SCALE;
int cursor_y = pos.y / PANGO_SCALE;
int cursor_height = pos.height / PANGO_SCALE;

View File

@@ -310,28 +310,6 @@ static void xcb_rofi_view_update(RofiViewState *state, gboolean qr) {
}
}
static void xcb_rofi_view_maybe_update(RofiViewState *state) {
if (rofi_view_get_completed(state)) {
// This menu is done.
rofi_view_finalize(state);
// If there a state. (for example error) reload it.
state = rofi_view_get_active();
// cleanup, if no more state to display.
if (state == NULL) {
// Quit main-loop.
rofi_quit_main_loop();
return;
}
}
// Update if requested.
if (state->refilter) {
rofi_view_refilter(state);
}
rofi_view_update(state, TRUE);
return;
}
/**
* Calculates the window position
@@ -962,14 +940,18 @@ static void xcb_rofi_view_cleanup(void) {
g_source_remove(XcbState.idle_timeout);
XcbState.idle_timeout = 0;
}
if (CacheState.user_timeout > 0) {
g_source_remove(CacheState.user_timeout);
CacheState.user_timeout = 0;
}
if (CacheState.refilter_timeout > 0) {
g_source_remove(CacheState.refilter_timeout);
CacheState.refilter_timeout = 0;
}
if (CacheState.overlay_timeout) {
g_source_remove(CacheState.overlay_timeout);
CacheState.overlay_timeout = 0;
}
if (CacheState.user_timeout > 0) {
g_source_remove(CacheState.user_timeout);
CacheState.user_timeout = 0;
}
if (XcbState.repaint_source > 0) {
g_source_remove(XcbState.repaint_source);
XcbState.repaint_source = 0;
@@ -1020,7 +1002,6 @@ static void xcb_rofi_view_set_window_title(const char *title) {
static view_proxy view_ = {
.update = xcb_rofi_view_update,
.maybe_update = xcb_rofi_view_maybe_update,
.temp_configure_notify = xcb_rofi_view_temp_configure_notify,
.temp_click_to_exit = xcb_rofi_view_temp_click_to_exit,
.frame_callback = xcb_rofi_view_frame_callback,

View File

@@ -132,6 +132,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},
@@ -249,6 +286,12 @@ static XrmOption xrmOptions[] = {
NULL,
"Set case-sensitivity",
CONFIG_DEFAULT},
{xrm_Boolean,
"case-smart",
{.num = &config.case_smart},
NULL,
"Set smartcase like vim (determine case-sensitivity by input)",
CONFIG_DEFAULT},
{xrm_Boolean,
"cycle",
{.num = &config.cycle},

View File

@@ -139,25 +139,25 @@ int main(int argc, char **argv) {
*/
TASSERT(levenshtein("aap", g_utf8_strlen("aap", -1), "aap",
g_utf8_strlen("aap", -1)) == 0);
g_utf8_strlen("aap", -1), 0) == 0);
TASSERT(levenshtein("aap", g_utf8_strlen("aap", -1), "aap ",
g_utf8_strlen("aap ", -1)) == 1);
g_utf8_strlen("aap ", -1), 0) == 1);
TASSERT(levenshtein("aap ", g_utf8_strlen("aap ", -1), "aap",
g_utf8_strlen("aap", -1)) == 1);
g_utf8_strlen("aap", -1), 0) == 1);
TASSERTE(levenshtein("aap", g_utf8_strlen("aap", -1), "aap noot",
g_utf8_strlen("aap noot", -1)),
g_utf8_strlen("aap noot", -1), 0),
5u);
TASSERTE(levenshtein("aap", g_utf8_strlen("aap", -1), "noot aap",
g_utf8_strlen("noot aap", -1)),
g_utf8_strlen("noot aap", -1), 0),
5u);
TASSERTE(levenshtein("aap", g_utf8_strlen("aap", -1), "noot aap mies",
g_utf8_strlen("noot aap mies", -1)),
g_utf8_strlen("noot aap mies", -1), 0),
10u);
TASSERTE(levenshtein("noot aap mies", g_utf8_strlen("noot aap mies", -1),
"aap", g_utf8_strlen("aap", -1)),
"aap", g_utf8_strlen("aap", -1), 0),
10u);
TASSERTE(levenshtein("otp", g_utf8_strlen("otp", -1), "noot aap",
g_utf8_strlen("noot aap", -1)),
g_utf8_strlen("noot aap", -1), 0),
5u);
/**
* Quick converision check.
@@ -189,20 +189,48 @@ int main(int argc, char **argv) {
}
{
TASSERTL(
rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "aap noot mies", 12),
rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "aap noot mies", 12, 0),
-605);
TASSERTL(rofi_scorer_fuzzy_evaluate("anm", 3, "aap noot mies", 12), -155);
TASSERTL(rofi_scorer_fuzzy_evaluate("blu", 3, "aap noot mies", 12),
TASSERTL(rofi_scorer_fuzzy_evaluate("anm", 3, "aap noot mies", 12, 0),
-155);
TASSERTL(rofi_scorer_fuzzy_evaluate("blu", 3, "aap noot mies", 12, 0),
1073741824);
config.case_sensitive = TRUE;
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12),
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12, 1),
1073741754);
config.case_sensitive = FALSE;
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12), -155);
TASSERTL(rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "Anm", 3),
TASSERTL(rofi_scorer_fuzzy_evaluate("Anm", 3, "aap noot mies", 12, 0),
-155);
TASSERTL(rofi_scorer_fuzzy_evaluate("aap noot mies", 12, "Anm", 3, 0),
1073741824);
}
/**
* Case sensitivity
*/
{
int case_smart = config.case_smart;
int case_sensitive = config.case_sensitive;
{
config.case_smart = FALSE;
config.case_sensitive = FALSE;
TASSERT(parse_case_sensitivity("all lower case 你好") == 0);
TASSERT(parse_case_sensitivity("not All lowEr Case 你好") == 0);
config.case_sensitive = TRUE;
TASSERT(parse_case_sensitivity("all lower case 你好") == 1);
TASSERT(parse_case_sensitivity("not All lowEr Case 你好") == 1);
}
{
config.case_smart = TRUE;
config.case_sensitive = TRUE;
TASSERT(parse_case_sensitivity("all lower case") == 0);
TASSERT(parse_case_sensitivity("AAAAAAAAAAAA") == 1);
config.case_sensitive = FALSE;
TASSERT(parse_case_sensitivity("all lower case 你好") == 0);
TASSERT(parse_case_sensitivity("not All lowEr Case 你好") == 1);
}
config.case_smart = case_smart;
config.case_sensitive = case_sensitive;
}
char *a;
a = helper_string_replace_if_exists(
"{terminal} [-t {title} blub ]-e {cmd}", "{cmd}", "aap", "{title}",

View File

@@ -125,7 +125,7 @@ END_TEST
START_TEST(test_mode_num_items) {
unsigned int rows = mode_get_num_entries(&help_keys_mode);
ck_assert_int_eq(rows, 79);
ck_assert_int_eq(rows, 81);
for (unsigned int i = 0; i < rows; i++) {
int state = 0;
GList *list = NULL;