Compare commits

...

96 Commits
4.6 ... master

Author SHA1 Message Date
Adam Purkrt
b1e217b29a XUngrabKeyboard() instead of XUngrabKey()
XUngrabKey(), which is currently used in cleanup(), is not the right
counterpart to XGrabKeyboard(), which is used in grabkeyboard(),
called from main().

XUngrabKeyboard() is the function to use, as grabbing the whole
keyboard is different to grabbing individual keys.

With the current code the keyboard gets ungrabbed, as far as I can tell,
only by the final XCloseDisplay() in cleanup(), as the XUngrabKey()
there effectively does nothing.
2025-03-17 21:01:05 +01:00
Hiltjo Posthuma
86f0b5119e remove extra newline 2024-10-30 13:03:25 +01:00
Hiltjo Posthuma
dd3d348ae8 Avoid unsigned integer underflow in drw_text()
Patch by Raymond Cole <rc@wolog.xyz>, thanks.
2024-10-30 13:02:57 +01:00
Hiltjo Posthuma
545031a076 util.c: output function might override errno and thus affect perror()
Original patch by Raymond Cole with some modifications, thanks!
2024-10-27 20:08:46 +01:00
Hiltjo Posthuma
475d8093cb drw.c: use the same pattern as ellipsis_width to check for infinite recursion 2024-07-14 11:43:01 +02:00
NRK
59936c7d97 render invalid utf8 sequences as U+FFFD
previously drw_text would do the width calculations as if
invalid utf8 sequences were replaced with U+FFFD but would pass
the invalid utf8 sequence to xft to render where xft would just
cut it off at the first invalid byte.

this change makes invalid utf8 render as U+FFFD and avoids
sending invalid sequences to xft. the following can be used to
check the behavior before and after the patch:

	$ printf "0\xef1234567\ntest" | dmenu

Ref: https://lists.suckless.org/dev/2407/35646.html
2024-07-14 11:42:58 +02:00
NRK
51e32d49b5 overhaul utf8decode()
this changes the utf8decode function to:

* report when an error occurs
* report how many bytes to advance on error

these will be useful in the next commit to render invalid utf8
sequences.

the new implementation is also shorter and more direct.
2024-07-14 11:42:55 +02:00
Hiltjo Posthuma
7be720cc88 bump version to 5.3 2024-03-19 12:12:52 +01:00
Hiltjo Posthuma
8df553e004 Makefile: remove the options target
The Makefile used to suppress output (by using @), so this target made sense at
the time.

But the Makefile should be simple and make debugging with less abstractions or
fancy printing.  The Makefile was made verbose and doesn't hide the build
output, so remove this target.

Prompted by a question on the mailing list about the options target.
2023-09-22 15:16:44 +02:00
NRK
7ab0cb5ef0 drw: minor improvement to the nomatches cache
1. use `unsigned int` to store the codepoints, this avoids waste on
   common case where `long` is 64bits. and POSIX guarantees `int` to be
   at least 32bits so there's no risk of truncation.
2. since switching to `unsigned int` cuts down the memory requirement by
   half, double the cache size from 64 to 128.
3. instead of a linear search, use a simple hash-table for O(1) lookups.
2023-07-07 15:03:57 +02:00
Lucas de Sena
0fe460dbd4 fix BadMatch error when embedding on some windows
When embedded into another window, dmenu will fail with the BadMatch
error if that window have not the same colormap/depth/visual as the
root window.

That happens because dmenu inherits the colormap/depth/visual from
its parent, but draws on a pixmap created based on the root window
using a GC created for the root window (see drw.c).  A BadMatch will
occur when copying the content of the pixmap into dmenu's window.

A solution is to create dmenu's window inside root and then reparent
it if embeded.

See this mail[1] on ports@openbsd.org mailing list for context.

[1]: https://marc.info/?l=openbsd-ports&m=168072150814664&w=2
2023-04-06 20:28:56 +02:00
Hiltjo Posthuma
dfbbf7f6e1 readstdin: reduce memory-usage by duplicating the line from getline()
Improves upon commit 32db2b125190d366be472ccb7cad833248696144

The getline() implementation often uses a more greedy way of allocating memory.
Using this buffer directly and forcing an allocation (by setting it to NULL)
would waste a bit of extra space, depending on the implementation of course.

Tested on musl libc and glibc.
The current glibc version allocates a minimum of 120 bytes per line.
For smaller lines musl libc seems less wasteful but still wastes a few bytes
per line.

On a dmenu_path listing on my system the memory usage was about 350kb (old) vs
30kb (new) on Void Linux glibc.

Side-note that getline() also reads NUL bytes in lines, while strdup() would
read until the NUL byte. Since dmenu reads text lines either is probably
fine(tm). Also rename junk to linesiz.
2023-03-08 21:28:51 +01:00
Hiltjo Posthuma
ba1a347dca readstdin: allocate amount of items
Keep track of the amount of items (not a total buffer size), allocate an array of
new items. For now change BUFSIZ bytes to 256 * sizeof(struct item)).
2022-10-31 11:52:30 +01:00
Hiltjo Posthuma
bcbc1ef5c4 readstdin: add a comment
Maybe too obvious / redundant, but OK.
2022-10-31 11:46:10 +01:00
NRK
689d9bfcf6 fix leak when getline fails
according to the getline(3) documentation, the calling code needs to
free the buffer even if getline fails.

dmenu currently doesn't do that which results in a small leak in case of
failure (e.g when piped /dev/null)

	$ ./dmenu < /dev/null
	==8201==ERROR: LeakSanitizer: detected memory leaks
	Direct leak of 120 byte(s) in 1 object(s) allocated from:
	    #0 0x7f6bf5785ef7 in malloc
	    #1 0x7f6bf538ec84 in __getdelim
	    #2 0x405d0c in readstdin dmenu.c:557

moving `line = NULL` inside the loop body wasn't strictly necessary, but
IMO it makes it more apparent that `line` is getting cleared to NULL
after each successful iteration.
2022-10-31 11:40:35 +01:00
Hiltjo Posthuma
e42c036634 dmenu: small XmbLookupString code improvements
* Increase the length of composed strings to the same limit as st (32 to 64 bytes).
* Initialize ksym to NoSymbol to be safe: currently this is not an issue though.
* Add comments to clarify the return values of XmbLookupString a bit.
2022-10-26 09:43:17 +02:00
Hiltjo Posthuma
1d2b462acf bump version to 5.2 2022-10-04 19:36:02 +02:00
Tom Schwindl
7ec32fe494 dmenu: use die() to print the usage message 2022-10-01 13:20:40 +02:00
Hiltjo Posthuma
fce06f437d remove workaround for a crash with color emojis on some systems, now fixed in libXft 2.3.5
https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS
2022-09-17 15:32:26 +02:00
Hiltjo Posthuma
1e8c5b68f4 fix a regression in the previous commit for tab complete
Reported by Santtu Lakkala <inz@inz.fi>, thanks!
2022-09-02 19:09:50 +02:00
NRK
528d39b011 tab-complete: figure out the size before copying
we already need to know the string length since `cursor` needs to be
adjusted.

so just calculate the length beforehand and use `memcpy` to copy exactly
as much as needed (as opposed to `strncpy` which always writes `n`
bytes).
2022-09-02 13:00:48 +02:00
NRK
32db2b1251 readstdin: use getline(3)
currently readstdin():
   - fgets() into a local buffer,
   - strchr() the buffer to eleminate the newline
   - stdups() the buffer into items

a simpler way is to just use getline(3), which will do the allocation
for us; eliminating the need for stdup()-ing.

additionally getline returns back the amount of bytes read, which
eliminates the need for strchr()-ing to find the newline.
2022-09-02 12:53:34 +02:00
Hiltjo Posthuma
e35976f4a5 sync code-style patch from libsl 2022-08-08 10:42:54 +02:00
Hiltjo Posthuma
28fb3e2812 Makefile: add manual path for OpenBSD 2022-05-01 18:38:25 +02:00
Hiltjo Posthuma
fe5d5c6709 fix incorrect comment, math is hard 2022-04-30 13:19:33 +02:00
Hiltjo Posthuma
e1e1de7b3b inputw: improve correctness and startup performance, by NRK
Always use ~30% of the monitor width for the input in horizontal mode.

