Coder Social home page Coder Social logo

tofi's Introduction

Tofi

An extremely fast and simple dmenu / rofi replacement for wlroots-based Wayland compositors such as Sway.

The aim is to do just what I want it to as quick as possible.

When configured correctly, tofi can get on screen within a single frame.

Table of Contents

Install

Building

Install the necessary dependencies.

For Arch:

# Runtime dependencies
sudo pacman -S freetype2 harfbuzz cairo pango wayland libxkbcommon

# Build-time dependencies
sudo pacman -S meson scdoc wayland-protocols

For Fedora

# Runtime dependencies
sudo dnf install freetype-devel cairo-devel pango-devel wayland-devel libxkbcommon-devel harfbuzz

# Build-time dependencies
sudo dnf install meson scdoc wayland-protocols-devel

For Debian/Ubuntu

# Runtime dependencies
sudo apt install libfreetype-dev libcairo2-dev libpango1.0-dev libwayland-dev libxkbcommon-dev libharfbuzz-dev

# Build-time dependencies
sudo apt install meson scdoc wayland-protocols

Then build:

meson build && ninja -C build install

Arch

Tofi is available in the AUR:

paru -S tofi

Usage

By default, running tofi causes it to act like dmenu, accepting options on stdin and printing the selection to stdout.

tofi-run is a symlink to tofi, which will cause tofi to display a list of executables under the user's $PATH.

tofi-drun is also a symlink to tofi, which will cause tofi to display a list of applications found in desktop files as described by the Desktop Entry Specification.

To use as a launcher for Sway, add something similar to the following to your Sway config file:

set $menu tofi-run | xargs swaymsg exec --
bindsym $mod+d exec $menu

For tofi-drun, there are two possible methods:

# Launch via Sway
set $drun tofi-drun | xargs swaymsg exec --
bindsym $mod+Shift+d exec $drun

# Launch directly
set $drun tofi-drun --drun-launch=true
bindsym $mod+Shift+d exec $drun

See the main manpage for more info.

Theming

Tofi supports a fair number of theming options - see the default config file or the config file manpage for a complete description. Theming is based on the box model shown below:

Default theme screenshot

This consists of a box with a border, border outlines and optionally rounded corners. Text inside the box can either be laid out vertically:

╔═══════════════════╗
║ prompt   input    ║
║          result 1 ║
║          result 2 ║
║          ...      ║
╚═══════════════════╝

or horizontally:

╔═══════════════════════════════════════════╗
║ prompt   input    result 1  result 2  ... ║
╚═══════════════════════════════════════════╝

Each piece of text can have its colour customised, and be surrounded by a box with optionally rounded corners,

A few example themes are included and shown below. Note that you may need to tweak them to look correct on your display.

themes/fullscreen Fullscreen theme screenshot

themes/dmenu dmenu theme screenshot

themes/dos DOS theme screenshot

themes/dark-paper Dark paper theme screenshot

themes/soy-milk Soy milk theme screenshot

Performance

By default, tofi isn't really any faster than its alternatives. However, when configured correctly, it can startup and get on screen within a single frame, or about 2ms in the ideal case.

Options

In roughly descending order, the most important options for performance are:

  • --font - This is by far the most important option. By default, tofi uses Pango for font rendering, which (on Linux) looks up fonts via Fontconfig. Unfortunately, this font lookup is about as slow as wading through treacle (relatively speaking). On battery power on my laptop (Arch linux, AMD Ryzen 5 5600U), with ~10000 fonts as the output of fc-list, loading a single font with Pango & Fontconfig takes ~120ms.

    The solution is to pass a path to a font file to --font, e.g. --font /usr/share/fonts/noto/NotoSansMono-Regular.ttf. Tofi will then skip any font searching, and use Harfbuzz and Cairo directly to load the font and display text. This massively speeds up startup (font loading takes <1ms). The (minor for me) downside is that any character not in the specified font won't render correctly, but unless you have commands (or items) with CJK characters or emojis in their names, that shouldn't be an issue.

  • --width, --height - Larger windows take longer to draw (mostly just for the first frame). Again, on battery power on my laptop, drawing a fullscreen window (2880px × 1800px) takes ~20ms on the first frame, whereas a dmenu-like ribbon (2880px × 60px) takes ~1ms.

  • --num-results - By default, tofi auto-detects how many results will fit in the window. This is quite tricky when --horizontal=true is passed, and leads to a few ms slowdown (only in this case). Setting a fixed number of results will speed this up, but since this likely only applies to dmenu-like themes (which are already very quick) it's probably not worth setting this.

  • --*-background - Drawing background boxes around text effectively requires drawing the text twice, so specifying a lot of these options can lead to a couple of ms slowdown.

  • --hint-font - Getting really into it now, one of the remaining slow points is hinting fonts. For the dmenu theme on battery power on my laptop, with a specific font file chosen, the initial text render with the default font hinting takes ~4-6ms. Specifying --hint-font false drops this to ~1ms. For hidpi screens or large font sizes, this doesn't noticeably impact font sharpness, but your mileage may vary. This option has no effect if a path to a font file hasn't been passed to --font.

  • --ascii-input - Proper Unicode handling is slower than plain ASCII - on the order of a few ms for ~40 kB of input. Specifying --ascii-input true will disable some of this handling, speeding up tofi's startup, but searching for non-ASCII characters may not work properly.

  • --late-keyboard-init - The last avoidable thing that slows down startup is initialisation of the keyboard. This only takes 1-2ms on my laptop, but up to 60ms on a Raspberry Pi Zero 2 W. Passing this option will delay keyboard initialisation until after the first draw to screen, meaning that keypresses will be missed until then, so it's disabled by default.

