/ rofi

rofi: Matching and Sorting

This is the fifth in a series of several posts on how to do way more than you really need to with rofi. It's a neat little tool that does so many cool things. I don't have a set number of posts, and I don't have a set goal. I just want to share something I find useful.

This post looks at configuring rofi's matching and sorting.

Assumptions

I'm running Fedora 27. Most of the instructions are based on that OS. This will translate fairly well to other RHEL derivatives. The Debian ecosystem should also work fairly well, albeit with totally different package names. This probably won't work at all on Windows, and I have no intention of fixing that.

You're going to need a newer version of rofi, >=1.4. I'm currently running this:

$ rofi -version
Version: 1.4.2-131-git-5c5665ef (next)

If you installed from source, you should be good to go.

Code

You can view the code related to this post under the post-05-matching-and-sorting tag.

Overview

For the most part, rofi sorts via Levenshtein distance. In a nutshell, the Levenshtein distance counts the number of changes necessary to go from one string to another. You can view rofi's implementation. It's utf-8-aware, which is very nice.

When running fuzzy matches, rofi can also sort via FZF. Viewing its implementation of FZF might be useful. I believe it too is utf-8-aware.

As for the other matching methods, they seem to be vanilla. While there can be fairly substantial differences in regex and glob implementations across different languages, I haven't been negatively affected by rofi's spin yet (I've been too busy writing other things to really delve deep). rofi's going to be better sometimes and worse other times. Using the Levenshtein distance makes things pretty easy to use.

Comparison

I've made a quick chart to (quite briefly) highlight the differences. I just looked at sorting, Levenshtein distance, and fuzzy matching. regex and glob matching are too specialized to easily throw in a simple chart like this.

comparison

I used this script to compile all the information (plus a bunch more).

generate-matching-and-sorting-comparison
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/bash

set -x

OPTIONS='1000
000
100
0000
1001'

MATCHING=(normal regex glob fuzzy)
FLAG_STATES=('-no' '')

SLEEP_TIME=0.25

function run_dmenu {
echo "$OPTIONS" | eval "rofi -dmenu -no-lazy-grab -theme-str '#inputbar{children: [entry,case-indicator];}' -hide-scrollbar -width 6 -lines 5 -matching $1 $2-sort $3-levenshtein-sort" &
}

function move_screenshot {
SCREENSHOT=$(find ~/Pictures -name 'rofi*' -exec ls -t {} + | head -1)
mv "$SCREENSHOT" "$HOME/Pictures/matching-$1$2-sort$3-levenshstein-sort.png"
}

function ghost_keys {
sleep $SLEEP_TIME
xdotool key 0 key 0
sleep $SLEEP_TIME
xdotool key alt+shift+s
sleep $SLEEP_TIME
xdotool key Escape
sleep $SLEEP_TIME
}

function execute_stage {
run_dmenu "$1" "$2" "$3"
ghost_keys
move_screenshot "$1" "$2" "$3"
}

for matching_state in "${MATCHING[@]}"; do
for sort_state in "${FLAG_STATES[@]}"; do
for levenshstein_state in "${FLAG_STATES[@]}"; do
execute_stage "$matching_state" "$sort_state" "$levenshstein_state"
done
done
done

Basic Sort Config

Personally, I find it useful to have things at least minimally sorted as I'm going.

$ sed \
--in-place='.bak' \
-E 's/^.*\ssort:.*$/\tsort: true;/g' \
$XDG_USER_CONFIG_DIR/rofi/config.rasi
$ diff --color --unified=0 "$XDG_USER_CONFIG_DIR/rofi/config.rasi"{.bak,}
--- $XDG_USER_CONFIG_DIR/rofi/config.rasi.bak
+++ $XDG_USER_CONFIG_DIR/rofi/config.rasi
@@ -25 +25 @@
-/* sort: false;*/
+ sort: true;

However, the matching method is very much situational. normal should work most of the time. fuzzy might be better, but I don't have any metrics so I'm not sure how it affects performance and all that. glob is amazing when moving files around, but might not be very useful when attempting to drun. You could always run regex but then no one else would want to read your scripts and you'd turn to a life of blogging like me.

(Yet Another) rofi Options Script

I slapped together a quick script to handle matching and sorting.

$ scripts/configure-matching-and-sorting -h
usage: configure-matching-and-sorting [-h] [--skip-diff]
[-o {matching,sort,levenshtein-sort}]
[-m [{fuzzy,glob,normal,regex}]]
[-s [{true,false}]] [-l [{true,false}]]

Configure rofi's matching and sorting

optional arguments:
-h, --help show this help message and exit
--skip-diff Don't print the config diff
-o {matching,sort,levenshtein-sort}, --only {matching,sort,levenshtein-sort}
Change only the specified option

-m [{fuzzy,glob,normal,regex}], --matching [{fuzzy,glob,normal,regex}]
Sets the matching method
-s [{true,false}], --sort [{true,false}]
Enables sorting
-l [{true,false}], --levenshtein-sort [{true,false}]
Forces Levenshtein sorting

I tried to make this easy. To use the existing config, just add the pertinent flag.

$ scripts/configure-matching-and-sorting -m -s -l
--- $XDG_USER_CONFIG_DIR/rofi/config.rasi.bak
+++ $XDG_USER_CONFIG_DIR/rofi/config.rasi
@@ -25,2 +25,2 @@
-/* sort: false;*/
-/* levenshtein-sort: false;*/
+ sort: false;
+ levenshtein-sort: false;
@@ -35 +35 @@
-/* matching: "normal";*/
+ matching: "normal";

While it looks like that did something, it just uncommented the defaults. It didn't actually change anything.

To actually change something, you can either feed it in via the command or leave off the flag and feed it in via the GUI.

$ scripts/configure-matching-and-sorting -o matching -m fuzzy
--- $XDG_USER_CONFIG_DIR/rofi/config.rasi.bak
+++ $XDG_USER_CONFIG_DIR/rofi/config.rasi
@@ -35 +35 @@
- matching: "normal";
+ matching: "fuzzy";
$ scripts/configure-matching-and-sorting -o matching

configure-matching-and-sorting-gui-matching-glob

--- $XDG_USER_CONFIG_DIR/rofi/config.rasi.bak
+++ $XDG_USER_CONFIG_DIR/rofi/config.rasi
@@ -35 +35 @@
- matching: "fuzzy";
+ matching: "glob";

Full Script

configure-matching-and-sorting
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#!/usr/bin/env python

# pylint: disable=misplaced-comparison-constant,missing-docstring
from __future__ import print_function

from os import environ
from os.path import exists, join
from re import compile as re_compile, finditer, match, MULTILINE, VERBOSE
from shutil import copy
from subprocess import check_output, PIPE, Popen
from sys import argv, exit as sys_exit
from tempfile import NamedTemporaryFile
from types import StringTypes

from argparse import ArgumentParser, SUPPRESS

CONFIG_FILE = join(environ['XDG_USER_CONFIG_DIR'], 'rofi', 'config.rasi')

ACTIVE_CHOICE_IDENTIFIER = " (active)"

MATCHING_METHODS = [
'fuzzy',
'glob',
'normal',
'regex',
]

FLAG_STATES = [
'true',
'false'
]

DEFAULTS = {
'matching': 'normal',
'sort': 'true',
'levenshtein-sort': 'false'
}

MESSAGES = {
'matching': 'change matching method',
'sort': 'enable sorting',
'levenshtein-sort': 'force levenshtein sorting'
}

CONFIG_OPTIONS_RAW_PATTERN = r"""
^
.*?
\s
(?P<option>
matching
|
(?:levenshtein-)?sort
)
:\s*
[\"']?
(?P<value>
.*?
)
[\"']?;
.*?
$
"""

CONFIG_OPTIONS_PATTERN = re_compile(
CONFIG_OPTIONS_RAW_PATTERN,
VERBOSE | MULTILINE
)


def review_diff():
"""Compares the config before and after running"""
with NamedTemporaryFile() as suppressed_output:
result_pipe = Popen(
[
'diff',
'--color=always',
'--unified=0',
'-t',
'--tabsize=4',
"%s.bak" % CONFIG_FILE,
CONFIG_FILE
],
stdout=suppressed_output,
)
result_pipe.communicate()
suppressed_output.seek(0)
result = suppressed_output.read()
print(result)


def update_config(key, value):
"""Updates the config file"""
if isinstance(value, StringTypes):
if not match("true|false", value):
value = '"%s"' % value
check_output(
[
'sed',
'-i',
'-e',
r"s/^.*\s%s:.*$/\t%s: %s;/g" % (key, key, value),
CONFIG_FILE
],
)


def validate_item(item_possibilities, defaults_key, desired_value):
"""
Validates the passed-in value. On failure, validates the current default. On
failure, returns the first possibility.
"""
if desired_value in item_possibilities:
return desired_value
desired_value = DEFAULTS[defaults_key]
if desired_value in item_possibilities:
return desired_value
return item_possibilities[0]


def pipe_choices_to_rofi(choices, defaults_key):
"""Throws the choices out via rofi and returns the result"""
rofi_pipe = Popen(
[
'rofi',
'-dmenu',
'-only-match',
'-mesg', "%s" % MESSAGES[defaults_key],
'-no-fixed-num-lines',
'-width', "%d" % (len(MESSAGES[defaults_key]) + 1),
'-hide-scrollbar',
'-theme-str',
'#inputbar {'
' children: [entry,case-indicator]; '
'}',
'-theme-str',
'#listview {'
' dynamic: true; '
'}',
'-theme-str',
'#mainbox {'
' children: [message,inputbar,listview]; '
'}',
'-theme-str',
'#message {'
' border: 0;'
' background-color: @selected-normal-background;'
' text-color: @selected-normal-foreground; '
'}',
'-theme-str',
'#textbox {'
' text-color: inherit; '
'}'
],
stdin=PIPE,
stdout=PIPE,
)
choices_pipe = Popen(
[
'echo',
'-e',
'\n'.join(choices),
],
stdout=rofi_pipe.stdin,
)
result = rofi_pipe.communicate()[0]
choices_pipe.wait()
result = result.strip()
if not result:
result = choices[0]
result = result.replace(ACTIVE_CHOICE_IDENTIFIER, "")
return result


def create_choices(all_choices, current_choice):
"""Creates a list of choices for dmenu"""
choices = ["%s%s" % (current_choice, ACTIVE_CHOICE_IDENTIFIER)]
all_choices.sort()
for choice in all_choices:
if current_choice != choice:
choices.append(choice)
return choices


def find_desired_item(item_possibilities, defaults_key):
"""Builds the dmenu list, polls the user, and validates the result"""
choices = create_choices(item_possibilities, DEFAULTS[defaults_key])
value = pipe_choices_to_rofi(choices, defaults_key)
return validate_item(item_possibilities, defaults_key, value)


def parse_and_update_desired_item(item_possibilities, defaults_key, options):
"""
Attempts to use passed-in values when possible. Otherwise polls the user for
each value via dmenu (via rofi)
"""
desired_value = getattr(options, defaults_key, None)
if (
not desired_value
or
desired_value != validate_item(
item_possibilities,
defaults_key,
desired_value
)
):
desired_value = find_desired_item(item_possibilities, defaults_key)
update_config(defaults_key, desired_value)


def determine_everything(options):
"""Parses and updates matching, sorting, and Levenshstein sorting options"""
parse_and_update_desired_item(MATCHING_METHODS, 'matching', options)
parse_and_update_desired_item(FLAG_STATES, 'sort', options)
parse_and_update_desired_item(FLAG_STATES, 'levenshtein-sort', options)


def parse_argv(args=None):
"""Parses CLI args"""
if args is None:
args = argv[1:]
parser = ArgumentParser(
description="Configure rofi's matching and sorting"
)
parser.add_argument(
'--skip-diff',
dest='skip_diff',
action='store_true',
help="Don't print the config diff"
)
exclusive_options = parser.add_mutually_exclusive_group()
exclusive_options.add_argument(
'-o', '--only',
dest='only',
default=SUPPRESS,
choices=['matching', 'sort', 'levenshtein-sort'],
help='Change only the specified option',
)
config_options = parser.add_argument_group()
config_options.add_argument(
'-m', '--matching',
dest='matching',
nargs='?',
const=DEFAULTS['matching'],
default=SUPPRESS,
choices=MATCHING_METHODS,
help='Sets the matching method',
)
config_options.add_argument(
'-s', '--sort',
dest='sort',
nargs='?',
const=DEFAULTS['sort'],
default=SUPPRESS,
choices=FLAG_STATES,
help='Enables sorting',
)
config_options.add_argument(
'-l', '--levenshtein-sort',
dest='levenshtein-sort',
nargs='?',
const=DEFAULTS['levenshtein-sort'],
default=SUPPRESS,
choices=FLAG_STATES,
help='Forces Levenshtein sorting',
)
return parser.parse_args(args)


def create_config_if_dne(raw_config):
"""Creates a new config file if it doesn't exist"""
if not exists(CONFIG_FILE):
with open(CONFIG_FILE, 'w+') as config_file:
config_file.write(raw_config)


def load_config():
"""Loads the active rofi config"""
raw_config = check_output(['rofi', '-dump-config'])
for config_match in finditer(CONFIG_OPTIONS_PATTERN, raw_config):
DEFAULTS[config_match.group('option')] = config_match.group('value')
create_config_if_dne(raw_config)


def backup_config():
"""Backs up the current config"""
copy(CONFIG_FILE, CONFIG_FILE + '.bak')


def cli():
"""Bootstraps the app"""
load_config()
backup_config()
options = parse_argv()
if getattr(options, 'only', None):
for key, value in DEFAULTS.iteritems():
if key != options.only:
setattr(options, key, value)
determine_everything(options)
if not options.skip_diff:
review_diff()
sys_exit(0)

if '__main__' == __name__:
cli()

Change Matching and Sorting Via a modi

After the last post's modi, I wanted to see if I could repeat something similar here. This script is still pretty raw, but it gets the job done. Total GUI, like before.

rofi-tweak-sort
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#!/usr/bin/env python

# pylint: disable=misplaced-comparison-constant,missing-docstring
from __future__ import print_function

from os import chmod, environ
from os.path import exists, join
from re import compile as re_compile, finditer, match, MULTILINE, VERBOSE
from shutil import copy
from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH
from subprocess import check_output, Popen
from sys import argv, exit as sys_exit
from types import StringTypes

from argparse import ArgumentParser

CONFIG_FILE = join(environ['XDG_USER_CONFIG_DIR'], 'rofi', 'config.rasi')

BASE_OPTIONS = [
'set matching',
'set sort',
'set levenshtein-sort',
'exit'
]

MATCHING_METHODS = [
'fuzzy',
'glob',
'normal',
'regex'
]

DEFAULTS = {
'matching': 'normal',
'sort': 'true',
'levenshtein-sort': 'false',
'pid': "%s" % join(environ['XDG_RUNTIME_DIR'], "rofi.pid")
}

MATCHING_OPTIONS = []

FLAG_STATES = [
'true',
'false'
]

SORT_OPTIONS = []

LEVENSHTEIN_SORT_OPTIONS = []

ACTIVE_CHOICE_IDENTIFIER = " (active)"

CONFIG_OPTIONS_RAW_PATTERN = r"""
^
.*?
\s
(?P<option>
matching
|
(?:levenshtein-)?sort
|
pid
)
:\s*
[\"']?
(?P<value>
.*?
)
[\"']?;
.*?
$
"""

CONFIG_OPTIONS_PATTERN = re_compile(
CONFIG_OPTIONS_RAW_PATTERN,
VERBOSE | MULTILINE
)


def clean_exit():
"""Exits with code 0"""
sys_exit(0)


def print_and_exit(options):
"""Dumps everything to STDOUT for rofi"""
for option in options:
print(option)
clean_exit()


def spawn_and_die():
"""Creates a new process and dies"""
runner = create_new_runner()
Popen(['bash', '-c', "coproc %s" % runner])
clean_exit()


def parse_pid():
"""Snags the original rofi command"""
with open(DEFAULTS['pid'], 'r') as pid_file:
process_id = pid_file.read().strip()
result = check_output(
[
'ps',
'--no-headers',
'-o', 'command',
'-p', "%s" % process_id
]
)
return result.strip()


def create_new_runner():
"""Creates a new runner script"""
result = check_output(
[
'mktemp',
'-p', environ['TMPDIR'],
'rofi-tweak-sort-XXXX'
],
)
result = result.strip()
root_command = parse_pid()
with open(result, 'w') as tempfile:
tempfile.write(
"""
#!/bin/bash
%s
rm -rf %s
""" % (root_command, result)
)
chmod(result, S_IRUSR | S_IWUSR | S_IXUSR |
S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
return result


def update_config(key, value):
"""Updates the config file"""
if isinstance(value, StringTypes):
if not match("true|false", value):
value = '"%s"' % value
check_output(
[
'sed',
'-i',
'-e',
r"s/^.*\s%s:.*$/\t%s: %s;/g" % (key, key, value),
CONFIG_FILE
],
)
spawn_and_die()


def base_options():
"""The root options"""
print_and_exit(BASE_OPTIONS)


def construct_matching_options():
"""Constructs the options for matching"""
for value in MATCHING_METHODS:
MATCHING_OPTIONS.append(
'set matching %s%s' % (
value,
ACTIVE_CHOICE_IDENTIFIER
if value == DEFAULTS['matching']
else ''
)
)
MATCHING_OPTIONS.append('set ^')


def set_matching_options(value=None):
"""Sets matching options"""
construct_matching_options()
if value:
value = value.replace(ACTIVE_CHOICE_IDENTIFIER, "")
if value in MATCHING_METHODS:
update_config('matching', value)
else:
print_and_exit(MATCHING_OPTIONS)


def construct_sort_options():
"""Constructs the options for basic sorting"""
for value in FLAG_STATES:
SORT_OPTIONS.append(
'set sort %s%s' % (
value,
ACTIVE_CHOICE_IDENTIFIER
if value == DEFAULTS['sort']
else ''
)
)
SORT_OPTIONS.append('set ^')


def set_sort_options(value=None):
"""Sets sort options"""
construct_sort_options()
if value:
value = value.replace(ACTIVE_CHOICE_IDENTIFIER, "")
if value in FLAG_STATES:
update_config('sort', value)
else:
print_and_exit(SORT_OPTIONS)


def construct_levenshtein_sort_options(): # pylint:disable=invalid-name
"""Constructs the options for levenshtein sorting"""
for value in FLAG_STATES:
LEVENSHTEIN_SORT_OPTIONS.append(
'set levenshtein-sort %s%s' % (
value,
ACTIVE_CHOICE_IDENTIFIER
if value == DEFAULTS['levenshtein-sort']
else ''
)
)
LEVENSHTEIN_SORT_OPTIONS.append('set ^')


def set_levenshtein_sort_options(value=None):
"""Sets Levenshtein sort options"""
construct_levenshtein_sort_options()
if value:
value = value.replace(ACTIVE_CHOICE_IDENTIFIER, "")
if value in FLAG_STATES:
update_config('levenshtein-sort', value)
else:
print_and_exit(LEVENSHTEIN_SORT_OPTIONS)


def parse_config_args(args):
"""Parses config arguments"""
config_parser = ArgumentParser()
config_subparsers = config_parser.add_subparsers()
matching_parser = config_subparsers.add_parser('matching')
matching_parser.add_argument('value', default=None, nargs='?')
matching_parser.set_defaults(func=set_matching_options)
sort_parser = config_subparsers.add_parser('sort')
sort_parser.add_argument('value', default=None, nargs='?')
sort_parser.set_defaults(func=set_sort_options)
levenshtein_sort_parser = config_subparsers.add_parser(
'levenshtein-sort')
levenshtein_sort_parser.add_argument('value', default=None, nargs='?')
levenshtein_sort_parser.set_defaults(func=set_levenshtein_sort_options)
try:
config_options = config_parser.parse_known_args(args)[0]
except SystemExit:
config_options = None
if config_options:
config_options.func(config_options.value)


def parse_root_args(args):
"""Parses base level args or escalates"""
root_parser = ArgumentParser()
root_subparsers = root_parser.add_subparsers()
set_parser = root_subparsers.add_parser('set')
exit_parser = root_subparsers.add_parser('exit')
set_parser.set_defaults(func=base_options)
exit_parser.set_defaults(func=clean_exit)
try:
options, command_args = root_parser.parse_known_args(args)
except SystemExit:
return base_options()
parse_config_args(command_args)
return options.func()


def prepare_args(args=None):
"""Cleans the args for processing"""
if args is None:
args = argv[1:]
if 1 > len(args):
base_options()
else:
new_args = []
for arg in args:
new_args += arg.split(' ')
args = new_args
return args


def parse_argv(args=None):
"""Parses the input"""
args = prepare_args(args)
parse_root_args(args)


def create_config_if_dne(raw_config):
"""Creates a new config file if it doesn't exist"""
if not exists(CONFIG_FILE):
with open(CONFIG_FILE, 'w+') as config_file:
config_file.write(raw_config)


def backup_config():
"""Backs up the current config"""
copy(CONFIG_FILE, CONFIG_FILE + '.bak')


def load_config():
"""Loads the active rofi config"""
raw_config = check_output(['rofi', '-dump-config'])
for config_match in finditer(CONFIG_OPTIONS_PATTERN, raw_config):
DEFAULTS[config_match.group('option')] = config_match.group('value')
create_config_if_dne(raw_config)


def cli():
load_config()
backup_config()
parse_argv()

if '__main__' == __name__:
cli()

As before, I'd recommend actually installing this to a common location.

$ mkdir -p $XDG_USER_CONFIG_DIR/rofi/scripts
$ cp scripts/rofi-tweak-sort $XDG_USER_CONFIG_DIR/rofi/scripts/tweak-sort
$ awk \
-i inplace \
-v INPLACE_SUFFIX='.bak' \
-v MODI="tweak-sort:$XDG_USER_CONFIG_DIR/rofi/scripts/tweak-sort" \
' \
match($0, /\s(combi-)?modi:[^"]*"([^"]*)"/, option) { \
current_modi = gensub(/tweak-sort:[^,]*/, "", "g", option[2]); \
final_modi = MODI","current_modi; \
printf "\t%smodi: \"%s\";\n", option[1], gensub(/,+/, ",", "g", final_modi); \
next; \
} \
{ \
print; \
}' \
$XDG_USER_CONFIG_DIR/rofi/config.rasi

CJ Harries

I did a thing once. Change "blog." to "cj@" and you've got my email. All these opinions are mine and might not be shared by clients or employers.

Read More