Patch adapted from NRK patches.
This also does not calculate inputw when using vertical mode anymore (because
the code is removed).
2022-04-29 20:18:02 +02:00
NRK
33685b06e9 drw_text: account for fallback fonts in ellipsis_width
additionally, ellipsis_width (which shouldn't change) is made static to
avoid re-calculating it on each drw_text() call.
2022-04-16 16:21:01 +02:00
NRK
e4827b0c40 drw_text: don't segfault when called with 0 width
this patch just rejects *any* 0 width draws, which is surely an error by
the caller.

this also guards against cases where the width is too small for the
ellipsis to fit, so ellipsis_w will remain 0.
reported by Bakkeby <bakkeby@gmail.com>
2022-04-16 16:21:01 +02:00
Hiltjo Posthuma
e73651f12a fix UB with the function iscntrl()
From commit 6818e07291f3b2913e687c8ec3d3fe4711724050 by NRK, thanks
2022-03-26 17:58:47 +01:00
Hiltjo Posthuma
31fa07b984 Revert "avoid redraw when there's no change"
This reverts commit 6818e07291f3b2913e687c8ec3d3fe4711724050.

This broke keys such as ^W to delete-backward-word
2022-03-26 17:57:50 +01:00
NRK
6818e07291 avoid redraw when there's no change
while i was timing the performance issue, i noticed that there was lots
of random redrawing going on.

turns out there were coming from here; if someone presses CTRL/ALT etc
without pressing anything else, nothing will be inserted, so nothing
will change. but the code will `break`, go down and do a needless redraw.

this patch changes it to simply return if the keypress iscntrl()

also avoid potential UB by casting *buf into an unsigned char.
2022-03-25 22:53:50 +01:00
NRK
b43ec0577f free all allocated items, use %zu for size_t
`items` itself is not checked for NULL as calling free on NULL is defined to be
a no-op.
2022-03-25 22:53:50 +01:00
NRK
22511c41d5 drw_text: improve performance when there's no match
this was the last piece of the puzzle, the case where we can't find any
font to draw the codepoint.

in such cases, we use XftFontMatch() which is INSANELY slow. but that's
not the real problem. the real problem was we were continuously trying
to match the same thing over and over again.

this patch introduces a small cache, which keeps track a couple
codepoints for which we know we won't find any matches.

with this, i can dump lots of emojies into dmenu where some of them
don't have any matching font, and still not have dmenu lag insanely or
FREEZE completely when scrolling up and down.

this also improves startup time, which will of course depend on the
system and all installed fonts; but on my system and test case i see the
following startup time drop:

before -> after
60ms   -> 34ms
2022-03-25 22:49:07 +01:00
NRK
77526f756e inputw: improve correctness and startup performance
a massive amount of time inside readstdin() is spent trying to get the
max input width and then put it into inputw, only for it to get clamped
down to mw/3 inside setup().

it makes more sense to calculate inputw inside setup() once we have mw
available. similar to the last patch, i see noticeable startup
performance improvement:

before -> after
160ms  -> 60ms

additionally this will take fallback fonts into account compared to the
previous version, so it's not only more performant but also more correct.
2022-03-25 22:49:07 +01:00
NRK
7269c5355d significantly improve performance on large strings
this replaces inefficient pattern of `MIN(TEXTW(..), n)` with
drw_fontset_getwidth_clamp() instead, which is far more efficient when
we only want up to a certain width.

dumping a decently sized (unicode) emoji file into dmenu, I see the
startup time drop significantly with this patch.

before -> after
360ms  -> 160ms

this should also noticeably improve input latency (responsiveness) given
that calcoffsets() and drawmenu() are pretty hot functions.
2022-03-25 22:49:07 +01:00
NRK
6be057f060 introduce drw_fontset_getwidth_clamp()
getting the width of a string is an O(n) operation, and in many cases
users only care about getting the width upto a certain number.

instead of calling drw_fontset_getwidth() and *then* clamping the
result, this patch introduces drw_fontset_getwidth_clamp() function,
similar to strnlen(), which will stop once we reach n.

the `invert` parameter was overloaded internally to preserve the API,
however library users should be calling drw_fontset_getwidth_clamp() and
not depend upon internal behavior of drw_text().
2022-03-25 22:49:07 +01:00
NRK
41fdabbf7c drw_text: improve both performance and correctness
this patch makes some non-trivial changes, which significantly improves
the performance of drawing large strings as well as fixes any issues
regarding the printing of the ellipsis when string gets truncated.

* performance:

before there were two O(n) loops, one which finds how long we can go
without changing font, and the second loop would (incorrectly) truncate
the string if it's too big.

this patch merges the overflow calculation into the first loop and exits
out when overflow is detected. when dumping lots of emojies into dmenu,
i see some noticeable startup time improvement:

before -> after
460ms  -> 360ms

input latency when scrolling up/down is also noticeably better and can
be tested with the following:

	for _ in $(seq 20); do
		cat /dev/urandom | base64 | tr -d '\n' | head -c 1000000
	echo
	done | ./dmenu -l 10

* correctness:

the previous version would incorrectly assumed single byte chars and
would overwrite them with '.' , this caused a whole bunch of obvious
problems, including the ellipsis not getting rendered if then font
changed.

in addition to exiting out when we detect overflow, this patch also
keeps track of the last x-position where the ellipsis would fit. if we
detect overflow, we simply make a recursing call to drw_text() at the
ellipsis_x position and overwrite what was there.

so now the ellipsis will always be printed properly, regardless of
weather the font changes or if the string is single byte char or not.

the idea of rendering the ellipsis on top incase of overflow was
from Bakkeby <bakkeby@gmail.com>, thanks! however the original patch had
some issues incorrectly truncating the prompt (-p flag) and cutting off
emojies. those have been fixed in here.
2022-03-25 22:49:07 +01:00
Hiltjo Posthuma
3a505cebe8 remove false-positive warning for int comparison as bool
Reported by Prathu Baronia <prathu.baronia@praton.me>, patch slightly changed.

Thanks!
2022-03-01 22:45:39 +01:00
Hiltjo Posthuma
308fe78b83 bump version to 5.1 2022-02-11 12:26:35 +01:00
Hiltjo Posthuma
c4b656e0da code-style: rm newline (oops) 2022-02-08 21:45:28 +01:00
Hiltjo Posthuma
3e39c526d2 revert using strcasestr and use a more optimized portable version
... compared to the old cistrstr().

Thanks for the feedback!
2022-02-08 19:38:23 +01:00
Hiltjo Posthuma
a9a3836861 follow-up fix: add -D_GNU_SOURCE for strcasestr for some systems 2022-02-07 10:36:13 +01:00
Hiltjo Posthuma
eb96af27f4 improve performance of case-insensitive matching 2022-02-07 00:21:12 +01:00
Hiltjo Posthuma
d78ff08d99 Revert "Improve speed of drw_text when provided with large strings"
This reverts commit c585e8e498ec6f9c423ab8ea07cf853ee5b05fbe.

It causes issues with truncation of characters when the text does not fit and
so on.  The patch should be reworked and properly tested.
2021-08-20 23:05:53 +02:00
Hiltjo Posthuma
cd2133a5f6 add support for more keypad keys
The keypad Enter key was already supported. On some keyboard layouts like my
laptop the page-up and page-down key is more comfortable to use.
This adds a few lines but no complexity.
2021-08-09 18:39:25 +02:00
Miles Alan
c585e8e498 Improve speed of drw_text when provided with large strings
Calculates len & ew in drw_font_getexts loop by incrementing instead of
decrementing; as such avoids proportional increase in time spent in loop
based on provided strings size.
2021-08-09 18:20:51 +02:00
Guilherme Janczak
523aa08f51 remove always true condition in if statement 2021-07-25 10:55:45 +02:00
Hiltjo Posthuma
1a13d0465d bump version to 5.0
... and bump LICENSE year.
2020-09-02 18:31:23 +02:00
Hiltjo Posthuma
9b38fda6fe Fix memory leaks in drw
Synced from dwm.
Patch by Alex Flierl <shad0w73@freenet.de>, thanks.
2020-06-11 18:45:33 +02:00
Hiltjo Posthuma
db6093f6ec revert IME support
dmenu will not handle IME support (st will, atleast for now).

revert parts of commit 377bd37e212b1ec4c03a481245603c6560d0be22
this commit also broke input focus.
2019-03-03 13:08:54 +01:00
Hiltjo Posthuma
a9b1de384a improve xopenim error message
die() already prints a newline.
2019-02-12 22:58:35 +01:00
Hiltjo Posthuma
43b0c2c3dd make dmenu_path script executable
(as dmenu_run is)
2019-02-12 22:13:58 +01:00
Hiltjo Posthuma
f5036b90ef fix crash when XOpenIM returns NULL
for example when IME variables are set, but the program is not started (yet).
2019-02-12 19:10:43 +01:00
Quentin Rameau
153aaf88bf Close when the embedding window is destroyed 2019-02-04 19:49:34 +01:00
Anselm R Garbe
65be875f5a Prepared 4.9 release. 2019-02-02 04:54:15 -08:00
Hiltjo Posthuma
7d19b2055d dmenu.1: document improved fastgrab behaviour from previous patch 2019-01-27 15:28:02 +01:00
dok
11a65377da Use slow path if stdin is a tty
If stdin is a tty and dmenu is ran with the fast option then it's
impossible to close stdin because the keyboard is already grabbed.
2019-01-27 15:26:04 +01:00
Quentin Rameau
bbc464dc80 dmenu_path: always use the cachedir 2018-07-21 12:49:00 +02:00
Hiltjo Posthuma
a314412f4b Makefile: just show the compiler output
Don't be fancy and just show the actual output so debugging is simpler.
2018-06-02 17:09:01 +02:00
Hiltjo Posthuma
a9eae39e93 Do not strip at link stage
Building with debug symbols is worthless unless LDFLAGS are manually adjusted
as well.
2018-06-02 17:01:24 +02:00
Hiltjo Posthuma
851b73d178 code-style for pledge: check the return code -1, not < 0
this is the proper idiom
2018-05-25 13:07:17 +02:00
Hiltjo Posthuma
05c138f5b8 code-style for pledge(2)
feedback from Klemens, thanks
2018-05-25 13:03:25 +02:00
Hiltjo Posthuma
cd132c8d5b Pledge on OpenBSD 2018-05-25 12:04:22 +02:00
David Demelier
e75494b730 Use bold for keyboard shortcuts in dmenu.1
Like dwm, use the same syntax for all keyboard shortcuts for
consistency.
2018-05-12 19:12:25 +02:00
Quentin Rameau
0f76dd2fb8 Fix cursor drawn position with wide glyphs 2018-04-22 14:19:20 +02:00
Quentin Rameau
0b57480218 Makefile: bikesheddingly replace ${} with $() 2018-04-22 14:09:05 +02:00
Quentin Rameau
377bd37e21 Handle IME input
Thanks to nzl <uruabi@gmail.com> for the patch!
2018-04-22 14:09:05 +02:00
Hiltjo Posthuma
b6d2cc9aea Fix handling of input strings 2018-04-22 14:09:05 +02:00
Quentin Rameau
2f398981fe Update LICENSE
Only "meaningful" commits and contributors who made changes over the
years have been added.
2018-03-15 18:29:32 +01:00
Hiltjo Posthuma
23051d78dd bump version to 4.8 2018-03-14 19:48:05 +01:00
Quentin Rameau
e2a280541e add key bindings for moving to the word start or end
Mod1+b/^Left and Mod1+f/^Right
2018-03-13 20:10:46 +01:00
Hiltjo Posthuma
889512811d Fix regression in 84a1bc5
Reported by Jochen Sprickerhof, thanks!

Applied patch with minor change (only initialize `i` for XINERAMA).
2018-01-04 23:45:49 +01:00
Vincent Carluer
84a1bc5d0d Instantiate j var outside #ifdef XINEMARA directive because it is used in loop outside directive 2018-01-04 18:14:41 +01:00
Hiltjo Posthuma
f0a5b75d6a drw: drw_scm_create: use Clr type
in this context XftColor is a too low-level type.
2017-11-03 21:10:38 +01:00
Hiltjo Posthuma
1cabeda550 fix a possible free of a uninitialize variable in paste() 2017-11-03 21:07:02 +01:00
Hiltjo Posthuma
41379f7c39 init colors using SchemeLast
this makes it slightly easier to add colors to schemes.
2017-11-03 21:05:29 +01:00
Omar Sandoval
64ab2801fb Set class name on menu window
WM_CLASS is a standard ICCCM property which is used to identify windows.
Window managers and compositors use it to allow per-application
configurable behavior.
2017-11-03 20:41:03 +01:00
Hiltjo Posthuma
f428f3e01a release 4.7 2017-05-02 18:32:04 +02:00
Andrew Gregory
5cd66e2c6c Revert "fix input text matching"
This reverts commit 09d0a36e0370f7ca9bdb171bf93c5ac3131c5a92.

Using strncmp with the length of the user input turns it into a prefix
match rather than an exact match as it's supposed to be.
2016-12-11 12:33:16 +01:00
Thomas Gardner
e90b88e12a Xinerama: correct variable declarations in preprocessor conditional 2016-11-25 14:21:40 +01:00
Hiltjo Posthuma
a280bdad1f die() on calloc failure
thanks Markus Teich and David!
2016-11-05 11:36:42 +01:00
Quentin Rameau
a9a5c6cc2d add embedding support with -w option 2016-10-14 15:26:35 +02:00
Quentin Rameau
a97f550aa7 dmenu.1: group single options 2016-10-14 15:26:34 +02:00
Quentin Rameau
ff8daf8847 dmenu.1: fix -l option 2016-10-14 15:26:34 +02:00
Hiltjo Posthuma
026827fd65 die() consistency: always add newline 2016-08-12 14:39:30 +02:00
Hiltjo Posthuma
d14670b995 fix crash if negative monitor (< -1) was passed
for example: dmenu -m '-9001'
2016-07-26 23:13:06 +02:00
Hiltjo Posthuma
aa92cd68bd Revert "Print highlighted input text only on single match"
This reverts commit a4053bc4e403ae57343f43b7e363a0911bba5a3a.

My bad, was working on the wrong branch and accidently pushed it...
*facepalm*
2016-07-26 23:02:34 +02:00
Quentin Rameau
a4053bc4e4 Print highlighted input text only on single match
When the input text fully matches a single item, do not draw the item
and highlight the input text to show that it matches an item in
opposition to regular input text not matching anything.
2016-07-26 18:29:42 +02:00
Quentin Rameau
657122f781 Partially revert 44c7de3: fix items text width offset calculation
Without this, we discard the item if it's longer than assigned width
instead of truncating it.
2016-07-25 19:57:31 +02:00
Hiltjo Posthuma
3c91eed0fb config.def.h: style improvement, use color Scheme enum 2016-06-28 17:56:25 +02:00
S. Gilles
76eb578304 fix: Do not crash on e.g. dmenu < /dev/null 2016-06-28 17:51:28 +02:00
Markus Teich
44c7de3dcf import new drw from libsl and minor fixes.
- extract drawitem function (code deduplication)
- fix bug where inputw was not correctly calculated from the widest item, but
  just from the one with the longest strlen() which is not the same. It's better
  now, but does not account for fallback fonts, since it would be too slow to
  calculate all the correct item widths on startup.
- minor code style fixes (indentation, useless line breaks)
2016-06-03 19:13:15 +02:00
Lucas Gabriel Vuotto
b3d9451c2d arg.h: fixed argv checks order
This prevents accessing to a potentially out-of-bounds memory section.

Signed-off-by: Lucas Gabriel Vuotto <l.vuotto92@gmail.com>
2016-02-23 00:31:03 +01:00
Klemens Nanni
3de85ca21c Typofix 2016-01-14 23:02:27 +01:00
Quentin Rameau
bf3deb6357 Shut up glibc about _BSD_SOURCE being deprecated 2015-12-19 20:32:17 +01:00
Quentin Rameau
120e840107 Add config option for word delimiters
Let the user configure word boundaries other than ' ', only works with
the portable character set.
2015-12-19 20:32:14 +01:00
13 changed files with 640 additions and 479 deletions

12
LICENSE
View File

@ -1,13 +1,15 @@
MIT/X Consortium License MIT/X Consortium License
© 2006-2014 Anselm R Garbe <anselm@garbe.us> © 2006-2019 Anselm R Garbe <anselm@garbe.ca>
© 2010-2012 Connor Lane Smith <cls@lubutu.com> © 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com>
© 2006-2007 Michał Janeczek <janeczek@gmail.com>
© 2007 Kris Maglione <jg@suckless.org>
© 2009 Gottox <gottox@s01.de> © 2009 Gottox <gottox@s01.de>
© 2009 Markus Schnalke <meillo@marmaro.de> © 2009 Markus Schnalke <meillo@marmaro.de>
© 2009 Evan Gates <evan.gates@gmail.com> © 2009 Evan Gates <evan.gates@gmail.com>
© 2006-2008 Sander van Dijk <a dot h dot vandijk at gmail dot com> © 2010-2012 Connor Lane Smith <cls@lubutu.com>
© 2006-2007 Michał Janeczek <janeczek at gmail dot com> © 2014-2022 Hiltjo Posthuma <hiltjo@codemadness.org>
© 2014-2015 Hiltjo Posthuma <hiltjo@codemadness.org> © 2015-2019 Quentin Rameau <quinq@fifth.space>
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),