Benchmarks

Below are some rough benchmarks of the included themes on different machines. These were generated with version 0.1.0 of tofi. The time shown is measured from program launch to Sway reporting that the window has entered the screen. Results are the mean and standard deviation of 10 runs. All tests were performed with --font /path/to/font/file.ttf, --hint-font false and the equivalent of --ascii-input true (as tofi 0.1.0 didn't support Unicode text).

Theme
fullscreen dmenu dos
Machine Ryzen 7 3700X
2560px × 1440px
9.5ms ± 1.8ms 5.2ms ± 1.5ms 6.1ms ± 1.3ms
Ryzen 5 5600U (AC)
2880px × 1800px
17.1ms ± 1.4ms 4.0ms ± 0.5ms 6.7ms ± 1.1ms
Ryzen 5 5600U (battery)
2880px × 1800px
28.1ms ± 3.7ms 6.0ms ± 1.6ms 12.3ms ± 3.4ms
Raspberry Pi Zero 2 W
1920px × 1080px
119.0ms ± 5.9ms 67.3ms ± 10.2ms 110.0ms ± 10.3ms

The table below additionally includes --late-keyboard-init in the arguments.

Theme
fullscreen dmenu dos
Machine Ryzen 7 3700X
2560px × 1440px
7.9ms ± 1.0ms 2.3ms ± 0.8ms 3.8ms ± 0.8ms
Ryzen 5 5600U (AC)
2880px × 1800px
13.4ms ± 0.8ms 2.6ms ± 0.5ms 5.5ms ± 0.51ms
Ryzen 5 5600U (battery)
2880px × 1800px
21.8ms ± 1.8ms 3.6ms ± 0.7ms 8.1ms ± 0.7ms
Raspberry Pi Zero 2 W
1920px × 1080px
98.3ms ± 5.7ms 44.8ms ± 16.3ms 87.4ms ± 9.9ms

Bonus Round: Transparent HugePages

It turns out that it's possible to speed up fullscreen windows somewhat with some advanced memory tweaks. See this Stack Overflow question if you want full details, but basically by setting /sys/kernel/mm/transparent_hugepage/shmem_enabled to advise, we can tell the kernel we're going to be working with large memory areas. This results in fewer page faults when first allocating memory, speeding up tofi.

Note that I don't recommend you play with this unless you know what you're doing (I don't), but I've included it just in case, and to show that the slowdown on large screens is partially due to factors beyond tofi's control.

The table below shows the effects of additionally enabling hugepages from the table above. The dmenu theme has been skipped, as the window it creates is too small to benefit from them. The Raspberry Pi is also omitted, as it doesn't support hugepages.

Theme
fullscreen dos
Machine Ryzen 7 3700X
2560px × 1440px
6.9ms ± 1.1ms 3.2ms ± 0.4ms
Ryzen 5 5600U (AC)
2880px × 1800px
7.9ms ± 1.2ms 3.4ms ± 1.0ms
Ryzen 5 5600U (battery)
2880px × 1800px
13.7ms ± 0.9ms 5.6ms ± 0.8ms

Where is the time spent?

For those who are interested in how much time there is even left to save, I've plotted the startup performance of version 0.8.0 of tofi-run below, alongside the corresponding debug output. This is the data from 1000 runs of the dmenu theme on a Ryzen 7 3700X machine, with all performance options set as mentioned above, along with --num-results 10. I've highlighted some points of interest, most of which are out of tofi's control.

Startup performance plot

(You may want to click the image to see it at full size).

Note that this is slightly faster than shown in previous benchmarks (with some runs under 1.5ms!), due to some string handling improvements made in version 0.8.0. Also note that the real performance is slightly better still, as the performance logging used slows down the code by roughly 10%.

As you can see, there's not a huge amount of time that could even theoretically be saved. Somewhere around 50% of the startup time is simply spent waiting, and most of the code isn't parallelisable, as many steps depend on the result of previous steps. One idea would be to daemonize tofi, skipping much of this startup. I don't want to do this, however, for two main reasons: complexity, and I think it's probably about fast enough already!

tofi's People

Contributors

akdjka avatar b1rger avatar bencollerson avatar dimkr avatar jbeich avatar myrrc avatar niki-on-github avatar philj56 avatar sktt avatar tmneth avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

tofi's Issues

Hyprland crashes when switching to other system on kvm switch

When I switch from my desktop to my laptop on my kvm switch hyprland (running on my desktop) crashes. This is repeatable behavior. I'm not sure if the crash happens after I switch from the desktop, or when I switch back. The kvm I'm using does not send EDID data.

I know this use case works on GNOME on wayland, so a fix is most likely possible.
Environment is Arch linux using the hyprland-nvidia-git AUR package and the recommended nvidia modifications from the wiki https://wiki.hyprland.org/Nvidia/

What other info do you need to be able to investigate this?

A way to theme selected text

Right now almost everything is themeable, however, I want to make it so that the color of the text i've typed in is different

image

For example in the screenshot above, the text i've already typed (alacr) should be a different color in what i'm selecting than black to indicate what portion of the text is matching

Height not scaled properly?

Debian bookworm's tofi 0.1.1-2, or aaccdaa.

anchor = top
width = 100%
height = 26
horizontal = true
font-size = 10
prompt-text = "$ "
font = monospace
outline-width = 0
border-width = 0
background-color = #000000
text-color = #ffffff
selection-color = #000000
selection-background = #ffffff
num-results = 10
min-input-width = 64
result-spacing = 10
padding-top = 4
padding-bottom = 4
padding-left = 4
padding-right = 4

Scale factor 1:

1

Scale factor 2:

2

100% width not operating as expected with vertical monitors

I use a mix of vertical and horizontal monitors, when I open it on my horizontal monitor tofi spans across my entire screen
2022-07-26_11-50

But when I open it on my vertical monitor, it seems to keep using the same width as the horizontal monitor
2022-07-26_11-52

Without seeing the code if I had to guess, width 100% is probably being based off of the main monitor width

Provide option to select top or overlay layer and to enable exclusive zone

Tofi is currently using ZWLR_LAYER_SHELL_V1_LAYER_TOP. An option should be provided to use ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY instead.

I see 2 good reasons to use overlay instead of top.

(1) Some people, like me, configure their status bar (ie. sway-bar, waybar, ...) in the top layer to keep it above the application windows. Unfortunately, that also means that tofi appears below the bar which makes it unusable when anchored at same border than the bar in horizontal mode.

(2) The overlay layer is visible when running a fullscreen application while the top layer is not.

You should also consider an option to enable the exclusive zone. That would be an effective way to keep tofi and the bar to overlap.

Improvement to scrolling

So, right now when I scroll down, instead of it moving the bottom thing so that it's fully visible, it goes to the bottom segment and leaves it cut-off, I believe this is an issue related to the fact that tofi now tries to fill the screen with options, it'd be better if the list scrolled down to make the bottom option fully visible when it is highlighted, and then continued to keep the bottom entry at the bottom as you continue to scroll, instead of wrapping around to the top.

in run mode, accept arguments

It seems it is not possible to execute a cli with parameters from tofi-run. On rofi, for instance, I was able to autocomplete my command then add any arguments after it, simply running the command as-is

Make 0 width/height be the minimum while showing all results

0 width/height should be the smallest possible size that displays everything without cutting anything off by default to maximize performance, I think that'd be a better default, although much less useful with height (in vertical mode, width in horizontal)

-1 should be fullscreened

This will let me use tofi for power management while making the power management window as small as possible, I could set it to be a specific number of pixels, but that would mess with scaling.

Space character

Just noticed that tofi doesn't appear to allow space input. Why is that? My use case is alongside --require-match false.

Thanks for the resolutions to my other issues by the way! 🙂

Delay when inputting from swaywm keybinding

I suspect this may have to do with sway rather than tofi, but I'm not sure, as I've not encountered the following issue with rofi or bemenu.

My swaywm configuration file contains the following:

bindsym $mod+a exec $run
set $run 'tofi-run --config ~/.config/tofi/config.ini --hint-font false --late-keyboard-init'

The configuration file of tofi is basically a slightly modified version of the dmenu theme, being as follows:

anchor = bottom
width = 100%
height = 19
horizontal = true
font = /usr/share/fonts/misc/ter-u20n.otb
font-size = 15
prompt-text = " run: "
outline-width = 0
border-width = 0
background-color = #121212
num-results = 12
min-input-width = 150
result-spacing = 14
padding-top = 0
padding-bottom = 0
padding-left = 0
padding-right = 0
history = false

The command I mentioned above works perfectly fine and as expect when passed on a terminal. But, for some reason, when using the sway keybinding ($mod+a, in this case), there appears to be a logical delay on input. What I mean by that is: the text displayed on the search bar is always behind by 1 character. So, if I input 'Chromium', nothing would appear on the first keypress, a 'C' would appear on the second, until I finish typing and 'Chromiu' appears on screen. If I press enter, tofi runs the first search result well enough. If I keep typing, or even if I press backspace, the delay continues (e.g. if I press backspace in the situation above, while 'Chromiu' is still displayed, the "buffered" 'm' would appear and only on the next keypress will the backspace register, same as any other character.

I realise this is a kind of confusing issue to describe, so, if I can help clarify what I mean, I would be more than happy to do so.

Edit: I forgot to mention that, sometimes, for no apparent reason, tofi works fine when using the sway keybinding. Most of the time, though, there is the delay described.

Second edit: ok, this is kind of stupid, but I think this has to do with how long I press the keybound combo. If I press $mod+a really fast, I don't experience the described issue. If, say, I hold the combo for a tenth of a second or more (estimating here), the delay makes itself present. This explains why passing the command through the terminal never presents the issue, as I can't "hold" the command; it is always instant.

the hide cursor option is ineffective because there appears to be no cursor no matter what

tofi-drun --font /usr/share/fonts/noto/NotoSansMono-Regular.ttf --font-size 14 --background-color 000000 --width 30% --height 50% --outline-width 0px --outline-color cc0000 --border-width 1px --border-color cc0000 --selection-color 000000 --selection-background cc0000 --drun-launch true --corner-radius 0 --selection-padding 4192 --prompt-text " " --history true --hide-cursor false

with and without the hide-cursor option changes nothing, I tested changing the background color, even with a white background no cursor appears

edit: no wait i'm stupid it means hiding the mouse cursor closing sorry

Packaging for flatpak

It would be great to install tofi from flatpak.

For systems without AUR (btw) and when you are forced to use Ubuntu 20.04 LTS with packages so old, that even meson version isn't enough (not to mention other dependencies).

readline binds

Specifically support for <C-w> and/or <C-u> would match dmenu and most other software in this space.

click outside to dismiss

Currently clicking outside of tofi's window does nothing which conflicts with my muscle memory :) Is it possible to dismiss tofi when clicking outside?

Name of executed program shows up in output, but nothing runs

Hello,
I have compiled tofi on both gentoo and OpenMandriva, and with both I've found that I'm not able to run things with it.
When running tofi-run, the application shows up in the menu and pressing enter to run it sends the name of the application to output, but it doesn't run.

The output is this, to run the terminal foot:

[ real,   cpu,   maxRSS]
[0.000, 0.008,  7744 KB][DEBUG]: This is tofi.
[0.000, 0.009, 10284 KB][DEBUG]: First roundtrip start.
[0.000, 0.009, 10284 KB][DEBUG]: 	Bound to shm 1.
[0.000, 0.009, 10284 KB][DEBUG]: 	Bound to compositor 4.
[0.000, 0.009, 10284 KB][DEBUG]: 	Bound to seat 15.
[0.000, 0.009, 10284 KB][DEBUG]: 	Bound to zwlr_layer_shell_v1 18.
[0.000, 0.009, 10284 KB][DEBUG]: 	Bound to output 41.
[0.000, 0.009, 10284 KB][DEBUG]: 	Bound to output 42.
[0.000, 0.009, 10284 KB][DEBUG]: First roundtrip done.
[0.000, 0.009, 10284 KB][DEBUG]: Second roundtrip start.
[0.000, 0.009, 10284 KB][DEBUG]: 	Got keyboard from seat.
[0.000, 0.009, 10284 KB][DEBUG]: 	Got pointer from seat.
[0.000, 0.009, 10284 KB][DEBUG]: 	Output configuration done.
[0.000, 0.009, 10284 KB][DEBUG]: 	Output configuration done.
[0.000, 0.009, 10284 KB][DEBUG]: Second roundtrip done.
[0.001, 0.009, 10284 KB][DEBUG]: Configuring keyboard.
[0.003, 0.012, 10932 KB][DEBUG]: Keyboard configured.
[0.003, 0.012, 10932 KB][DEBUG]: Key repeat every 40 ms after 600 ms.
[0.003, 0.012, 10932 KB][DEBUG]: Created shm file with size 0 KiB.
[0.004, 0.012, 10932 KB][DEBUG]: Selected output HDMI-A-2.
[0.004, 0.012, 10932 KB][DEBUG]: Generating command list.
[0.004, 0.012, 10932 KB][DEBUG]: 	Retrieving PATH.
[0.004, 0.012, 10932 KB][DEBUG]: 	Retrieving cache location.
[0.004, 0.012, 10932 KB][DEBUG]: 	Cache up to date, loading.
[0.005, 0.013, 10932 KB][DEBUG]: 	Moving already known programs to the front.
[0.005, 0.013, 10932 KB][DEBUG]: Command list generated.
[0.005, 0.013, 10932 KB][DEBUG]: Creating main window surface.
[0.005, 0.013, 10932 KB][DEBUG]: Third roundtrip start.
[0.007, 0.013, 10932 KB][DEBUG]: 	Layer surface configure, 1280 x 720.
[0.007, 0.013, 10932 KB][DEBUG]: Third roundtrip done.
[0.007, 0.013, 10932 KB][DEBUG]: Initialising window surface.
[0.007, 0.013, 10932 KB][DEBUG]: 	Created shm file with size 7200 KiB.
[0.007, 0.013, 10932 KB][DEBUG]: Window surface initialised.
[0.007, 0.013, 10932 KB][DEBUG]: Initialising renderer.
[0.011, 0.018, 15132 KB][DEBUG]: 	Creating Pango context.
[0.012, 0.018, 15696 KB][DEBUG]: 	Creating Pango font description.
[0.012, 0.018, 15696 KB][DEBUG]: 	Initial text render.
[0.029, 0.042, 21128 KB][DEBUG]: 	Drew 13 results.
[0.029, 0.042, 21128 KB][DEBUG]: Renderer initialised.
[0.032, 0.042, 21128 KB][DEBUG]: Layer surface configure, 1280 x 720.
[0.032, 0.042, 21128 KB][DEBUG]: Surface entered output.
[2.053, 0.048, 24816 KB][DEBUG]: Start rendering entry.
[2.057, 0.052, 25008 KB][DEBUG]: Drew 13 results.
[2.057, 0.052, 25008 KB][DEBUG]: Finish rendering entry.
[2.184, 0.052, 25008 KB][DEBUG]: Start rendering entry.
[2.189, 0.057, 27968 KB][DEBUG]: Drew 13 results.
[2.189, 0.057, 27968 KB][DEBUG]: Finish rendering entry.
[2.292, 0.057, 27968 KB][DEBUG]: Start rendering entry.
[2.294, 0.059, 27968 KB][DEBUG]: Drew 2 results.
[2.294, 0.059, 27968 KB][DEBUG]: Finish rendering entry.
[2.364, 0.059, 27968 KB][DEBUG]: Start rendering entry.
[2.365, 0.061, 27968 KB][DEBUG]: Drew 2 results.
[2.365, 0.061, 27968 KB][DEBUG]: Finish rendering entry.
[2.741, 0.061, 27968 KB][DEBUG]: Window closed, performing cleanup.
[2.745, 0.062, 27968 KB][DEBUG]: Finished, exiting.
foot

Is there something I'm doing wrong?

Open a new terminal when asked to do so

Some programs have a desktop file with the option Terminal=true and should be opened in a new terminal window.
lf is an example and does not open in a new terminal window.

Is this feature missing and the Terminal option ignored?

Benchmark comparison with popular launchers

Hello. According to readme in repo the main point of tofi is speed.
May you provide some benchmarks to compare tofi with popular launchers like bemenu, fuzzel, and rofi-wl?
Thanks.

Show maximum number of entries screen allows

Currently you can specify a maximum number of entries, but this isn't very good for if you use width and height as percentages and then move to a smaller display, not a huge problem though, but the default behavior should be to use all the available screen real estate should it not?

[Suggestion] Sort by relevancy and then alphabetically

I am not sure if this is in the scope of this project, but I think tofi could be improved by changing the order the results are displayed after a query.

For example:

image

I query "pl" and the first result shown is alphabetically first, but, I believe, less relevant to my search. Ideally, the results would begin with the most relevant first, starting the search from the beginning of the word, and only then show results that merely "contain" the combination of letters queried.

So, in my case, PlugY should be displayed before Joplin, although both satisfy the condition set.

Prevent multiple instances to be opened

I know this is not a crucial feature, but I mention it for the record.

When the user opens tofi multiple times by accident, multiple instances open. Because it is annoying to close multiple instances, it would be great to have at most one instance open at a time.

Proposal:
The current pid is written to $XDG_RUNTIME_DIR/<app>.pid. The existence and content of the file is checked on startup. Exit if the pid specified in the file does still exist.

[feature]: equivalent of fzf --with-nth

let's say, i have for instance the following file.txt:

useful dont_show number one
also_useful idk number two

i would like to filter out some information, and only display a portion of it to the user. with fzf, i could do the following
fzf --with-nth 3.. < file.txt

image

and after i select one of the entries, i get back the whole line with other useful information, which i would not like to be displayed, but would like to use

image

would it be possible for this feature to be implemented for the dmenu mode, and ideally would it be possible to also integrate regex matching into it, instead of using awk/cut delimiters, like fzf is currently doing? hopefully i was clear enough, sorry if some parts might've sounded confusing

Release it

Can you please create a release – tag a version?

package in aur

I would like to have this project in aur, by the way good work

A border around the selected item

image
As it currently stands with how little there is to the selected text, it looks a bit off, if there was a bit of padding to the background color on the left and right i think it would look a lot better, thoughts?

edit: alternatively you could make the selection background go all the way to the left/right of the selection in the window, which would also look a lot better IMO, or have a fixed width

publish to copr

would it be possible to have a fedora copr packadge that could potentially become get into the main repos?

Additional message option

Sometimes it's nice to be able to display some additional information to the user, e.g. what the operation will do, similar to rofi's -mesg option.

Do you think it would be possible to add such option and do you think it would add value as a feature?

Percentage values incorrect when fractional scaling is applied

There's no way (that I'm aware of) for an application to be aware of a fractional scale factor directly - all of the Wayland protocols (such as wl_output_scale) use integers for the scale factor. Sway achieves fractional scaling (I think) by passing the next largest integer to the application, then scaling it down.

As a workaround for 100% width/height windows, you can pass --width 0 / --height 0 to tofi. This will leave the decision of how big to make the window up to Sway, and so as long as you're anchored in the center of the screen in the relevant direction, it'll do the right thing.

Unfortunately, I don't think there's any way to make it so that percentages work with fractional scaling though.

Improved or customisable fuzzy match heuristics

The original algorithm fuzzy matching is based on has a scoring system designed for searching through code, which isn't tofi's main use case:

tofi/src/fuzzy_match.c

Lines 170 to 176 in 3406922

const int adjacency_bonus = 15;
const int separator_bonus = 30;
const int camel_bonus = 30;
const int first_letter_bonus = 15;
const int leading_letter_penalty = -5;
const int max_leading_letter_penalty = -15;

These scores can occasionally lead to very unintuitive results:

printf "think\nTxHxIxNxK" | tofi --fuzzy-match true
# Type "think", hit enter
TxHxIxNxK

A better default may be to increase the adjacency bonus somewhat, or otherwise tweak the scores.

Document lists incorrect flag to change font value

The document and application's help context mentions the option to define font as font-name, which should be changed to font.

 tofi-drun -h
Usage: tofi [options]

  -h, --help                           Print this message and exit.
  -c, --config                         Specify a config file.
      --font <name|path>               Font to use.
tofi-drun --font-name=sans
[ERROR]: Unknown option --font-name=sans.

config: allow to include files

I'm using pywal and friends to manage my colorscheme, and it seems that we cannot use multiple config files (or include one into another) with tofi. I would like to be able to do so to separate the color config from the actual config.

Thanks for this great piece of software btw!

feature request: shift-tab

Please add a mapping shift-tab to go back one entry;
or stop shift from resetting the current selection to the first entry.

It is very common in applications to be able to select the next entry with tab and the previous with shift-tab. since tab works in tofi, one would assume that shift-tab does too, but instead the selections resets and then you cannot simply use the back arrow anymore which is annoying.

Great tool, kr

No cursor appearing

not completely sure if the issue name is correct, but i'd basically like to ask the user for input, and when using the following command there is no indication for the user that he can start typing (i've noticed that in drun and run the same thing happens, and i'm not sure whether this is intended and if so what's the reasoning behind it)

printf ""|tofi --require-match false --prompt-text "Enter your query: "

tofi-drun: launch application on selected workspace

So I ditched j4-dmenu-desktop in favor of tofi's built in drun, which has been great and blazingly fast. The only thing I miss is the ability (or characteristic, rather?) is that I could set sway exec to launch the application selected by j4-dmenu-desktop, and it would launch on the workspace selected (for example, I could launch GIMP on workspace 4 and, even though it has a slow startup, I could change back to workspace 1 and the application would still open on workspace 4).

The command I had for launching applications this way was: set $menu 'j4-dmenu-desktop --no-generic --use-xdg-de --dmenu="tofi --hint-font false --config ~/.config/tofi/config.ini && sway exec {cmd}"'.

I am aware of the examples in the README of tofi, though neither set $drun tofi-drun | xargs swaymsg exec gio launch or set $drun tofi-drun --drun-launch=true works the same way as I had it set before. Maybe I am being oblivious of something, but is there any way to achieve the behaviour I've described before?

drun option

Is there a way to run it as rofi -show drun to see all installed applications (all .desktop files)?

Proper scrolling

Right now when you scroll to the bottom of tofi it doesn't give you the next entry... it goes back to the top, regardless of how many entries there should be.

Case insensitivity for non-latin

TL;DR

Currently tofi is only case insensitive for latin.

Would be really nice to have support for more locales (in my case - cyrillic).

...How it should map (but doesn't)

абвгдеёжзийклмнопрстуфхцчшщъыьэюя to АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ

Of course, unicode-aware is better than just another alphabet.

Use case

This is useful if e.g. tofi is used as a front end for clipboard manager.

What I did

I've tried to implement it myself.

Found utf8.h, which provides a bunch of utf8 aware functions as replacements for the ones from string.h.

Digging around tofi's source I've found that the main search stuff happens in src/fuzzy_match.c with two functions - fuzzy_match_simple_words() (space split and strcasestr()) and fuzzy_match_words() (space split and fuzzy search algo).

There are also uses of strcasestr() related to highlight of the found pattern, but that's beside the point.

Replacing strcasestr() with utf8casestr() from the utf8.h in fuzzy_match_simple_words() easily makes tofi case insensetive for cyrillic (and not only that), at least without --fuzzy-match true.

But with fuzzy_match_words() it is trickier.

Even if I replace string.h related functions in fuzzy_match_words() and things related to it (I can replace everything aside isalnum()), fuzzy search starts to behave weirdly.

It works fine on latin strings, still.

But if I enter а single cyrillic letter, it instantly shows no results.

I've narrowed it to

while ((match = strcasestr(match, search)) != NULL) {

Basically, if you replace strcasestr() with utf8casestr() it never matches non-latin, while loop never starts.

Idk why.

I suspect this line

const char search[2] = { *pattern, '\0' };

which is the only significantly different argument compared to the ones used for strcasestr() in fuzzy_match_simple_words().

This is where I am at.

Any insight?

Patch

Just so you know, I've tried replacing everything string.h everywhere with utf8.h alternative. It compiles, it seems to run fine for a brief test, but it doesn't make a difference to the problem with fuzzy search function, so I didn't include it.

Patch
diff --git a/src/entry_backend/harfbuzz.c b/src/entry_backend/harfbuzz.c
index c5fc07e..77869df 100644
--- a/src/entry_backend/harfbuzz.c
+++ b/src/entry_backend/harfbuzz.c
@@ -7,6 +7,8 @@
 #include "../nelem.h"
 #include "../xmalloc.h"

+#include "../utf8.h"
+
 /*
  * FreeType is normally compiled without error strings, so we have to do this
  * funky macro trick to get them. See <freetype/fterrors.h> for more details.
@@ -368,7 +370,7 @@ void entry_backend_harfbuzz_update(struct entry *entry)
 			char *postmatch = NULL;
 			cairo_text_extents_t subextents;
 			if (entry->input_mb_length > 0 && entry->selection_highlight_color.a != 0) {
-				char *match_pos = strcasestr(prematch, entry->input_mb);
+				char *match_pos = utf8casestr(prematch, entry->input_mb);
 				if (match_pos != NULL) {
 					match = xstrdup(result);
 					prematch_len = (match_pos - prematch);
diff --git a/src/entry_backend/pango.c b/src/entry_backend/pango.c
index 1f19bce..2439166 100644
--- a/src/entry_backend/pango.c
+++ b/src/entry_backend/pango.c
@@ -6,6 +6,8 @@
 #include "../nelem.h"
 #include "../xmalloc.h"

+#include "../utf8.h"
+
 #undef MAX
 #define MAX(a, b) ((a) > (b) ? (a) : (b))

@@ -181,7 +183,7 @@ void entry_backend_pango_update(struct entry *entry)
 			PangoRectangle ink_subrect;
 			PangoRectangle logical_subrect;
 			if (entry->input_mb_length > 0 && entry->selection_highlight_color.a != 0) {
-				char *match_pos = strcasestr(str, entry->input_mb);
+				char *match_pos = utf8casestr(str, entry->input_mb);
 				if (match_pos != NULL) {
 					prematch_len = (match_pos - str);
 					postmatch_len = strlen(str) - prematch_len - match_len;
diff --git a/src/fuzzy_match.c b/src/fuzzy_match.c
index 94c60e8..47aff1a 100644
--- a/src/fuzzy_match.c
+++ b/src/fuzzy_match.c
@@ -6,6 +6,9 @@

 #include "fuzzy_match.h"
 #include "xmalloc.h"
+#include "log.h"
+
+#include "utf8.h"

 #undef MAX
 #define MAX(a, b) ((a) > (b) ? (a) : (b))
@@ -33,7 +36,7 @@ int32_t fuzzy_match_simple_words(const char *restrict patterns, const char *rest
 	char *tmp = xstrdup(patterns);
 	char *pattern = strtok_r(tmp, " ", &saveptr);
 	while (pattern != NULL) {
-		char *c = strcasestr(str, pattern);
+		char *c = utf8casestr(str, pattern);
 		if (c == NULL) {
 			score = INT32_MIN;
 			break;
@@ -78,8 +81,8 @@ int32_t fuzzy_match_words(const char *restrict patterns, const char *restrict st
 int32_t fuzzy_match(const char *restrict pattern, const char *restrict str)
 {
 	const int unmatched_letter_penalty = -1;
-	const size_t slen = strlen(str);
-	const size_t plen = strlen(pattern);
+	const size_t slen = utf8len(str);
+	const size_t plen = utf8len(pattern);
 	int32_t score = 0;

 	if (*pattern == '\0') {
@@ -119,15 +122,22 @@ int32_t fuzzy_match_recurse(
 	}

 	const char *match = str;
+
+	// XXX: could be the culprit?
 	const char search[2] = { *pattern, '\0' };

 	int32_t best_score = INT32_MIN;
+    log_debug(">> Fuzzy match recurse 1!\n");

 	/*
 	 * Find all occurrences of the next pattern character in str, and
 	 * recurse on them.
 	 */
-	while ((match = strcasestr(match, search)) != NULL) {
+	// FIXME utf8casestr doesn't work here
+	while ((match = utf8casestr(match, search)) != NULL) {
+		// we never get there with utf8casestr
+		log_debug(">> Fuzzy match recurse 2!\n");
+
 		int32_t subscore = fuzzy_match_recurse(
 				pattern + 1,
 				match + 1,
@@ -174,23 +184,30 @@ int32_t compute_score(int32_t jump, bool first_char, const char *restrict match)

 	/* Apply bonuses. */
 	if (!first_char && jump == 0) {
+		log_debug(">> Compute score 1!\n");
 		score += adjacency_bonus;
 	}
 	if (!first_char || jump > 0) {
-		if (isupper(*match) && islower(*(match - 1))) {
+		log_debug(">> Compute score 2!\n");
+		if (utf8isupper(*match) && utf8islower(*(match - 1))) {
+			log_debug(">> Compute score 3!\n");
 			score += camel_bonus;
 		}
+		// XXX this is ascii only, but we never actually get there, wtf
 		if (isalnum(*match) && !isalnum(*(match - 1))) {
+			log_debug(">> Compute score 4!\n");
 			score += separator_bonus;
 		}
 	}
 	if (first_char && jump == 0) {
+		log_debug(">> Compute score 5!\n");
 		/* Match at start of string gets separator bonus. */
 		score += first_letter_bonus;
 	}

 	/* Apply penalties. */
 	if (first_char) {
+		log_debug(">> Compute score 6!\n");
 		score += MAX(leading_letter_penalty * jump,
 				max_leading_letter_penalty);
 	}

Assumes that utf8.h inside src/.

power menu possible?

Is it possible to use a power menu with tofi? like could I use rofi-power-menu with it? or even script my own?

Release 0.5.0 fails to build on Alpine Linux Edge – undefined reference to `fuzzy_match`

The Meson build system
Version: 0.63.2
Source dir: tofi-0.5.0
Build dir: tofi-0.5.0/output
Build type: native build
Project name: tofi
Project version: 0.5.0
C compiler for the host machine: gcc (gcc 12.1.1 "gcc (Alpine 12.1.1_git20220630) 12.1.1 20220630")
C linker for the host machine: gcc ld.bfd 2.39
Host machine cpu family: x86_64
Host machine cpu: x86_64
Library rt found: YES
Library m found: YES
Found pkg-config: /usr/bin/pkg-config (1.9.3)
Run-time dependency freetype2 found: YES 24.3.18
Run-time dependency harfbuzz found: YES 5.1.0
Run-time dependency cairo found: YES 1.17.6
Run-time dependency pangocairo found: YES 1.50.9
Run-time dependency wayland-client found: YES 1.21.0
Found pkg-config: /usr/bin/pkg-config (1.9.3)
Build-time dependency wayland-protocols found: YES 1.26
Build-time dependency wayland-scanner found: YES 1.21.0
Run-time dependency xkbcommon found: YES 1.4.1
Run-time dependency glib-2.0 found: YES 2.72.3
Run-time dependency gio-unix-2.0 found: YES 2.72.3
Program /usr/bin/wayland-scanner found: YES (/usr/bin/wayland-scanner)
Program scdoc found: YES (/usr/bin/scdoc)
Program sed found: YES (/bin/sed)
Program sh found: YES (/bin/sh)
Build targets in project: 6

tofi 0.5.0

  User defined options
    auto_features : auto
    bindir        : /usr/bin
    buildtype     : plain
    datadir       : /usr/share
    includedir    : /usr/include
    infodir       : /usr/share/info
    libdir        : /usr/lib
    libexecdir    : /usr/libexec
    localedir     : /usr/share/locale
    localstatedir : /var
    mandir        : /usr/share/man
    prefix        : /usr
    sbindir       : /usr/sbin
    sharedstatedir: /var/lib
    sysconfdir    : /etc
    wrap_mode     : nodownload
    b_lto         : false
    b_pie         : true
    b_staticpic   : true

Found ninja-1.9 at /usr/bin/ninja
ninja: entering directory 'tofi-0.5.0/output'
[1/31] Generating tofi.5 with a custom command
[2/31] Generating tofi.1 with a custom command
[3/31] Compiling C object tofi-compgen.p/src_xmalloc.c.o
[4/31] Compiling C object tofi-compgen.p/src_string_vec.c.o
[5/31] Compiling C object tofi-compgen.p/src_mkdirp.c.o
[6/31] Compiling C object tofi-compgen.p/src_log.c.o
[7/31] Compiling C object tofi-compgen.p/src_compgen.c.o
[8/31] Compiling C object tofi-compgen.p/src_main_compgen.c.o
[9/31] Generating __usr_share_wayland_protocols_stable_xdg_shell_xdg_shell_xml_client_header with a custom command
[10/31] Generating __usr_share_wayland_protocols_stable_xdg_shell_xdg_shell_xml_private_code with a custom command
[11/31] Compiling C object tofi.p/src_xmalloc.c.o
[12/31] Compiling C object tofi.p/src_wlr-layer-shell-unstable-v1.c.o
[13/31] Compiling C object tofi.p/src_surface.c.o
[14/31] Compiling C object tofi.p/src_string_vec.c.o
[15/31] Compiling C object tofi.p/src_shm.c.o
[16/31] Compiling C object tofi.p/src_mkdirp.c.o
[17/31] Compiling C object tofi.p/src_log.c.o
[18/31] Compiling C object tofi.p/src_history.c.o
[19/31] Compiling C object tofi.p/src_fuzzy_match.c.o
[20/31] Compiling C object tofi.p/src_entry_backend_harfbuzz.c.o
../src/shm.c:19:12: warning: 'create_shm_file' defined but not used [-Wunused-function]
   19 | static int create_shm_file(void)
      |            ^~~~~~~~~~~~~~~
[21/31] Compiling C object tofi.p/src_entry_backend_pango.c.o
[22/31] Compiling C object tofi.p/src_entry.c.o
[23/31] Compiling C object tofi.p/src_drun.c.o
[24/31] Linking target tofi-compgen
[25/31] Compiling C object tofi.p/src_desktop_vec.c.o
[26/31] Compiling C object tofi.p/src_config.c.o
ninja: job failed: gcc  -o tofi-compgen tofi-compgen.p/src_main_compgen.c.o tofi-compgen.p/src_compgen.c.o tofi-compgen.p/src_log.c.o tofi-compgen.p/src_mkdirp.c.o tofi-compgen.p/src_string_vec.c.o tofi-compgen.p/src_xmalloc.c.o -Wl,--as-needed -Wl,--no-undefined -pie -Wl,--as-needed,-O1,--sort-common -Wl,--start-group -lfts -Wl,--end-group -Os -fomit-frame-pointer -Os -fomit-frame-pointer
/usr/lib/gcc/x86_64-alpine-linux-musl/12.1.1/../../../../x86_64-alpine-linux-musl/bin/ld: tofi-compgen.p/src_string_vec.c.o: in function `string_vec_filter':
string_vec.c:(.text+0x2a3): undefined reference to `fuzzy_match'
collect2: error: ld returned 1 exit status
ninja: subcommand failed

Fuzzy matching

First off, thanks for making tofi, it's great! As a feature request, it would be nice to have support for fuzzy string matching like how fzf or the VS Code command prompt works. For instance, the string mm would match the word Mattermost, and vc would match Visual Studio Code.

I found a couple of resources explaining the algorithm and implementation for fzf and for VS Code.

Prompt padding

I may be glancing over this in -h, but something like a --prompt-padding would be handy.

$ echo foo | tofi --prompt bar

Type anything and there'll be no space after "bar". This means that every use of tofi requires that the prompt is given a trailing space.

Display and input can get out of sync

Currently, it's sometimes possible for the display to get out of sync with tofi's internal state, causing input to lag by one keypress. This is only really noticeable when e.g. performing fuzzy matches takes a very long time, but it should never happen.

Allow arbitrary selection

It's possible to reuse dmenu for general searching not against results like so:

$ echo "" | dmenu # enter "foo<Enter>"
foo

tofi doesn't appear to support this because it requires a match. I think that's a sane default, but could an option be added to accept the entered text regardless of whether it has a match?

My use case is that I use dmenu/tofi for opening bookmarks, apps, and so on, but also for launching web searches from anywhere, in which case it's really acting more like a plain text input.

Release v0.1.0 is broken and missing in master

meson.build:5:2: ERROR: Expecting rparen got id.
  license: 'MIT',
  ^
For a block that started at 1,7
project(
       ^

And the 1da9e9c is missing in the master branch – this is really not a good approach for tagging releases.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.