View File

@ -4,71 +4,55 @@
include config.mk include config.mk
SRC = drw.c dmenu.c stest.c util.c SRC = drw.c dmenu.c stest.c util.c
OBJ = ${SRC:.c=.o} OBJ = $(SRC:.c=.o)
all: options dmenu stest all: dmenu stest
options:
@echo dmenu build options:
@echo "CFLAGS = ${CFLAGS}"
@echo "LDFLAGS = ${LDFLAGS}"
@echo "CC = ${CC}"
.c.o: .c.o:
@echo CC $< $(CC) -c $(CFLAGS) $<
@${CC} -c ${CFLAGS} $<
config.h: config.h:
@echo creating $@ from config.def.h cp config.def.h $@
@cp config.def.h $@
${OBJ}: arg.h config.h config.mk drw.h $(OBJ): arg.h config.h config.mk drw.h
dmenu: dmenu.o drw.o util.o dmenu: dmenu.o drw.o util.o
@echo CC -o $@ $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS)
@${CC} -o $@ dmenu.o drw.o util.o ${LDFLAGS}
stest: stest.o stest: stest.o
@echo CC -o $@ $(CC) -o $@ stest.o $(LDFLAGS)
@${CC} -o $@ stest.o ${LDFLAGS}
clean: clean:
@echo cleaning rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz
@rm -f dmenu stest ${OBJ} dmenu-${VERSION}.tar.gz
dist: clean dist: clean
@echo creating dist tarball mkdir -p dmenu-$(VERSION)
@mkdir -p dmenu-${VERSION} cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\
@cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1 \ drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\
drw.h util.h dmenu_path dmenu_run stest.1 ${SRC} \ dmenu-$(VERSION)
dmenu-${VERSION} tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION)
@tar -cf dmenu-${VERSION}.tar dmenu-${VERSION} gzip dmenu-$(VERSION).tar
@gzip dmenu-${VERSION}.tar rm -rf dmenu-$(VERSION)
@rm -rf dmenu-${VERSION}
install: all install: all
@echo installing executables to ${DESTDIR}${PREFIX}/bin mkdir -p $(DESTDIR)$(PREFIX)/bin
@mkdir -p ${DESTDIR}${PREFIX}/bin cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin
@cp -f dmenu dmenu_path dmenu_run stest ${DESTDIR}${PREFIX}/bin chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu
@chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path
@chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_path chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run
@chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run chmod 755 $(DESTDIR)$(PREFIX)/bin/stest
@chmod 755 ${DESTDIR}${PREFIX}/bin/stest mkdir -p $(DESTDIR)$(MANPREFIX)/man1
@echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1 sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1
@mkdir -p ${DESTDIR}${MANPREFIX}/man1 sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1
@sed "s/VERSION/${VERSION}/g" < dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/dmenu.1 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1
@sed "s/VERSION/${VERSION}/g" < stest.1 > ${DESTDIR}${MANPREFIX}/man1/stest.1 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/dmenu.1
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/stest.1
uninstall: uninstall:
@echo removing executables from ${DESTDIR}${PREFIX}/bin rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\
@rm -f ${DESTDIR}${PREFIX}/bin/dmenu $(DESTDIR)$(PREFIX)/bin/dmenu_path\
@rm -f ${DESTDIR}${PREFIX}/bin/dmenu_path $(DESTDIR)$(PREFIX)/bin/dmenu_run\
@rm -f ${DESTDIR}${PREFIX}/bin/dmenu_run $(DESTDIR)$(PREFIX)/bin/stest\
@rm -f ${DESTDIR}${PREFIX}/bin/stest $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\
@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 $(DESTDIR)$(MANPREFIX)/man1/stest.1
@rm -f ${DESTDIR}${MANPREFIX}/man1/dmenu.1
@rm -f ${DESTDIR}${MANPREFIX}/man1/stest.1
.PHONY: all options clean dist install uninstall .PHONY: all clean dist install uninstall

4
arg.h
View File

@ -10,8 +10,8 @@ extern char *argv0;
/* use main(int argc, char *argv[]) */ /* use main(int argc, char *argv[]) */
#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ #define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
argv[0] && argv[0][1]\ argv[0] && argv[0][0] == '-'\
&& argv[0][0] == '-';\ && argv[0][1];\
argc--, argv++) {\ argc--, argv++) {\
char argc_;\ char argc_;\
char **argv_;\ char **argv_;\

View File

@ -6,12 +6,18 @@ static int topbar = 1; /* -b option; if 0, dmenu appears a
static const char *fonts[] = { static const char *fonts[] = {
"monospace:size=10" "monospace:size=10"
}; };
static const char *prompt = NULL; /* -p option; prompt to the elft of input field */ static const char *prompt = NULL; /* -p option; prompt to the left of input field */
static const char *normbgcolor = "#222222"; /* -nb option; normal background */ static const char *colors[SchemeLast][2] = {
static const char *normfgcolor = "#bbbbbb"; /* -nf option; normal foreground */ /* fg bg */
static const char *selbgcolor = "#005577"; /* -sb option; selected background */ [SchemeNorm] = { "#bbbbbb", "#222222" },
static const char *selfgcolor = "#eeeeee"; /* -sf option; selected foreground */ [SchemeSel] = { "#eeeeee", "#005577" },
static const char *outbgcolor = "#00ffff"; [SchemeOut] = { "#000000", "#00ffff" },
static const char *outfgcolor = "#000000"; };
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ /* -l option; if nonzero, dmenu uses vertical list with given number of lines */
static unsigned int lines = 0; static unsigned int lines = 0;
/*
* Characters not considered part of a word while deleting words
* for example: " /?\"&[]"
*/
static const char worddelimiters[] = " ";

View File

@ -1,9 +1,9 @@
# dmenu version # dmenu version
VERSION = 4.6 VERSION = 5.3
# paths # paths
PREFIX = /usr/local PREFIX = /usr/local
MANPREFIX = ${PREFIX}/share/man MANPREFIX = $(PREFIX)/share/man
X11INC = /usr/X11R6/include X11INC = /usr/X11R6/include
X11LIB = /usr/X11R6/lib X11LIB = /usr/X11R6/lib
@ -16,16 +16,17 @@ XINERAMAFLAGS = -DXINERAMA
FREETYPELIBS = -lfontconfig -lXft FREETYPELIBS = -lfontconfig -lXft
FREETYPEINC = /usr/include/freetype2 FREETYPEINC = /usr/include/freetype2
# OpenBSD (uncomment) # OpenBSD (uncomment)
#FREETYPEINC = ${X11INC}/freetype2 #FREETYPEINC = $(X11INC)/freetype2
#MANPREFIX = ${PREFIX}/man
# includes and libs # includes and libs
INCS = -I${X11INC} -I${FREETYPEINC} INCS = -I$(X11INC) -I$(FREETYPEINC)
LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS)
# flags # flags
CPPFLAGS = -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS)
LDFLAGS = -s ${LIBS} LDFLAGS = $(LIBS)
# compiler and linker # compiler and linker
CC = cc CC = cc

80
dmenu.1
View File

@ -3,13 +3,11 @@
dmenu \- dynamic menu dmenu \- dynamic menu
.SH SYNOPSIS .SH SYNOPSIS
.B dmenu .B dmenu
.RB [ \-b ] .RB [ \-bfiv ]
.RB [ \-f ]
.RB [ \-i ]
.RB [ \-l .RB [ \-l
.IR lines ]
.RB [ \-m .RB [ \-m
.IR monitor ] .IR monitor ]
.IR lines ]
.RB [ \-p .RB [ \-p
.IR prompt ] .IR prompt ]
.RB [ \-fn .RB [ \-fn
@ -22,7 +20,8 @@ dmenu \- dynamic menu
.IR color ] .IR color ]
.RB [ \-sf .RB [ \-sf
.IR color ] .IR color ]
.RB [ \-v ] .RB [ \-w
.IR windowid ]
.P .P
.BR dmenu_run " ..." .BR dmenu_run " ..."
.SH DESCRIPTION .SH DESCRIPTION
@ -42,8 +41,8 @@ which lists programs in the user's $PATH and runs the result in their $SHELL.
dmenu appears at the bottom of the screen. dmenu appears at the bottom of the screen.
.TP .TP
.B \-f .B \-f
dmenu grabs the keyboard before reading stdin. This is faster, but will lock up dmenu grabs the keyboard before reading stdin if not reading from a tty. This
X until stdin reaches end\-of\-file. is faster, but will lock up X until stdin reaches end\-of\-file.
.TP .TP
.B \-i .B \-i
dmenu matches menu items case insensitively. dmenu matches menu items case insensitively.
@ -78,6 +77,9 @@ defines the selected foreground color.
.TP .TP
.B \-v .B \-v
prints version information to stdout, then exits. prints version information to stdout, then exits.
.TP
.BI \-w " windowid"
embed into windowid.
.SH USAGE .SH USAGE
dmenu is completely controlled by the keyboard. Items are selected using the dmenu is completely controlled by the keyboard. Items are selected using the
arrow keys, page up, page down, home, and end. arrow keys, page up, page down, home, and end.
@ -98,82 +100,94 @@ Confirm input. Prints the input text to stdout and exits, returning success.
.B Escape .B Escape
Exit without selecting an item, returning failure. Exit without selecting an item, returning failure.
.TP .TP
C\-a .B Ctrl-Left
Move cursor to the start of the current word
.TP
.B Ctrl-Right
Move cursor to the end of the current word
.TP
.B C\-a
Home Home
.TP .TP
C\-b .B C\-b
Left Left
.TP .TP
C\-c .B C\-c
Escape Escape
.TP .TP
C\-d .B C\-d
Delete Delete
.TP .TP
C\-e .B C\-e
End End
.TP .TP
C\-f .B C\-f
Right Right
.TP .TP
C\-g .B C\-g
Escape Escape
.TP .TP
C\-h .B C\-h
Backspace Backspace
.TP .TP
C\-i .B C\-i
Tab Tab
.TP .TP
C\-j .B C\-j
Return Return
.TP .TP
C\-J .B C\-J
Shift-Return Shift-Return
.TP .TP
C\-k .B C\-k
Delete line right Delete line right
.TP .TP
C\-m .B C\-m
Return Return
.TP .TP
C\-M .B C\-M
Shift-Return Shift-Return
.TP .TP
C\-n .B C\-n
Down Down
.TP .TP
C\-p .B C\-p
Up Up
.TP .TP
C\-u .B C\-u
Delete line left Delete line left
.TP .TP
C\-w .B C\-w
Delete word left Delete word left
.TP .TP
C\-y .B C\-y
Paste from primary X selection Paste from primary X selection
.TP .TP
C\-Y .B C\-Y
Paste from X clipboard Paste from X clipboard
.TP .TP
M\-g .B M\-b
Move cursor to the start of the current word
.TP
.B M\-f
Move cursor to the end of the current word
.TP
.B M\-g
Home Home
.TP .TP
M\-G .B M\-G
End End
.TP .TP
M\-h .B M\-h
Up Up
.TP .TP
M\-j .B M\-j
Page down Page down
.TP .TP
M\-k .B M\-k
Page up Page up
.TP .TP
M\-l .B M\-l
Down Down
.SH SEE ALSO .SH SEE ALSO
.IR dwm (1), .IR dwm (1),

385
dmenu.c
View File

@ -6,6 +6,7 @@
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <time.h> #include <time.h>
#include <unistd.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xatom.h> #include <X11/Xatom.h>
@ -21,9 +22,7 @@
/* macros */ /* macros */
#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \
* MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
#define LENGTH(X) (sizeof X / sizeof X[0]) #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
#define TEXTNW(X,N) (drw_font_getexts_width(drw->fonts[0], (X), (N)))
#define TEXTW(X) (drw_text(drw, 0, 0, 0, 0, (X), 0) + drw->fonts[0]->h)
/* enums */ /* enums */
enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
@ -35,9 +34,10 @@ struct item {
}; };
static char text[BUFSIZ] = ""; static char text[BUFSIZ] = "";
static char *embed;
static int bh, mw, mh; static int bh, mw, mh;
static int sw, sh; /* X display screen geometry width, height */ static int inputw = 0, promptw;
static int inputw, promptw; static int lrpad; /* sum of left and right padding */
static size_t cursor; static size_t cursor;
static struct item *items = NULL; static struct item *items = NULL;
static struct item *matches, *matchend; static struct item *matches, *matchend;
@ -46,17 +46,24 @@ static int mon = -1, screen;
static Atom clip, utf8; static Atom clip, utf8;
static Display *dpy; static Display *dpy;
static Window root, win; static Window root, parentwin, win;
static XIC xic; static XIC xic;
static ClrScheme scheme[SchemeLast];
static Drw *drw; static Drw *drw;
static Clr *scheme[SchemeLast];
#include "config.h" #include "config.h"
static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
static char *(*fstrstr)(const char *, const char *) = strstr; static char *(*fstrstr)(const char *, const char *) = strstr;
static unsigned int
textw_clamp(const char *str, unsigned int n)
{
unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
return MIN(w, n);
}
static void static void
appenditem(struct item *item, struct item **list, struct item **last) appenditem(struct item *item, struct item **list, struct item **last)
{ {
@ -81,10 +88,10 @@ calcoffsets(void)
n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
/* calculate which items will begin the next page and previous page */ /* calculate which items will begin the next page and previous page */
for (i = 0, next = curr; next; next = next->right) for (i = 0, next = curr; next; next = next->right)
if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n)
break; break;
for (i = 0, prev = curr; prev && prev->left; prev = prev->left) for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n)
break; break;
} }
@ -93,110 +100,130 @@ cleanup(void)
{ {
size_t i; size_t i;
XUngrabKey(dpy, AnyKey, AnyModifier, root); XUngrabKeyboard(dpy, CurrentTime);
for (i = 0; i < SchemeLast; i++) { for (i = 0; i < SchemeLast; i++)
drw_clr_free(scheme[i].bg); free(scheme[i]);
drw_clr_free(scheme[i].fg); for (i = 0; items && items[i].text; ++i)
} free(items[i].text);
free(items);
drw_free(drw); drw_free(drw);
XSync(dpy, False); XSync(dpy, False);
XCloseDisplay(dpy); XCloseDisplay(dpy);
} }
static char * static char *
cistrstr(const char *s, const char *sub) cistrstr(const char *h, const char *n)
{ {
size_t len; size_t i;
for (len = strlen(sub); *s; s++) if (!n[0])
if (!strncasecmp(s, sub, len)) return (char *)h;
return (char *)s;
for (; *h; ++h) {
for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
tolower((unsigned char)h[i]); ++i)
;
if (n[i] == '\0')
return (char *)h;
}
return NULL; return NULL;
} }
static int
drawitem(struct item *item, int x, int y, int w)
{
if (item == sel)
drw_setscheme(drw, scheme[SchemeSel]);
else if (item->out)
drw_setscheme(drw, scheme[SchemeOut]);
else
drw_setscheme(drw, scheme[SchemeNorm]);
return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
}
static void static void
drawmenu(void) drawmenu(void)
{ {
int curpos; unsigned int curpos;
struct item *item; struct item *item;
int x = 0, y = 0, h = bh, w; int x = 0, y = 0, w;
drw_setscheme(drw, &scheme[SchemeNorm]); drw_setscheme(drw, scheme[SchemeNorm]);
drw_rect(drw, 0, 0, mw, mh, 1, 1, 1); drw_rect(drw, 0, 0, mw, mh, 1, 1);
if (prompt && *prompt) { if (prompt && *prompt) {
drw_setscheme(drw, &scheme[SchemeSel]); drw_setscheme(drw, scheme[SchemeSel]);
drw_text(drw, x, 0, promptw, bh, prompt, 0); x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
x += promptw;
} }
/* draw input field */ /* draw input field */
w = (lines > 0 || !matches) ? mw - x : inputw; w = (lines > 0 || !matches) ? mw - x : inputw;
drw_setscheme(drw, &scheme[SchemeNorm]); drw_setscheme(drw, scheme[SchemeNorm]);
drw_text(drw, x, 0, w, bh, text, 0); drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
if ((curpos = TEXTNW(text, cursor) + bh / 2 - 2) < w) { curpos = TEXTW(text) - TEXTW(&text[cursor]);
drw_setscheme(drw, &scheme[SchemeNorm]); if ((curpos += lrpad / 2 - 1) < w) {
drw_rect(drw, x + curpos + 2, 2, 1, bh - 4, 1, 1, 0); drw_setscheme(drw, scheme[SchemeNorm]);
drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
} }
if (lines > 0) { if (lines > 0) {
/* draw vertical list */ /* draw vertical list */
w = mw - x; for (item = curr; item != next; item = item->right)
for (item = curr; item != next; item = item->right) { drawitem(item, x, y += bh, mw - x);
y += h;
if (item == sel)
drw_setscheme(drw, &scheme[SchemeSel]);
else if (item->out)
drw_setscheme(drw, &scheme[SchemeOut]);
else
drw_setscheme(drw, &scheme[SchemeNorm]);
drw_text(drw, x, y, w, bh, item->text, 0);
}
} else if (matches) { } else if (matches) {
/* draw horizontal list */ /* draw horizontal list */
x += inputw; x += inputw;
w = TEXTW("<"); w = TEXTW("<");
if (curr->left) { if (curr->left) {
drw_setscheme(drw, &scheme[SchemeNorm]); drw_setscheme(drw, scheme[SchemeNorm]);
drw_text(drw, x, 0, w, bh, "<", 0); drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0);
} }
for (item = curr; item != next; item = item->right) { x += w;
x += w; for (item = curr; item != next; item = item->right)
w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">")));
if (item == sel)
drw_setscheme(drw, &scheme[SchemeSel]);
else if (item->out)
drw_setscheme(drw, &scheme[SchemeOut]);
else
drw_setscheme(drw, &scheme[SchemeNorm]);
drw_text(drw, x, 0, w, bh, item->text, 0);
}
w = TEXTW(">");
x = mw - w;
if (next) { if (next) {
drw_setscheme(drw, &scheme[SchemeNorm]); w = TEXTW(">");
drw_text(drw, x, 0, w, bh, ">", 0); drw_setscheme(drw, scheme[SchemeNorm]);
drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
} }
} }
drw_map(drw, win, 0, 0, mw, mh); drw_map(drw, win, 0, 0, mw, mh);
} }
static void
grabfocus(void)
{
struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 };
Window focuswin;
int i, revertwin;
for (i = 0; i < 100; ++i) {
XGetInputFocus(dpy, &focuswin, &revertwin);
if (focuswin == win)
return;
XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
nanosleep(&ts, NULL);
}
die("cannot grab focus");
}
static void static void
grabkeyboard(void) grabkeyboard(void)
{ {
struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
int i; int i;
if (embed)
return;
/* try to grab keyboard, we may have to wait for another process to ungrab */ /* try to grab keyboard, we may have to wait for another process to ungrab */
for (i = 0; i < 1000; i++) { for (i = 0; i < 1000; i++) {
if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync,
GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) GrabModeAsync, CurrentTime) == GrabSuccess)
return; return;
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
} }
die("cannot grab keyboard\n"); die("cannot grab keyboard");
} }
static void static void
@ -214,11 +241,11 @@ match(void)
/* separate input text into tokens to be matched individually */ /* separate input text into tokens to be matched individually */
for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
die("cannot realloc %u bytes\n", tokn * sizeof *tokv); die("cannot realloc %zu bytes:", tokn * sizeof *tokv);
len = tokc ? strlen(tokv[0]) : 0; len = tokc ? strlen(tokv[0]) : 0;
matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
textsize = strlen(text); textsize = strlen(text) + 1;
for (item = items; item && item->text; item++) { for (item = items; item && item->text; item++) {
for (i = 0; i < tokc; i++) for (i = 0; i < tokc; i++)
if (!fstrstr(item->text, tokv[i])) if (!fstrstr(item->text, tokv[i]))
@ -277,18 +304,42 @@ nextrune(int inc)
return n; return n;
} }
static void
movewordedge(int dir)
{
if (dir < 0) { /* move cursor to the start of the word*/
while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
cursor = nextrune(-1);
while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
cursor = nextrune(-1);
} else { /* move cursor to the end of the word */
while (text[cursor] && strchr(worddelimiters, text[cursor]))
cursor = nextrune(+1);
while (text[cursor] && !strchr(worddelimiters, text[cursor]))
cursor = nextrune(+1);
}
}
static void static void
keypress(XKeyEvent *ev) keypress(XKeyEvent *ev)
{ {
char buf[32]; char buf[64];
int len; int len;
KeySym ksym = NoSymbol; KeySym ksym = NoSymbol;
Status status; Status status;
len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
if (status == XBufferOverflow) switch (status) {
default: /* XLookupNone, XBufferOverflow */
return; return;
if (ev->state & ControlMask) case XLookupChars: /* composed string from input method */
goto insert;
case XLookupKeySym:
case XLookupBoth: /* a KeySym and a string are returned: use keysym */
break;
}
if (ev->state & ControlMask) {
switch(ksym) { switch(ksym) {
case XK_a: ksym = XK_Home; break; case XK_a: ksym = XK_Home; break;
case XK_b: ksym = XK_Left; break; case XK_b: ksym = XK_Left; break;
@ -314,9 +365,9 @@ keypress(XKeyEvent *ev)
insert(NULL, 0 - cursor); insert(NULL, 0 - cursor);
break; break;
case XK_w: /* delete word */ case XK_w: /* delete word */
while (cursor > 0 && text[nextrune(-1)] == ' ') while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
insert(NULL, nextrune(-1) - cursor); insert(NULL, nextrune(-1) - cursor);
while (cursor > 0 && text[nextrune(-1)] != ' ') while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
insert(NULL, nextrune(-1) - cursor); insert(NULL, nextrune(-1) - cursor);
break; break;
case XK_y: /* paste selection */ case XK_y: /* paste selection */
@ -324,6 +375,14 @@ keypress(XKeyEvent *ev)
XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
utf8, utf8, win, CurrentTime); utf8, utf8, win, CurrentTime);
return; return;
case XK_Left:
case XK_KP_Left:
movewordedge(-1);
goto draw;
case XK_Right:
case XK_KP_Right:
movewordedge(+1);
goto draw;
case XK_Return: case XK_Return:
case XK_KP_Enter: case XK_KP_Enter:
break; break;
@ -333,8 +392,14 @@ keypress(XKeyEvent *ev)
default: default:
return; return;
} }
else if (ev->state & Mod1Mask) } else if (ev->state & Mod1Mask) {
switch(ksym) { switch(ksym) {
case XK_b:
movewordedge(-1);
goto draw;
case XK_f:
movewordedge(+1);
goto draw;
case XK_g: ksym = XK_Home; break; case XK_g: ksym = XK_Home; break;
case XK_G: ksym = XK_End; break; case XK_G: ksym = XK_End; break;
case XK_h: ksym = XK_Up; break; case XK_h: ksym = XK_Up; break;
@ -344,12 +409,16 @@ keypress(XKeyEvent *ev)
default: default:
return; return;
} }
}
switch(ksym) { switch(ksym) {
default: default:
if (!iscntrl(*buf)) insert:
if (!iscntrl((unsigned char)*buf))
insert(buf, len); insert(buf, len);
break; break;
case XK_Delete: case XK_Delete:
case XK_KP_Delete:
if (text[cursor] == '\0') if (text[cursor] == '\0')
return; return;
cursor = nextrune(+1); cursor = nextrune(+1);
@ -360,6 +429,7 @@ keypress(XKeyEvent *ev)
insert(NULL, nextrune(-1) - cursor); insert(NULL, nextrune(-1) - cursor);
break; break;
case XK_End: case XK_End:
case XK_KP_End:
if (text[cursor] != '\0') { if (text[cursor] != '\0') {
cursor = strlen(text); cursor = strlen(text);
break; break;
@ -379,6 +449,7 @@ keypress(XKeyEvent *ev)
cleanup(); cleanup();
exit(1); exit(1);
case XK_Home: case XK_Home:
case XK_KP_Home:
if (sel == matches) { if (sel == matches) {
cursor = 0; cursor = 0;
break; break;
@ -387,6 +458,7 @@ keypress(XKeyEvent *ev)
calcoffsets(); calcoffsets();
break; break;
case XK_Left: case XK_Left:
case XK_KP_Left:
if (cursor > 0 && (!sel || !sel->left || lines > 0)) { if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
cursor = nextrune(-1); cursor = nextrune(-1);
break; break;
@ -395,18 +467,21 @@ keypress(XKeyEvent *ev)
return; return;
/* fallthrough */ /* fallthrough */
case XK_Up: case XK_Up:
case XK_KP_Up:
if (sel && sel->left && (sel = sel->left)->right == curr) { if (sel && sel->left && (sel = sel->left)->right == curr) {
curr = prev; curr = prev;
calcoffsets(); calcoffsets();
} }
break; break;
case XK_Next: case XK_Next:
case XK_KP_Next:
if (!next) if (!next)
return; return;
sel = curr = next; sel = curr = next;
calcoffsets(); calcoffsets();
break; break;
case XK_Prior: case XK_Prior:
case XK_KP_Prior:
if (!prev) if (!prev)
return; return;
sel = curr = prev; sel = curr = prev;
@ -423,6 +498,7 @@ keypress(XKeyEvent *ev)
sel->out = 1; sel->out = 1;
break; break;
case XK_Right: case XK_Right:
case XK_KP_Right:
if (text[cursor] != '\0') { if (text[cursor] != '\0') {
cursor = nextrune(+1); cursor = nextrune(+1);
break; break;
@ -431,6 +507,7 @@ keypress(XKeyEvent *ev)
return; return;
/* fallthrough */ /* fallthrough */
case XK_Down: case XK_Down:
case XK_KP_Down:
if (sel && sel->right && (sel = sel->right) == next) { if (sel && sel->right && (sel = sel->right) == next) {
curr = next; curr = next;
calcoffsets(); calcoffsets();
@ -439,12 +516,14 @@ keypress(XKeyEvent *ev)
case XK_Tab: case XK_Tab:
if (!sel) if (!sel)
return; return;
strncpy(text, sel->text, sizeof text - 1); cursor = strnlen(sel->text, sizeof text - 1);
text[sizeof text - 1] = '\0'; memcpy(text, sel->text, cursor);
cursor = strlen(text); text[cursor] = '\0';
match(); match();
break; break;
} }
draw:
drawmenu(); drawmenu();
} }
@ -457,35 +536,39 @@ paste(void)
Atom da; Atom da;
/* we have been given the current selection, now insert it into input */ /* we have been given the current selection, now insert it into input */
XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
utf8, &da, &di, &dl, &dl, (unsigned char **)&p); utf8, &da, &di, &dl, &dl, (unsigned char **)&p)
insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); == Success && p) {
XFree(p); insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
XFree(p);
}
drawmenu(); drawmenu();
} }
static void static void
readstdin(void) readstdin(void)
{ {
char buf[sizeof text], *p, *maxstr = NULL; char *line = NULL;
size_t i, max = 0, size = 0; size_t i, itemsiz = 0, linesiz = 0;
ssize_t len;
/* read each line from stdin and add it to the item list */ /* read each line from stdin and add it to the item list */
for (i = 0; fgets(buf, sizeof buf, stdin); i++) { for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) {
if (i + 1 >= size / sizeof *items) if (i + 1 >= itemsiz) {
if (!(items = realloc(items, (size += BUFSIZ)))) itemsiz += 256;
die("cannot realloc %u bytes:", size); if (!(items = realloc(items, itemsiz * sizeof(*items))))
if ((p = strchr(buf, '\n'))) die("cannot realloc %zu bytes:", itemsiz * sizeof(*items));
*p = '\0'; }
if (!(items[i].text = strdup(buf))) if (line[len - 1] == '\n')
die("cannot strdup %u bytes:", strlen(buf) + 1); line[len - 1] = '\0';
if (!(items[i].text = strdup(line)))
die("strdup:");
items[i].out = 0; items[i].out = 0;
if (strlen(items[i].text) > max)
max = strlen(maxstr = items[i].text);
} }
free(line);
if (items) if (items)
items[i].text = NULL; items[i].text = NULL;
inputw = maxstr ? TEXTW(maxstr) : 0;
lines = MIN(lines, i); lines = MIN(lines, i);
} }
@ -498,10 +581,20 @@ run(void)
if (XFilterEvent(&ev, win)) if (XFilterEvent(&ev, win))
continue; continue;
switch(ev.type) { switch(ev.type) {
case DestroyNotify:
if (ev.xdestroywindow.window != win)
break;
cleanup();
exit(1);
case Expose: case Expose:
if (ev.xexpose.count == 0) if (ev.xexpose.count == 0)
drw_map(drw, win, 0, 0, mw, mh); drw_map(drw, win, 0, 0, mw, mh);
break; break;
case FocusIn:
/* regrab focus from parent window */
if (ev.xfocus.window != win)
grabfocus();
break;
case KeyPress: case KeyPress:
keypress(&ev.xkey); keypress(&ev.xkey);
break; break;
@ -520,36 +613,34 @@ run(void)
static void static void
setup(void) setup(void)
{ {
int x, y; int x, y, i, j;
unsigned int du;
XSetWindowAttributes swa; XSetWindowAttributes swa;
XIM xim; XIM xim;
Window w, dw, *dws;
XWindowAttributes wa;
XClassHint ch = {"dmenu", "dmenu"};
#ifdef XINERAMA #ifdef XINERAMA
XineramaScreenInfo *info; XineramaScreenInfo *info;
Window w, pw, dw, *dws; Window pw;
XWindowAttributes wa; int a, di, n, area = 0;
int a, j, di, n, i = 0, area = 0;
unsigned int du;
#endif #endif
/* init appearance */ /* init appearance */
scheme[SchemeNorm].bg = drw_clr_create(drw, normbgcolor); for (j = 0; j < SchemeLast; j++)
scheme[SchemeNorm].fg = drw_clr_create(drw, normfgcolor); scheme[j] = drw_scm_create(drw, colors[j], 2);
scheme[SchemeSel].bg = drw_clr_create(drw, selbgcolor);
scheme[SchemeSel].fg = drw_clr_create(drw, selfgcolor);
scheme[SchemeOut].bg = drw_clr_create(drw, outbgcolor);
scheme[SchemeOut].fg = drw_clr_create(drw, outfgcolor);
clip = XInternAtom(dpy, "CLIPBOARD", False); clip = XInternAtom(dpy, "CLIPBOARD", False);
utf8 = XInternAtom(dpy, "UTF8_STRING", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False);
/* calculate menu geometry */ /* calculate menu geometry */
bh = drw->fonts[0]->h + 2; bh = drw->fonts->h + 2;
lines = MAX(lines, 0); lines = MAX(lines, 0);
mh = (lines + 1) * bh; mh = (lines + 1) * bh;
#ifdef XINERAMA #ifdef XINERAMA
if ((info = XineramaQueryScreens(dpy, &n))) { i = 0;
if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
XGetInputFocus(dpy, &w, &di); XGetInputFocus(dpy, &w, &di);
if (mon != -1 && mon < n) if (mon >= 0 && mon < n)
i = mon; i = mon;
else if (w != root && w != PointerRoot && w != None) { else if (w != root && w != PointerRoot && w != None) {
/* find top-level window containing current input focus */ /* find top-level window containing current input focus */
@ -566,9 +657,9 @@ setup(void)
} }
} }
/* no focused window is on screen, so use pointer location instead */ /* no focused window is on screen, so use pointer location instead */
if (mon == -1 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
for (i = 0; i < n; i++) for (i = 0; i < n; i++)
if (INTERSECT(x, y, 1, 1, info[i])) if (INTERSECT(x, y, 1, 1, info[i]) != 0)
break; break;
x = info[i].x_org; x = info[i].x_org;
@ -578,29 +669,44 @@ setup(void)
} else } else
#endif #endif
{ {
if (!XGetWindowAttributes(dpy, parentwin, &wa))
die("could not get embedding window attributes: 0x%lx",
parentwin);
x = 0; x = 0;
y = topbar ? 0 : sh - mh; y = topbar ? 0 : wa.height - mh;
mw = sw; mw = wa.width;
} }
promptw = (prompt && *prompt) ? TEXTW(prompt) : 0; promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
inputw = MIN(inputw, mw/3); inputw = mw / 3; /* input width: ~33% of monitor width */
match(); match();
/* create menu window */ /* create menu window */
swa.override_redirect = True; swa.override_redirect = True;
swa.background_pixel = scheme[SchemeNorm].bg->pix; swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
win = XCreateWindow(dpy, root, x, y, mw, mh, 0, win = XCreateWindow(dpy, root, x, y, mw, mh, 0,
DefaultDepth(dpy, screen), CopyFromParent, CopyFromParent, CopyFromParent, CopyFromParent,
DefaultVisual(dpy, screen),
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
XSetClassHint(dpy, win, &ch);
/* input methods */
if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
die("XOpenIM failed: could not open input device");
/* open input methods */
xim = XOpenIM(dpy, NULL, NULL, NULL);
xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win, XNFocusWindow, win, NULL); XNClientWindow, win, XNFocusWindow, win, NULL);
XMapRaised(dpy, win); XMapRaised(dpy, win);
if (embed) {
XReparentWindow(dpy, win, parentwin, x, y);
XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
for (i = 0; i < du && dws[i] != win; ++i)
XSelectInput(dpy, dws[i], FocusChangeMask);
XFree(dws);
}
grabfocus();
}
drw_resize(drw, mw, mh); drw_resize(drw, mw, mh);
drawmenu(); drawmenu();
} }
@ -608,14 +714,14 @@ setup(void)
static void static void
usage(void) usage(void)
{ {
fputs("usage: dmenu [-b] [-f] [-i] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
" [-nb color] [-nf color] [-sb color] [-sf color] [-v]\n", stderr); " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
exit(1);
} }
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
XWindowAttributes wa;
int i, fast = 0; int i, fast = 0;
for (i = 1; i < argc; i++) for (i = 1; i < argc; i++)
@ -642,31 +748,40 @@ main(int argc, char *argv[])
else if (!strcmp(argv[i], "-fn")) /* font or font set */ else if (!strcmp(argv[i], "-fn")) /* font or font set */
fonts[0] = argv[++i]; fonts[0] = argv[++i];
else if (!strcmp(argv[i], "-nb")) /* normal background color */ else if (!strcmp(argv[i], "-nb")) /* normal background color */
normbgcolor = argv[++i]; colors[SchemeNorm][ColBg] = argv[++i];
else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ else if (!strcmp(argv[i], "-nf")) /* normal foreground color */
normfgcolor = argv[++i]; colors[SchemeNorm][ColFg] = argv[++i];
else if (!strcmp(argv[i], "-sb")) /* selected background color */ else if (!strcmp(argv[i], "-sb")) /* selected background color */
selbgcolor = argv[++i]; colors[SchemeSel][ColBg] = argv[++i];
else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ else if (!strcmp(argv[i], "-sf")) /* selected foreground color */
selfgcolor = argv[++i]; colors[SchemeSel][ColFg] = argv[++i];
else if (!strcmp(argv[i], "-w")) /* embedding window id */
embed = argv[++i];
else else
usage(); usage();
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
fputs("warning: no locale support\n", stderr); fputs("warning: no locale support\n", stderr);
if (!(dpy = XOpenDisplay(NULL))) if (!(dpy = XOpenDisplay(NULL)))
die("cannot open display\n"); die("cannot open display");
screen = DefaultScreen(dpy); screen = DefaultScreen(dpy);
root = RootWindow(dpy, screen); root = RootWindow(dpy, screen);
sw = DisplayWidth(dpy, screen); if (!embed || !(parentwin = strtol(embed, NULL, 0)))
sh = DisplayHeight(dpy, screen); parentwin = root;
drw = drw_create(dpy, screen, root, sw, sh); if (!XGetWindowAttributes(dpy, parentwin, &wa))
drw_load_fonts(drw, fonts, LENGTH(fonts)); die("could not get embedding window attributes: 0x%lx",
if (!drw->fontcount) parentwin);
die("no fonts could be loaded.\n"); drw = drw_create(dpy, screen, root, wa.width, wa.height);
drw_setscheme(drw, &scheme[SchemeNorm]); if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
die("no fonts could be loaded.");
lrpad = drw->fonts->h;
if (fast) { #ifdef __OpenBSD__
if (pledge("stdio rpath", NULL) == -1)
die("pledge");
#endif
if (fast && !isatty(0)) {
grabkeyboard(); grabkeyboard();
readstdin(); readstdin();
} else { } else {

12
dmenu_path Normal file → Executable file
View File

@ -1,10 +1,10 @@
#!/bin/sh #!/bin/sh
cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"}
if [ -d "$cachedir" ]; then cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}"
cache=$cachedir/dmenu_run cache="$cachedir/dmenu_run"
else
cache=$HOME/.dmenu_cache # if no xdg dir, fall back to dotfile in ~ [ ! -e "$cachedir" ] && mkdir -p "$cachedir"
fi
IFS=: IFS=:
if stest -dqr -n "$cache" $PATH; then if stest -dqr -n "$cache" $PATH; then
stest -flx $PATH | sort -u | tee "$cache" stest -flx $PATH | sort -u | tee "$cache"

398
drw.c
View File

@ -9,63 +9,48 @@
#include "util.h" #include "util.h"
#define UTF_INVALID 0xFFFD #define UTF_INVALID 0xFFFD
#define UTF_SIZ 4
static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; static int
static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; utf8decode(const char *s_in, long *u, int *err)
static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
static long
utf8decodebyte(const char c, size_t *i)
{ {
for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) static const unsigned char lens[] = {
if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
return (unsigned char)c & ~utfmask[*i]; /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */
return 0; /* 110XX */ 2, 2, 2, 2,
} /* 1110X */ 3, 3,
/* 11110 */ 4,
static size_t /* 11111 */ 0, /* invalid */
utf8validate(long *u, size_t i) };
{ static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 };
if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 };
*u = UTF_INVALID;
for (i = 1; *u > utfmax[i]; ++i)
;
return i;
}
static size_t
utf8decode(const char *c, long *u, size_t clen)
{
size_t i, j, len, type;
long udecoded;
const unsigned char *s = (const unsigned char *)s_in;
int len = lens[*s >> 3];
*u = UTF_INVALID; *u = UTF_INVALID;
if (!clen) *err = 1;
return 0; if (len == 0)
udecoded = utf8decodebyte(c[0], &len);
if (!BETWEEN(len, 1, UTF_SIZ))
return 1; return 1;
for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
if (type)
return j;
}
if (j < len)
return 0;
*u = udecoded;
utf8validate(u, len);
long cp = s[0] & leading_mask[len - 1];
for (int i = 1; i < len; ++i) {
if (s[i] == '\0' || (s[i] & 0xC0) != 0x80)
return i;
cp = (cp << 6) | (s[i] & 0x3F);
}
/* out of range, surrogate, overlong encoding */
if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1])
return len;
*err = 0;
*u = cp;
return len; return len;
} }
Drw * Drw *
drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
{ {
Drw *drw; Drw *drw = ecalloc(1, sizeof(Drw));
drw = ecalloc(1, sizeof(Drw));
drw->dpy = dpy; drw->dpy = dpy;
drw->screen = screen; drw->screen = screen;
drw->root = root; drw->root = root;
@ -73,7 +58,6 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h
drw->h = h; drw->h = h;
drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
drw->gc = XCreateGC(dpy, root, 0, NULL); drw->gc = XCreateGC(dpy, root, 0, NULL);
drw->fontcount = 0;
XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
return drw; return drw;
@ -82,6 +66,9 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h
void void
drw_resize(Drw *drw, unsigned int w, unsigned int h) drw_resize(Drw *drw, unsigned int w, unsigned int h)
{ {
if (!drw)
return;
drw->w = w; drw->w = w;
drw->h = h; drw->h = h;
if (drw->drawable) if (drw->drawable)
@ -92,84 +79,57 @@ drw_resize(Drw *drw, unsigned int w, unsigned int h)
void void
drw_free(Drw *drw) drw_free(Drw *drw)
{ {
size_t i;
for (i = 0; i < drw->fontcount; i++)
drw_font_free(drw->fonts[i]);
XFreePixmap(drw->dpy, drw->drawable); XFreePixmap(drw->dpy, drw->drawable);
XFreeGC(drw->dpy, drw->gc); XFreeGC(drw->dpy, drw->gc);
drw_fontset_free(drw->fonts);
free(drw); free(drw);
} }
/* This function is an implementation detail. Library users should use /* This function is an implementation detail. Library users should use
* drw_font_create instead. * drw_fontset_create instead.
*/ */
static Fnt * static Fnt *
drw_font_xcreate(Drw *drw, const char *fontname, FcPattern *fontpattern) xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
{ {
Fnt *font; Fnt *font;
XftFont *xfont = NULL; XftFont *xfont = NULL;
FcPattern *pattern = NULL; FcPattern *pattern = NULL;
if (fontname) { if (fontname) {
/* Using the pattern found at font->xfont->pattern does not yield same /* Using the pattern found at font->xfont->pattern does not yield the
* the same substitution results as using the pattern returned by * same substitution results as using the pattern returned by
* FcNameParse; using the latter results in the desired fallback * FcNameParse; using the latter results in the desired fallback
* behaviour whereas the former just results in * behaviour whereas the former just results in missing-character
* missing-character-rectangles being drawn, at least with some fonts. * rectangles being drawn, at least with some fonts. */
*/
if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
fprintf(stderr, "error, cannot load font: '%s'\n", fontname); fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
return NULL; return NULL;
} }
if (!(pattern = FcNameParse((FcChar8 *) fontname))) { if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
fprintf(stderr, "error, cannot load font: '%s'\n", fontname); fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
XftFontClose(drw->dpy, xfont); XftFontClose(drw->dpy, xfont);
return NULL; return NULL;
} }
} else if (fontpattern) { } else if (fontpattern) {
if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
fprintf(stderr, "error, cannot load font pattern.\n"); fprintf(stderr, "error, cannot load font from pattern.\n");
return NULL; return NULL;
} }
} else { } else {
die("no font specified.\n"); die("no font specified.");
} }
font = ecalloc(1, sizeof(Fnt)); font = ecalloc(1, sizeof(Fnt));
font->xfont = xfont; font->xfont = xfont;
font->pattern = pattern; font->pattern = pattern;
font->ascent = xfont->ascent; font->h = xfont->ascent + xfont->descent;
font->descent = xfont->descent;
font->h = font->ascent + font->descent;
font->dpy = drw->dpy; font->dpy = drw->dpy;
return font; return font;
} }
Fnt* static void
drw_font_create(Drw *drw, const char *fontname) xfont_free(Fnt *font)
{
return drw_font_xcreate(drw, fontname, NULL);
}
void
drw_load_fonts(Drw* drw, const char *fonts[], size_t fontcount)
{
size_t i;
Fnt *font;
for (i = 0; i < fontcount; i++) {
if (drw->fontcount >= DRW_FONT_CACHE_SIZE) {
die("Font cache exhausted.\n");
} else if ((font = drw_font_xcreate(drw, fonts[i], NULL))) {
drw->fonts[drw->fontcount++] = font;
}
}
}
void
drw_font_free(Fnt *font)
{ {
if (!font) if (!font)
return; return;
@ -179,150 +139,218 @@ drw_font_free(Fnt *font)
free(font); free(font);
} }
Clr * Fnt*
drw_clr_create(Drw *drw, const char *clrname) drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)
{ {
Clr *clr; Fnt *cur, *ret = NULL;
size_t i;
if (!drw || !fonts)
return NULL;
for (i = 1; i <= fontcount; i++) {
if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
cur->next = ret;
ret = cur;
}
}
return (drw->fonts = ret);
}
void
drw_fontset_free(Fnt *font)
{
if (font) {
drw_fontset_free(font->next);
xfont_free(font);
}
}
void
drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
{
if (!drw || !dest || !clrname)
return;
clr = ecalloc(1, sizeof(Clr));
if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen), DefaultColormap(drw->dpy, drw->screen),
clrname, &clr->rgb)) clrname, dest))
die("error, cannot allocate color '%s'\n", clrname); die("error, cannot allocate color '%s'", clrname);
clr->pix = clr->rgb.pixel; }
return clr; /* Wrapper to create color schemes. The caller has to call free(3) on the
* returned color scheme when done using it. */
Clr *
drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
{
size_t i;
Clr *ret;
/* need at least two colors for a scheme */
if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
return NULL;
for (i = 0; i < clrcount; i++)
drw_clr_create(drw, &ret[i], clrnames[i]);
return ret;
} }
void void
drw_clr_free(Clr *clr) drw_setfontset(Drw *drw, Fnt *set)
{ {
free(clr); if (drw)
drw->fonts = set;
} }
void void
drw_setscheme(Drw *drw, ClrScheme *scheme) drw_setscheme(Drw *drw, Clr *scm)
{ {
drw->scheme = scheme; if (drw)
drw->scheme = scm;
} }
void void
drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int empty, int invert) drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
{ {
if (!drw->scheme) if (!drw || !drw->scheme)
return; return;
XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->bg->pix : drw->scheme->fg->pix); XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
if (filled) if (filled)
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w + 1, h + 1); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
else if (empty) else
XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
} }
int int
drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int invert) drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
{ {
char buf[1024]; int ty, ellipsis_x = 0;
int tx, ty, th; unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1;
Extnts tex;
XftDraw *d = NULL; XftDraw *d = NULL;
Fnt *curfont, *nextfont; Fnt *usedfont, *curfont, *nextfont;
size_t i, len; int utf8strlen, utf8charlen, utf8err, render = x || y || w || h;
int utf8strlen, utf8charlen, render;
long utf8codepoint = 0; long utf8codepoint = 0;
const char *utf8str; const char *utf8str;
FcCharSet *fccharset; FcCharSet *fccharset;
FcPattern *fcpattern; FcPattern *fcpattern;
FcPattern *match; FcPattern *match;
XftResult result; XftResult result;
int charexists = 0; int charexists = 0, overflow = 0;
/* keep track of a couple codepoints for which we have no match. */
static unsigned int nomatches[128], ellipsis_width, invalid_width;
static const char invalid[] = "<EFBFBD>";
if (!drw->scheme || !drw->fontcount) if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
return 0; return 0;
if (!(render = x || y || w || h)) { if (!render) {
w = ~w; w = invert ? invert : ~invert;
} else { } else {
XSetForeground(drw->dpy, drw->gc, invert ? XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
drw->scheme->fg->pix : drw->scheme->bg->pix);
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
if (w < lpad)
return x + w;
d = XftDrawCreate(drw->dpy, drw->drawable, d = XftDrawCreate(drw->dpy, drw->drawable,
DefaultVisual(drw->dpy, drw->screen), DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen)); DefaultColormap(drw->dpy, drw->screen));
x += lpad;
w -= lpad;
} }
curfont = drw->fonts[0]; usedfont = drw->fonts;
if (!ellipsis_width && render)
ellipsis_width = drw_fontset_getwidth(drw, "...");
if (!invalid_width && render)
invalid_width = drw_fontset_getwidth(drw, invalid);
while (1) { while (1) {
utf8strlen = 0; ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0;
utf8str = text; utf8str = text;
nextfont = NULL; nextfont = NULL;
while (*text) { while (*text) {
utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); utf8charlen = utf8decode(text, &utf8codepoint, &utf8err);
for (i = 0; i < drw->fontcount; i++) { for (curfont = drw->fonts; curfont; curfont = curfont->next) {
charexists = charexists || XftCharExists(drw->dpy, drw->fonts[i]->xfont, utf8codepoint); charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
if (charexists) { if (charexists) {
if (drw->fonts[i] == curfont) { drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL);
utf8strlen += utf8charlen; if (ew + ellipsis_width <= w) {
/* keep track where the ellipsis still fits */
ellipsis_x = x + ew;
ellipsis_w = w - ew;
ellipsis_len = utf8strlen;
}
if (ew + tmpw > w) {
overflow = 1;
/* called from drw_fontset_getwidth_clamp():
* it wants the width AFTER the overflow
*/
if (!render)
x += tmpw;
else
utf8strlen = ellipsis_len;
} else if (curfont == usedfont) {
text += utf8charlen; text += utf8charlen;
utf8strlen += utf8err ? 0 : utf8charlen;
ew += utf8err ? 0 : tmpw;
} else { } else {
nextfont = drw->fonts[i]; nextfont = curfont;
} }
break; break;
} }
} }
if (!charexists || (nextfont && nextfont != curfont)) if (overflow || !charexists || nextfont || utf8err)
break; break;
else else
charexists = 0; charexists = 0;
} }
if (utf8strlen) { if (utf8strlen) {
drw_font_getexts(curfont, utf8str, utf8strlen, &tex); if (render) {
/* shorten text if necessary */ ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
for (len = MIN(utf8strlen, (sizeof buf) - 1); len && (tex.w > w - drw->fonts[0]->h || w < drw->fonts[0]->h); len--) XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
drw_font_getexts(curfont, utf8str, len, &tex); usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen);
if (len) {
memcpy(buf, utf8str, len);
buf[len] = '\0';
if (len < utf8strlen)
for (i = len; i && i > len - 3; buf[--i] = '.');
if (render) {
th = curfont->ascent + curfont->descent;
ty = y + (h / 2) - (th / 2) + curfont->ascent;
tx = x + (h / 2);
XftDrawStringUtf8(d, invert ? &drw->scheme->bg->rgb : &drw->scheme->fg->rgb, curfont->xfont, tx, ty, (XftChar8 *)buf, len);
}
x += tex.w;
w -= tex.w;
} }
x += ew;
w -= ew;
} }
if (utf8err && (!render || invalid_width < w)) {
if (render)
drw_text(drw, x, y, w, h, 0, invalid, invert);
x += invalid_width;
w -= invalid_width;
}
if (render && overflow)
drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
if (!*text) { if (!*text || overflow) {
break; break;
} else if (nextfont) { } else if (nextfont) {
charexists = 0; charexists = 0;
curfont = nextfont; usedfont = nextfont;
} else { } else {
/* Regardless of whether or not a fallback font is found, the /* Regardless of whether or not a fallback font is found, the
* character must be drawn. * character must be drawn. */
*/
charexists = 1; charexists = 1;
if (drw->fontcount >= DRW_FONT_CACHE_SIZE) hash = (unsigned int)utf8codepoint;
continue; hash = ((hash >> 16) ^ hash) * 0x21F0AAAD;
hash = ((hash >> 15) ^ hash) * 0xD35A2D97;
h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches);
h1 = (hash >> 17) % LENGTH(nomatches);
/* avoid expensive XftFontMatch call when we know we won't find a match */
if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint)
goto no_match;
fccharset = FcCharSetCreate(); fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, utf8codepoint); FcCharSetAddChar(fccharset, utf8codepoint);
if (!drw->fonts[0]->pattern) { if (!drw->fonts->pattern) {
/* Refer to the comment in drw_font_xcreate for more /* Refer to the comment in xfont_create for more information. */
* information. */ die("the first font in the cache must be loaded from a font string.");
die("the first font in the cache must be loaded from a font string.\n");
} }
fcpattern = FcPatternDuplicate(drw->fonts[0]->pattern); fcpattern = FcPatternDuplicate(drw->fonts->pattern);
FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
@ -334,12 +362,16 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *tex
FcPatternDestroy(fcpattern); FcPatternDestroy(fcpattern);
if (match) { if (match) {
curfont = drw_font_xcreate(drw, NULL, match); usedfont = xfont_create(drw, NULL, match);
if (curfont && XftCharExists(drw->dpy, curfont->xfont, utf8codepoint)) { if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
drw->fonts[drw->fontcount++] = curfont; for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
; /* NOP */
curfont->next = usedfont;
} else { } else {
drw_font_free(curfont); xfont_free(usedfont);
curfont = drw->fonts[0]; nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint;
no_match:
usedfont = drw->fonts;
} }
} }
} }
@ -347,34 +379,49 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *tex
if (d) if (d)
XftDrawDestroy(d); XftDrawDestroy(d);
return x; return x + (render ? w : 0);
} }
void void
drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
{ {
if (!drw)
return;
XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
XSync(drw->dpy, False); XSync(drw->dpy, False);
} }
void unsigned int
drw_font_getexts(Fnt *font, const char *text, unsigned int len, Extnts *tex) drw_fontset_getwidth(Drw *drw, const char *text)
{ {
XGlyphInfo ext; if (!drw || !drw->fonts || !text)
return 0;
XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
tex->h = font->h;
tex->w = ext.xOff;
} }
unsigned int unsigned int
drw_font_getexts_width(Fnt *font, const char *text, unsigned int len) drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n)
{ {
Extnts tex; unsigned int tmp = 0;
if (drw && drw->fonts && text && n)
tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n);
return MIN(n, tmp);
}
drw_font_getexts(font, text, len, &tex); void
drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
{
XGlyphInfo ext;
return tex.w; if (!font || !text)
return;
XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
if (w)
*w = ext.xOff;
if (h)
*h = font->h;
} }
Cur * Cur *
@ -382,7 +429,9 @@ drw_cur_create(Drw *drw, int shape)
{ {
Cur *cur; Cur *cur;
cur = ecalloc(1, sizeof(Cur)); if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
return NULL;
cur->cursor = XCreateFontCursor(drw->dpy, shape); cur->cursor = XCreateFontCursor(drw->dpy, shape);
return cur; return cur;
@ -393,6 +442,7 @@ drw_cur_free(Drw *drw, Cur *cursor)
{ {
if (!cursor) if (!cursor)
return; return;
XFreeCursor(drw->dpy, cursor->cursor); XFreeCursor(drw->dpy, cursor->cursor);
free(cursor); free(cursor);
} }

64
drw.h
View File

@ -1,29 +1,19 @@
/* See LICENSE file for copyright and license details. */ /* See LICENSE file for copyright and license details. */
#define DRW_FONT_CACHE_SIZE 32
typedef struct {
unsigned long pix;
XftColor rgb;
} Clr;
typedef struct { typedef struct {
Cursor cursor; Cursor cursor;
} Cur; } Cur;
typedef struct { typedef struct Fnt {
Display *dpy; Display *dpy;
int ascent;
int descent;
unsigned int h; unsigned int h;
XftFont *xfont; XftFont *xfont;
FcPattern *pattern; FcPattern *pattern;
struct Fnt *next;
} Fnt; } Fnt;
typedef struct { enum { ColFg, ColBg }; /* Clr scheme index */
Clr *fg; typedef XftColor Clr;
Clr *bg;
Clr *border;
} ClrScheme;
typedef struct { typedef struct {
unsigned int w, h; unsigned int w, h;
@ -32,43 +22,37 @@ typedef struct {
Window root; Window root;
Drawable drawable; Drawable drawable;
GC gc; GC gc;
ClrScheme *scheme; Clr *scheme;
size_t fontcount; Fnt *fonts;
Fnt *fonts[DRW_FONT_CACHE_SIZE];
} Drw; } Drw;
typedef struct {
unsigned int w;
unsigned int h;
} Extnts;
/* Drawable abstraction */ /* Drawable abstraction */
Drw *drw_create(Display *, int, Window, unsigned int, unsigned int); Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h);
void drw_resize(Drw *, unsigned int, unsigned int); void drw_resize(Drw *drw, unsigned int w, unsigned int h);
void drw_free(Drw *); void drw_free(Drw *drw);
/* Fnt abstraction */ /* Fnt abstraction */
Fnt *drw_font_create(Drw *, const char *); Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount);
void drw_load_fonts(Drw *, const char *[], size_t); void drw_fontset_free(Fnt* set);
void drw_font_free(Fnt *); unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
void drw_font_getexts(Fnt *, const char *, unsigned int, Extnts *); unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n);
unsigned int drw_font_getexts_width(Fnt *, const char *, unsigned int); void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
/* Colour abstraction */ /* Colorscheme abstraction */
Clr *drw_clr_create(Drw *, const char *); void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
void drw_clr_free(Clr *); Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
/* Cursor abstraction */ /* Cursor abstraction */
Cur *drw_cur_create(Drw *, int); Cur *drw_cur_create(Drw *drw, int shape);
void drw_cur_free(Drw *, Cur *); void drw_cur_free(Drw *drw, Cur *cursor);
/* Drawing context manipulation */ /* Drawing context manipulation */
void drw_setfont(Drw *, Fnt *); void drw_setfontset(Drw *drw, Fnt *set);
void drw_setscheme(Drw *, ClrScheme *); void drw_setscheme(Drw *drw, Clr *scm);
/* Drawing functions */ /* Drawing functions */
void drw_rect(Drw *, int, int, unsigned int, unsigned int, int, int, int); void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
int drw_text(Drw *, int, int, unsigned int, unsigned int, const char *, int); int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
/* Map functions */ /* Map functions */
void drw_map(Drw *, Window, int, int, unsigned int, unsigned int); void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);

View File

@ -84,7 +84,7 @@ main(int argc, char *argv[])
if (!argc) { if (!argc) {
/* read list from stdin */ /* read list from stdin */
while ((n = getline(&line, &linesiz, stdin)) > 0) { while ((n = getline(&line, &linesiz, stdin)) > 0) {
if (n && line[n - 1] == '\n') if (line[n - 1] == '\n')
line[n - 1] = '\0'; line[n - 1] = '\0';
test(line, line); test(line, line);
} }

38
util.c
View File

@ -1,4 +1,5 @@
/* See LICENSE file for copyright and license details. */ /* See LICENSE file for copyright and license details. */
#include <errno.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -6,28 +7,31 @@
#include "util.h" #include "util.h"
void
die(const char *fmt, ...)
{
va_list ap;
int saved_errno;
saved_errno = errno;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] && fmt[strlen(fmt)-1] == ':')
fprintf(stderr, " %s", strerror(saved_errno));
fputc('\n', stderr);
exit(1);
}
void * void *
ecalloc(size_t nmemb, size_t size) ecalloc(size_t nmemb, size_t size)
{ {
void *p; void *p;
if (!(p = calloc(nmemb, size))) if (!(p = calloc(nmemb, size)))
perror(NULL); die("calloc:");
return p; return p;
} }
void
die(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
fputc(' ', stderr);
perror(NULL);
}
exit(1);
}

5
util.h
View File

@ -3,6 +3,7 @@
#define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B))
#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
#define LENGTH(X) (sizeof (X) / sizeof (X)[0])
void die(const char *errstr, ...); void die(const char *fmt, ...);
void *ecalloc(size_t, size_t); void *ecalloc(size_t nmemb, size_t size);