From 1a117fa7c13de387e85a38530ae740d812980c4e Mon Sep 17 00:00:00 2001 From: Tucker Johnson Date: Fri, 7 Feb 2025 07:26:17 -0500 Subject: [PATCH] init --- FUNDING.yml | 2 + LICENSE | 30 + Makefile | 76 +++ arg.h | 49 ++ config.h | 34 ++ config.mk | 31 ++ dmenu | Bin 0 -> 43224 bytes dmenu-scripts/dm-handler | 21 + dmenu-scripts/dm-kampv | 3 + dmenu-scripts/dm-mountcifs | 19 + dmenu-scripts/dm-pass | 6 + dmenu-scripts/dm-record | 123 +++++ dmenu-scripts/dm-streamselect | 12 + dmenu-scripts/dm-unicode | 16 + dmenu.1 | 200 +++++++ dmenu.c | 997 ++++++++++++++++++++++++++++++++++ dmenu.o | Bin 0 -> 38976 bytes dmenu_path | 13 + dmenu_run | 2 + drw.c | 423 +++++++++++++++ drw.h | 60 ++ drw.o | Bin 0 -> 10208 bytes stest | Bin 0 -> 16408 bytes stest.1 | 90 +++ stest.c | 109 ++++ stest.o | Bin 0 -> 5256 bytes util.c | 35 ++ util.h | 8 + util.o | Bin 0 -> 2224 bytes 29 files changed, 2359 insertions(+) create mode 100644 FUNDING.yml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 arg.h create mode 100644 config.h create mode 100644 config.mk create mode 100755 dmenu create mode 100755 dmenu-scripts/dm-handler create mode 100755 dmenu-scripts/dm-kampv create mode 100755 dmenu-scripts/dm-mountcifs create mode 100755 dmenu-scripts/dm-pass create mode 100755 dmenu-scripts/dm-record create mode 100755 dmenu-scripts/dm-streamselect create mode 100755 dmenu-scripts/dm-unicode create mode 100644 dmenu.1 create mode 100644 dmenu.c create mode 100644 dmenu.o create mode 100755 dmenu_path create mode 100755 dmenu_run create mode 100644 drw.c create mode 100644 drw.h create mode 100644 drw.o create mode 100755 stest create mode 100644 stest.1 create mode 100644 stest.c create mode 100644 stest.o create mode 100644 util.c create mode 100644 util.h create mode 100644 util.o diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..c7c9a22 --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1,2 @@ +custom: ["https://lukesmith.xyz/donate.html"] +github: lukesmithxyz diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3afd28e --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2008 Sander van Dijk +© 2006-2007 Michał Janeczek +© 2007 Kris Maglione +© 2009 Gottox +© 2009 Markus Schnalke +© 2009 Evan Gates +© 2010-2012 Connor Lane Smith +© 2014-2020 Hiltjo Posthuma +© 2015-2019 Quentin Rameau + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cb6d970 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# dmenu - dynamic menu +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dmenu.c stest.c util.c +OBJ = $(SRC:.c=.o) + +all: options dmenu stest + +options: + @echo dmenu build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + @echo "CC = $(CC)" + +.c.o: + $(CC) -c $(CFLAGS) $< + +config.h: + cp config.def.h $@ + +$(OBJ): arg.h config.h config.mk drw.h + +dmenu: dmenu.o drw.o util.o + $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) + +stest: stest.o + $(CC) -o $@ stest.o $(LDFLAGS) + +clean: + rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz *.rej *.orig + +dist: clean + mkdir -p dmenu-$(VERSION) + cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ + drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ + dmenu-$(VERSION) + tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) + gzip dmenu-$(VERSION).tar + rm -rf dmenu-$(VERSION) + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dmenu dmenu_path dmenu_run stest dmenu-scripts/* $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run + chmod 755 $(DESTDIR)$(PREFIX)/bin/stest + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenuhandler + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenumountcifs + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenupass + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenuunicode + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenukampv + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenustreamselect + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ + $(DESTDIR)$(PREFIX)/bin/dmenu_path\ + $(DESTDIR)$(PREFIX)/bin/dmenu_run\ + $(DESTDIR)$(PREFIX)/bin/stest\ + $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ + $(DESTDIR)$(MANPREFIX)/man1/stest.1\ + $(DESTDIR)$(PREFIX)/bin/dmenuhandler\ + $(DESTDIR)$(PREFIX)/bin/dmenumountcifs\ + $(DESTDIR)$(PREFIX)/bin/dmenupass\ + $(DESTDIR)$(PREFIX)/bin/dmenuunicode\ + $(DESTDIR)$(PREFIX)/bin/dmenukampv\ + $(DESTDIR)$(PREFIX)/bin/dmenustreamselect + +.PHONY: all options clean dist install uninstall diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..e94e02b --- /dev/null +++ b/arg.h @@ -0,0 +1,49 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.h b/config.h new file mode 100644 index 0000000..2511ce0 --- /dev/null +++ b/config.h @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "monospace:size=10", + "NotoColorEmoji:pixelsize=8:antialias=true:autohint=true" +}; +static const unsigned int bgalpha = 0xe0; +static const unsigned int fgalpha = OPAQUE; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#666666" }, + [SchemeOut] = { "#000000", "#444444" }, +}; +static const unsigned int alphas[SchemeLast][2] = { + /* fgalpha bgalphga */ + [SchemeNorm] = { fgalpha, bgalpha }, + [SchemeSel] = { fgalpha, bgalpha }, + [SchemeOut] = { fgalpha, bgalpha }, +}; + +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..8531fb9 --- /dev/null +++ b/config.mk @@ -0,0 +1,31 @@ +# dmenu version +VERSION = 5.0 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = $(X11INC)/freetype2 + +# includes and libs +INCS = -I$(X11INC) -I$(FREETYPEINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lXrender + +# flags +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) +LDFLAGS = $(LIBS) + +# compiler and linker +CC = cc diff --git a/dmenu b/dmenu new file mode 100755 index 0000000000000000000000000000000000000000..8c4b2f32e6b71eb9d4707dbaa6d631a2ed54bb51 GIT binary patch literal 43224 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­vlK5drOu97twR0(rmfHpm/dev/null 2>&1 ;; + "mpv loop") setsid -f mpv -quiet --loop "$feed" >/dev/null 2>&1 ;; + "mpv float") setsid -f "$TERMINAL" -e mpv --geometry=+0-0 --autofit=30% --title="mpvfloat" "$feed" >/dev/null 2>&1 ;; + "queue yt-dlp") qndl "$feed" >/dev/null 2>&1 ;; + "queue yt-dlp audio") qndl "$feed" 'yt-dlp -o "%(title)s.%(ext)s" -f bestaudio --embed-metadata --restrict-filenames' ;; + "queue download") qndl "$feed" 'curl -LO' >/dev/null 2>&1 ;; + PDF) curl -sL "$feed" > "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && zathura "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;; + nsxiv) curl -sL "$feed" > "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && nsxiv -a "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;; + vim) curl -sL "$feed" > "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && setsid -f "$TERMINAL" -e "$EDITOR" "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;; + setbg) curl -L "$feed" > $XDG_CACHE_HOME/pic ; xwallpaper --zoom $XDG_CACHE_HOME/pic >/dev/null 2>&1 ;; + browser) setsid -f "$BROWSER" "$feed" >/dev/null 2>&1 ;; + lynx) lynx "$feed" >/dev/null 2>&1 ;; +esac diff --git a/dmenu-scripts/dm-kampv b/dmenu-scripts/dm-kampv new file mode 100755 index 0000000..e7fd782 --- /dev/null +++ b/dmenu-scripts/dm-kampv @@ -0,0 +1,3 @@ +#!/bin/sh + +killall mpv diff --git a/dmenu-scripts/dm-mountcifs b/dmenu-scripts/dm-mountcifs new file mode 100755 index 0000000..46c2b57 --- /dev/null +++ b/dmenu-scripts/dm-mountcifs @@ -0,0 +1,19 @@ +#!/bin/sh +# Gives a dmenu prompt to mount unmounted local NAS shares for read/write. +# Requirements - "%wheel ALL=(ALL) NOPASSWD: ALL" +# +# Browse for mDNS/DNS-SD services using the Avahi daemon... +srvname=$(avahi-browse _smb._tcp -t | awk '{print $4}' | dmenu -i -p "Which NAS?") || exit 1 +notify-send "Searching for network shares..." "Please wait..." +# Choose share disk... +share=$(smbclient -L "$srvname" -N | grep Disk | awk '{print $1}' | dmenu -i -p "Mount which share?") || exit 1 +# Format URL... +share2mnt=//"$srvname".local/"$share" + +sharemount() { + mounted=$(mount -v | grep "$share2mnt") || ([ ! -d /mnt/"$share" ] && sudo mkdir /mnt/"$share") + [ -z "$mounted" ] && sudo mount -t cifs "$share2mnt" -o user=nobody,password="",noperm /mnt/"$share" && notify-send "Netshare $share mounted" && exit 0 + notify-send "Netshare $share already mounted"; exit 1 +} + +sharemount diff --git a/dmenu-scripts/dm-pass b/dmenu-scripts/dm-pass new file mode 100755 index 0000000..2c14e6f --- /dev/null +++ b/dmenu-scripts/dm-pass @@ -0,0 +1,6 @@ +#!/bin/sh + +# This script is the SUDO_ASKPASS variable, meaning that it will be used as a +# password prompt if needed. + +dmenu -fn Monospace-18 -P -p "$1" <&- && echo diff --git a/dmenu-scripts/dm-record b/dmenu-scripts/dm-record new file mode 100755 index 0000000..ca6b011 --- /dev/null +++ b/dmenu-scripts/dm-record @@ -0,0 +1,123 @@ +#!/bin/sh + +# Usage: +# `$0`: Ask for recording type via dmenu +# `$0 screencast`: Record both audio and screen +# `$0 video`: Record only screen +# `$0 audio`: Record only audio +# `$0 kill`: Kill existing recording +# +# If there is already a running instance, user will be prompted to end it. + +getdim() { xrandr | grep -oP '(?<=current ).*(?=,)' | tr -d ' ' ;} + +updateicon() { \ + echo "$1" > /tmp/recordingicon + pkill -RTMIN+9 "${STATUSBAR:-dwmblocks}" + } + +killrecording() { + recpid="$(cat /tmp/recordingpid)" + kill -15 "$recpid" + rm -f /tmp/recordingpid + updateicon "" + pkill -RTMIN+9 "${STATUSBAR:-dwmblocks}" + } + +screencast() { \ + ffmpeg -y \ + -f x11grab \ + -framerate 30 \ + -s "$(getdim)" \ + -i "$DISPLAY" \ + -r 24 \ + -use_wallclock_as_timestamps 1 \ + -f alsa -thread_queue_size 1024 -i default \ + -c:v h264 \ + -crf 0 -preset ultrafast -c:a aac \ + "$HOME/screencast-$(date '+%y%m%d-%H%M-%S').mp4" & + echo $! > /tmp/recordingpid + updateicon "⏺️🎙️" + } + +video() { ffmpeg \ + -f x11grab \ + -framerate 30 \ + -s "$(getdim)" \ + -i "$DISPLAY" \ + -c:v libx264 -qp 0 -r 30 \ + "$HOME/video-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "⏺️" + } + +webcamhidef() { ffmpeg \ + -f v4l2 \ + -i /dev/video0 \ + -video_size 1920x1080 \ + "$HOME/webcam-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "🎥" + } + +webcam() { ffmpeg \ + -f v4l2 \ + -i /dev/video0 \ + -video_size 640x480 \ + "$HOME/webcam-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "🎥" + } + + +audio() { \ + ffmpeg \ + -f alsa -i default \ + -c:a flac \ + "$HOME/audio-$(date '+%y%m%d-%H%M-%S').flac" & + echo $! > /tmp/recordingpid + updateicon "🎙️" + } + +askrecording() { \ + choice=$(printf "screencast\\nvideo\\nvideo selected\\naudio\\nwebcam\\nwebcam (hi-def)" | dmenu -i -p "Select recording style:") + case "$choice" in + screencast) screencast;; + audio) audio;; + video) video;; + *selected) videoselected;; + webcam) webcam;; + "webcam (hi-def)") webcamhidef;; + esac + } + +asktoend() { \ + response=$(printf "No\\nYes" | dmenu -i -p "Recording still active. End recording?") && + [ "$response" = "Yes" ] && killrecording + } + +videoselected() +{ + slop -f "%x %y %w %h" > /tmp/slop + read -r X Y W H < /tmp/slop + rm /tmp/slop + + ffmpeg \ + -f x11grab \ + -framerate 30 \ + -video_size "$W"x"$H" \ + -i :0.0+"$X,$Y" \ + -c:v libx264 -qp 0 -r 30 \ + "$HOME/box-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "⏺️" +} + +case "$1" in + screencast) screencast;; + audio) audio;; + video) video;; + *selected) videoselected;; + kill) killrecording;; + *) ([ -f /tmp/recordingpid ] && asktoend && exit) || askrecording;; +esac diff --git a/dmenu-scripts/dm-streamselect b/dmenu-scripts/dm-streamselect new file mode 100755 index 0000000..c278485 --- /dev/null +++ b/dmenu-scripts/dm-streamselect @@ -0,0 +1,12 @@ +#!/bin/sh + +streamList=" +audio: WXXI Classical (Rochester Classical Music) link:https://22213.live.streamtheworld.com/WXXIFM.mp3 +video: Hasan Piker link:https://twitch.tv/hasanabi +" +selected=$(echo "$streamList" | grep -P "^$(echo "$streamList" | grep "https:" | sed 's/link:.*//g' | dmenu -i -p "Select a Stream" -l 20 | awk '{print $1}')\s") + +case "$(echo "$selected" | cut -c 1-5 )" in + video) echo "$selected" | sed 's/.*link://' | xargs -r mpv ;; + audio) echo "$selected" | sed 's/.*link://' | xargs -r mpv --no-video --terminal=no ;; +esac diff --git a/dmenu-scripts/dm-unicode b/dmenu-scripts/dm-unicode new file mode 100755 index 0000000..9ae9245 --- /dev/null +++ b/dmenu-scripts/dm-unicode @@ -0,0 +1,16 @@ +#!/bin/sh + +# Get user selection via dmenu from emoji file. +chosen=$(cut -d ';' -f1 ~/.local/share/larbs/chars/* | dmenu -i -l 30 | sed "s/ .*//") + +# Exit if none chosen. +[ -z "$chosen" ] && exit + +# If you run this command with an argument, it will automatically insert the +# character. Otherwise, show a message that the emoji has been copied. +if [ -n "$1" ]; then + xdotool type "$chosen" +else + printf "%s" "$chosen" | xclip -selection clipboard + notify-send "'$chosen' copied to clipboard." & +fi diff --git a/dmenu.1 b/dmenu.1 new file mode 100644 index 0000000..4c87074 --- /dev/null +++ b/dmenu.1 @@ -0,0 +1,200 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfirvP ] +.RB [ \-l +.IR lines ] +.RB [ \-m +.IR monitor ] +.RB [ \-p +.IR prompt ] +.RB [ \-fn +.IR font ] +.RB [ \-nb +.IR color ] +.RB [ \-nf +.IR color ] +.RB [ \-sb +.IR color ] +.RB [ \-sf +.IR color ] +.RB [ \-w +.IR windowid ] +.P +.BR dmenu_run " ..." +.SH DESCRIPTION +.B dmenu +is a dynamic menu for X, which reads a list of newline\-separated items from +stdin. When the user selects an item and presses Return, their choice is printed +to stdout and dmenu terminates. Entering text will narrow the items to those +matching the tokens in the input. +.P +.B dmenu_run +is a script used by +.IR dwm (1) +which lists programs in the user's $PATH and runs the result in their $SHELL. +.SH OPTIONS +.TP +.B \-b +dmenu appears at the bottom of the screen. +.TP +.B \-f +dmenu grabs the keyboard before reading stdin if not reading from a tty. This +is faster, but will lock up X until stdin reaches end\-of\-file. +.TP +.B \-i +dmenu matches menu items case insensitively. +.TP +.B \-P +dmenu will not directly display the keyboard input, but instead replace it with dots. All data from stdin will be ignored. +.TP +.B \-r +dmenu will reject any input which would result in no matching option left. +.TP +.BI \-l " lines" +dmenu lists items vertically, with the given number of lines. +.TP +.BI \-m " monitor" +dmenu is displayed on the monitor number supplied. Monitor numbers are starting +from 0. +.TP +.BI \-p " prompt" +defines the prompt to be displayed to the left of the input field. +.TP +.BI \-fn " font" +defines the font or font set used. +.TP +.BI \-nb " color" +defines the normal background color. +.IR #RGB , +.IR #RRGGBB , +and X color names are supported. +.TP +.BI \-nf " color" +defines the normal foreground color. +.TP +.BI \-sb " color" +defines the selected background color. +.TP +.BI \-sf " color" +defines the selected foreground color. +.TP +.B \-v +prints version information to stdout, then exits. +.TP +.BI \-w " windowid" +embed into windowid. +.SH USAGE +dmenu is completely controlled by the keyboard. Items are selected using the +arrow keys, page up, page down, home, and end. +.TP +.B Tab +Copy the selected item to the input field. +.TP +.B Return +Confirm selection. Prints the selected item to stdout and exits, returning +success. +.TP +.B Ctrl-Return +Confirm selection. Prints the selected item to stdout and continues. +.TP +.B Shift\-Return +Confirm input. Prints the input text to stdout and exits, returning success. +.TP +.B Escape +Exit without selecting an item, returning failure. +.TP +.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 +.TP +.B C\-b +Left +.TP +.B C\-c +Escape +.TP +.B C\-d +Delete +.TP +.B C\-e +End +.TP +.B C\-f +Right +.TP +.B C\-g +Escape +.TP +.B C\-h +Backspace +.TP +.B C\-i +Tab +.TP +.B C\-j +Return +.TP +.B C\-J +Shift-Return +.TP +.B C\-k +Delete line right +.TP +.B C\-m +Return +.TP +.B C\-M +Shift-Return +.TP +.B C\-n +Down +.TP +.B C\-p +Up +.TP +.B C\-u +Delete line left +.TP +.B C\-w +Delete word left +.TP +.B C\-y +Paste from primary X selection +.TP +.B C\-Y +Paste from X clipboard +.TP +.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 +.TP +.B M\-G +End +.TP +.B M\-h +Up +.TP +.B M\-j +Page down +.TP +.B M\-k +Page up +.TP +.B M\-l +Down +.SH SEE ALSO +.IR dwm (1), +.IR stest (1) diff --git a/dmenu.c b/dmenu.c new file mode 100644 index 0000000..34d0943 --- /dev/null +++ b/dmenu.c @@ -0,0 +1,997 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include +#include + +#include "drw.h" +#include "util.h" + +/* macros */ +#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))) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* define opaqueness */ +#define OPAQUE 0xFFU + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw, passwd = 0; +static int lrpad; /* sum of left and right padding */ +static int reject_no_match = 0; +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static int usergb = 0; +static Visual *visual; +static int depth; +static Colormap cmap; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; + + +static void +xinitvisual() +{ + XVisualInfo *infos; + XRenderPictFormat *fmt; + int nitems; + int i; + + XVisualInfo tpl = { + .screen = screen, + .depth = 32, + .class = TrueColor + }; + + long masks = VisualScreenMask | VisualDepthMask | VisualClassMask; + + infos = XGetVisualInfo(dpy, masks, &tpl, &nitems); + visual = NULL; + + for (i = 0; i < nitems; i++){ + fmt = XRenderFindVisualFormat(dpy, infos[i].visual); + if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) { + visual = infos[i].visual; + depth = infos[i].depth; + cmap = XCreateColormap(dpy, root, visual, AllocNone); + usergb = 1; + break; + } + } + + XFree(infos); + + if (! visual) { + visual = DefaultVisual(dpy, screen); + depth = DefaultDepth(dpy, screen); + cmap = DefaultColormap(dpy, screen); + } +} + +static void +appenditem(struct item *item, struct item **list, struct item **last) +{ + if (*last) + (*last)->right = item; + else + *list = item; + + item->left = *last; + item->right = NULL; + *last = item; +} + +static void +calcoffsets(void) +{ + int i, n; + + if (lines > 0) + n = lines * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ + for (i = 0, next = curr; next; next = next->right) + if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) + break; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static char * +cistrstr(const char *s, const char *sub) +{ + size_t len; + + for (len = strlen(sub); *s; s++) + if (!strncasecmp(s, sub, len)) + return (char *)s; + 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 +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, w; + char *censort; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt && *prompt) { + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); + if (passwd) { + censort = ecalloc(1, sizeof(text)); + memset(censort, '.', strlen(text)); + drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0); + free(censort); + } else drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + } + + if (lines > 0) { + /* draw vertical list */ + for (item = curr; item != next; item = item->right) + drawitem(item, x, y += bh, mw - x); + } else if (matches) { + /* draw horizontal list */ + x += inputw; + w = TEXTW("<"); + if (curr->left) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">"))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + } + } + 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 +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) + return; + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } + die("cannot grab keyboard"); +} + +static void +match(void) +{ + static char **tokv = NULL; + static int tokn = 0; + + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + + strcpy(buf, text); + /* separate input text into tokens to be matched individually */ + for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) + if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) + die("cannot realloc %u bytes:", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + for (item = items; item && item->text; item++) { + for (i = 0; i < tokc; i++) + if (!fstrstr(item->text, tokv[i])) + break; + if (i != tokc) /* not all tokens match */ + continue; + /* exact matches go first, then prefixes, then substrings */ + if (!tokc || !fstrncmp(text, item->text, textsize)) + appenditem(item, &matches, &matchend); + else if (!fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lprefix, &prefixend); + else + appenditem(item, &lsubstr, &substrend); + } + if (lprefix) { + if (matches) { + matchend->right = lprefix; + lprefix->left = matchend; + } else + matches = lprefix; + matchend = prefixend; + } + if (lsubstr) { + if (matches) { + matchend->right = lsubstr; + lsubstr->left = matchend; + } else + matches = lsubstr; + matchend = substrend; + } + curr = sel = matches; + calcoffsets(); +} + +static void +insert(const char *str, ssize_t n) +{ + if (strlen(text) + n > sizeof text - 1) + return; + + static char last[BUFSIZ] = ""; + if(reject_no_match) { + /* store last text value in case we need to revert it */ + memcpy(last, text, BUFSIZ); + } + + /* move existing text out of the way, insert new text, and update cursor */ + memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + if (n > 0) + memcpy(&text[cursor], str, n); + cursor += n; + match(); + + if(!matches && reject_no_match) { + /* revert to last text value if theres no match */ + memcpy(text, last, BUFSIZ); + cursor -= n; + match(); + } +} + +static size_t +nextrune(int inc) +{ + ssize_t n; + + /* return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + 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 +keypress(XKeyEvent *ev) +{ + char buf[32]; + int len; + KeySym ksym; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + break; + } + + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: /* fallthrough */ + case XK_J: /* fallthrough */ + case XK_m: /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: ksym = XK_Down; break; + case XK_p: ksym = XK_Up; break; + + case XK_k: /* delete right */ + text[cursor] = '\0'; + match(); + break; + case XK_u: /* delete left */ + insert(NULL, 0 - cursor); + break; + case XK_w: /* delete word */ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + break; + case XK_y: /* paste selection */ + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_Left: + movewordedge(-1); + goto draw; + case XK_Right: + movewordedge(+1); + goto draw; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + cleanup(); + exit(1); + default: + return; + } + } else if (ev->state & Mod1Mask) { + 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_End; break; + case XK_h: ksym = XK_Up; break; + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; + default: + return; + } + } + + switch(ksym) { + default: +insert: + if (!iscntrl(*buf)) + insert(buf, len); + break; + case XK_Delete: + if (text[cursor] == '\0') + return; + cursor = nextrune(+1); + /* fallthrough */ + case XK_BackSpace: + if (cursor == 0) + return; + insert(NULL, nextrune(-1) - cursor); + break; + case XK_End: + if (text[cursor] != '\0') { + cursor = strlen(text); + break; + } + if (next) { + /* jump to end of list and position items in reverse */ + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + } + sel = matchend; + break; + case XK_Escape: + cleanup(); + exit(1); + case XK_Home: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Up: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_Next: + if (!next) + return; + sel = curr = next; + calcoffsets(); + break; + case XK_Prior: + if (!prev) + return; + sel = curr = prev; + calcoffsets(); + break; + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Right: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_Tab: + if (!sel) + return; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text); + match(); + break; + } + +draw: + drawmenu(); +} + +static void +buttonpress(XEvent *e) +{ + struct item *item; + XButtonPressedEvent *ev = &e->xbutton; + int x = 0, y = 0, h = bh, w; + + if (ev->window != win) + return; + + /* right-click: exit */ + if (ev->button == Button3) + exit(1); + + if (prompt && *prompt) + x += promptw; + + /* input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + + /* left-click on input: clear input, + * NOTE: if there is no left-arrow the space for < is reserved so + * add that to the input width */ + if (ev->button == Button1 && + ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + + ((!prev || !curr->left) ? TEXTW("<") : 0)) || + (lines > 0 && ev->y >= y && ev->y <= y + h))) { + insert(NULL, -cursor); + drawmenu(); + return; + } + /* middle-mouse click: paste selection */ + if (ev->button == Button2) { + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + drawmenu(); + return; + } + /* scroll up */ + if (ev->button == Button4 && prev) { + sel = curr = prev; + calcoffsets(); + drawmenu(); + return; + } + /* scroll down */ + if (ev->button == Button5 && next) { + sel = curr = next; + calcoffsets(); + drawmenu(); + return; + } + if (ev->button != Button1) + return; + if (ev->state & ~ControlMask) + return; + if (lines > 0) { + /* vertical list: (ctrl)left-click on item */ + w = mw - x; + for (item = curr; item != next; item = item->right) { + y += h; + if (ev->y >= y && ev->y <= (y + h)) { + puts(item->text); + if (!(ev->state & ControlMask)) + exit(0); + sel = item; + if (sel) { + sel->out = 1; + drawmenu(); + } + return; + } + } + } else if (matches) { + /* left-click on left arrow */ + x += inputw; + w = TEXTW("<"); + if (prev && curr->left) { + if (ev->x >= x && ev->x <= x + w) { + sel = curr = prev; + calcoffsets(); + drawmenu(); + return; + } + } + /* horizontal list: (ctrl)left-click on item */ + for (item = curr; item != next; item = item->right) { + x += w; + w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); + if (ev->x >= x && ev->x <= x + w) { + puts(item->text); + if (!(ev->state & ControlMask)) + exit(0); + sel = item; + if (sel) { + sel->out = 1; + drawmenu(); + } + return; + } + } + /* left-click on right arrow */ + w = TEXTW(">"); + x = mw - w; + if (next && ev->x >= x && ev->x <= x + w) { + sel = curr = next; + calcoffsets(); + drawmenu(); + return; + } + } +} + +static void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* we have been given the current selection, now insert it into input */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +static void +readstdin(void) +{ + char buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 0; + + if(passwd){ + inputw = lines = 0; + return; + } + + /* read each line from stdin and add it to the item list */ + for (i = 0; fgets(buf, sizeof buf, stdin); i++) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + die("cannot realloc %u bytes:", size); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(items[i].text = strdup(buf))) + die("cannot strdup %u bytes:", strlen(buf) + 1); + items[i].out = 0; + drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); + if (tmpmax > inputw) { + inputw = tmpmax; + imax = i; + } + } + if (items) + items[i].text = NULL; + inputw = items ? TEXTW(items[imax].text) : 0; + lines = MIN(lines, i); +} + +static void +run(void) +{ + XEvent ev; + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case ButtonPress: + buttonpress(&ev); + break; + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) + XRaiseWindow(dpy, win); + break; + } + } +} + +static void +setup(void) +{ + int x, y, i, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; +#endif + /* init appearance */ + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], alphas[j], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + lines = MAX(lines, 0); + mh = (lines + 1) * bh; +#ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i])) + break; + + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = MIN(inputw, mw/3); + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.border_pixel = 0; + swa.colormap = cmap; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | + ButtonPressMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + depth, InputOutput, visual, + CWOverrideRedirect | CWBackPixel | CWColormap | CWEventMask | CWBorderPixel, &swa); + XSetClassHint(dpy, win, &ch); + + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + if (embed) { + 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); + drawmenu(); +} + +static void +usage(void) +{ + fputs("usage: dmenu [-bfiPrv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); +} + +void +read_Xresources(void) { + XrmInitialize(); + + char* xrm; + if ((xrm = XResourceManagerString(drw->dpy))) { + char *type; + XrmDatabase xdb = XrmGetStringDatabase(xrm); + XrmValue xval; + + if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval) == True) /* font or font set */ + fonts[0] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.color0", "*", &type, &xval) == True) /* normal background color */ + colors[SchemeNorm][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.color4", "*", &type, &xval) == True) /* normal foreground color */ + colors[SchemeNorm][ColFg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.color4", "*", &type, &xval) == True) /* selected background color */ + colors[SchemeSel][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.color0", "*", &type, &xval) == True) /* selected foreground color */ + colors[SchemeSel][ColFg] = strdup(xval.addr); + + XrmDestroyDatabase(xdb); + } +} + +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + xinitvisual(); + drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); + read_Xresources(); + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (!strcmp(argv[i], "-P")) /* is the input a password */ + passwd = 1; + else if (!strcmp(argv[i], "-r")) /* reject input which results in no match */ + reject_no_match = 1; + else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colors[SchemeNorm][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else + usage(); + + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif + + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); + } else { + readstdin(); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} + diff --git a/dmenu.o b/dmenu.o new file mode 100644 index 0000000000000000000000000000000000000000..c63a80d8ccbfe5ac47ab044b9f698737d7b95654 GIT binary patch literal 38976 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

mBQFY7gcUIC)-vR>aK&T+ok1p%ad#kqaY zQasl+a%ZyQqljOi_{qdeiK9QOVd45-qV(KP7Awx@MT6q(=Ne%j-}SToo0OjYxkYid zbC0ki{d`#I_fx(8sQ3Zm&nV9JUl8`M7JmLs>4%V=e<{xK|5$Ohe^A(${vTF)j`QFF z-~=S@4_xjsigWx=RGiB_O>w?{jUtZiF5Bg7r61s;7x48e#sBCU?35_JkN6D54-lWL zIJbMH;_Uwt#SfF6dg8j@rj&jU>DLMU8b|a)!R37HR%M6de6Ql%UK|DO@|Wt?{l zevR0aK+h9zK@-Al%DH# zvEpo}TygFv^MrkQ9#twm$0w;c+fNHS@_hfX(sTd+rQ&x|9Db|#t;8Qt`~dNdinE`8 zQ=HGQeTuXHoq|twdiwLnf@9nB`S>MqWU_s`l%CrqdmsoQaX;bu9c`ikYQfQ0ZpQ}2x!kLX>v*;({Q(z@ zLAqXXw(}FkIS#k^*uTq1f4|~vf0K`$ElSVj;?Gzyal5nsFZH?=W@R%?5_qp+`glI^b-{4`bHGzet5p(?9XiC=)3HPl}f+g z1p%ab#kt&NiqCb8+*z$SmwTh)HKe~yah~7aNgVxbf`#LApVD*u9};?bo%&Cu&!uuV z`RKPQ{V3A!^3lJb^jzPUg}zz%zenl0zV8Tqi_mxY=npA=nEVV3hRl$#Kc5c^$88jG zjORMR&ldcLf)@)rm?z7H4@j3Pe)v?&=PAzm8ev~PPfI90+qqWoD41sd*9klFKIJ;0 zM?cxlZ9*^Wwb4hvMd;=H>d#8Q&9(J?Me)aozoqyl;vXo^?RA(q`Yy{onZ7c};~Up2 zU+@Uz;rPrJ9Br{bNyWL`D+Nb8mGZkn1vlVCi zzaWnOvWb*s<0#NmkBQI&k=UeKHI4m zdTD31;L=XBvNIH>S5ErJcJ4mv$adc4&BU{vh<%gI)IX8NucGl@{08YlZ$p!Pg2t z8n!WIz;Y2RY=105NEr7UVPSqQLP)4z3JddMgpiPz!onP{(U{Oq4J^zrMhFS{O|USZ zix3iW{QDQoD-l9M`*!yD}VYvL3IYn2Z+I%<-8dCT-_hi@W(}t_2u#Osrq; znqB8EyXxjO#2;3?iuhB+(Vv@PVSjcIXZx&wOXzj|86~^q&*gaX3C;4{*6$ zFZo_o#`!YRBg5b9u>T8`ov3T=>i41CUaN(jp9woZS9Um_zgC>>+@m;`%iquDdQBvM z9u?(o5an)EcE*z)|AGc4tnV$baD87^dMbDX~~IBxR!c>!^Z!_Q&iILr`wJQj1|1Jb2J|0CFEeVx$H6nve~ z<8QuLkKY5wgsU=GnEy=ZZxx(>ABu-(3I2f4OZ$H$jw^h1#`d2R{1U-m^0D)t&|fO_ z|MJm)PW%ke;@HLI9u{^Yf@jiuJv@vYV~uI3;N`H-c1|FUI$7W0LXXGzKD|Kj%Y>at zK6dc?-k8uIwDY11ACTq=j>pCs>NxoQZA{2!!@_>n`Pg42_^*WiT44vv&354f(vJn7 z1N&@$gRmpxaJS&nzI-oC5@Y{Q!aiP`*gk&W8w&M88Vc4*f*Q}Fr1&UoUul5vX)J=)=NX9&JP@JoH{ zTp{#mhv!Qv!7GHFD}C(zNa)cH&zCj`zEIftrH`G5g&sFQbKwIL|L!ycwvqGrhTr$b zgdEG|c6p6Bu5N>cpvEH+~jh{g#IqU(br}dJ|Nvo9GSHLu+pz`Q4N2; zMRAV9cEx$z`-|cqxyFq1n&SJ3^Lumb3+Ph~@qa3P74gp$=XicY9Bupt7LMo9{b2_Z z+h_f7;;8=tEUeEHdK&65yHhN<^mB=@Bj;nweDv!SXMb)JcJ37Z==ayGe?sVgBJ>xC z_kHL;ef_g*vEb5weow07a~mc{p57T*`ET%x!uoGoZI(O#mBo)IHb7Z6J3MhFQ3^p_x~vIwZzf)yJ6w+;YOur ze{NTNF4?(9*!iun^PtkRosB|&kI+9Y^j8S}qR`8J`-;$G9oV0L3cgs_`9Sb$!M_rG ziQrwt(f02I?>`83AaOjo+@lod`VLjR%r)7a6Nsbzdtu@D>-Tz}l75V`!~NlG#kpL5 ze~0$(6ZU5)J=?iVakjIVING^W*tuTmISv~XXFGQYJJO%u2`*;Xi6roX@Y#f){~~PR=Pg%-}PWS+`jz=g9sAZ zc>oqJ_b%eBzuAR@knUIfuxqfx@0HMwyzhQq=+PEGe|%Y#TML}a-6Qm9i`(m6q5r+m zf2s7`jscqRqm2iJ{#@c*FSZ|7e25E~AT3v%$L-aMXS+u3G%L>YuIm-&_XYPWzK`r| zQv3k%-HP8w{0+tLB>th|1Beek$}x%K5F>t!;8Q`t?Q#-vjHkQ~o-XuwjIV?Fia$s8 zOBCn$mny!B^ota4CSI#JxBCj>=*vT}u>Y%t9_!5K#iN2_9*)nS6z6zuQ~XvJGC?{( z9Bs({9|)Sm-QzHyM<*)I{b7XSG1ullQ*hZXX9(qd#+9_<*!WalS6U zqd5EdzT#}>GsU?bza)-jUJDEN&we>r`jPXoVS=OoJb%b1j&>e~h3zy5eG%}9uwYsv zIOaL*B8C4j;Ap!>+sL1D?fxpoS>GZ!=HdH@8wHnkejzyOHymZ{-=jGD|DfPSAm!iX zZ6c1g8(?AmpM_r9|Eu8ApSKm~dhJ)7(Vz9OU}{pF=VQMVya;rMQ4HxL z#YfQdz+uPQ<7ivP|5U{}{^JD4JnZKL!KIyu51%2pv@>6E?uSbSm-ZVKXFpdHM{S+( zGp+Q$aN!`NA1nTtYq0Zk#d*BCTXEJupg50vj|h(S|}vzbVdk-uB@iD!z&QIjA_tp&vciM_**S9Ig1XWalKs+0JOidH!~;505F%cB*}N zgW?>AYZT}Bw<&%r`TtYJS%0_UZ2tkp+0Pezc&Fk#zI~!N_w$V7An=ed{8wu$Ma``W8C=q_#45o>{Tv&K-#GIT-RXd8O4*t_Yp@MkHW(K4?f=JLoTn6 zuHe%Dk;J)uc|1Hz=x-7BFA{nzgZ;cjarSeLu=AL(vqEtBy(5sAg#L_&g}(=kWnp6e z3Ej`rW6o|qh3;c%)y=;mJxzbQ`8UL=`*-sUGBC^*+s*qEKUwip$grz;ko38VA47bE z;-ApGaFpUFl75Whrx3?jVdD5SQhW<6?&jYmJu#jhnhuPV;>A#W)DBhv3x{5s-y{3*ClpQM^JO9NEX8^J;lKC7dLDm#d-X372iX4auw(MCB9E$|5uRxQA!^iYU6@`zZw(krxWMD=ga&f z;_~m+V7*4tykVlU^Bd9^DgGW@;^B(R%V$Odjeu3ir-spbC`R}q1p)Y5!9eyrcE;z@~6quh_Sqpzm&B=#9 zy=B*nd8;vAQ;~1}4qSc}{J}LRe_ucdp8YZ3my82{1AO0Jh(dLiZ(f$>6seJg;iT1C6S>y4=^B2KiJBynd z{2zU|kQhl(xH>>8c$tf2U z#KZsq|I1lSKXg(_mFbpC@UJk_&aEHJ+ga8 U#t+T+mEZix2afUYVEpp`Pnh5KDgXcg literal 0 HcmV?d00001 diff --git a/dmenu_path b/dmenu_path new file mode 100755 index 0000000..3a7cda7 --- /dev/null +++ b/dmenu_path @@ -0,0 +1,13 @@ +#!/bin/sh + +cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" +cache="$cachedir/dmenu_run" + +[ ! -e "$cachedir" ] && mkdir -p "$cachedir" + +IFS=: +if stest -dqr -n "$cache" $PATH; then + stest -flx $PATH | sort -u | tee "$cache" +else + cat "$cache" +fi diff --git a/dmenu_run b/dmenu_run new file mode 100755 index 0000000..834ede5 --- /dev/null +++ b/dmenu_run @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & diff --git a/drw.c b/drw.c new file mode 100644 index 0000000..ac1d696 --- /dev/null +++ b/drw.c @@ -0,0 +1,423 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +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)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *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; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + 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); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->visual = visual; + drw->depth = depth; + drw->cmap = cmap; + drw->drawable = XCreatePixmap(dpy, root, w, h, depth); + drw->gc = XCreateGC(dpy, drw->drawable, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, drw->depth); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + 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, unsigned int alpha) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap, + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); + dest->pixel = (dest->pixel & 0x00FFFFFFFU) | alpha << 24; +} + +/* 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[], const unsigned int alphas[], 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], alphas[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +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; + unsigned int ew; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + size_t i, len; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); + /* shorten text if necessary */ + for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + + if (len) { + memcpy(buf, utf8str, len); + buf[len] = '\0'; + if (len < utf8strlen) + for (i = len; i && i > len - 3; buf[--i] = '.') + ; /* NOP */ + + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, len); + } + x += ew; + w -= ew; + } + } + + if (!*text) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +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); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/drw.h b/drw.h new file mode 100644 index 0000000..4f66f0d --- /dev/null +++ b/drw.h @@ -0,0 +1,60 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Visual *visual; + unsigned int depth; + Colormap cmap; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +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 */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/drw.o b/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..5e706f31689066e88caaf0f0bacb9a5f9bdb5757 GIT binary patch literal 10208 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 literal 0 HcmV?d00001 diff --git a/stest b/stest new file mode 100755 index 0000000000000000000000000000000000000000..d5b85fb4ff3dbaab6200a43d0c128f686bd004ed GIT binary patch literal 16408 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 literal 0 HcmV?d00001 diff --git a/stest.1 b/stest.1 new file mode 100644 index 0000000..2667d8a --- /dev/null +++ b/stest.1 @@ -0,0 +1,90 @@ +.TH STEST 1 dmenu\-VERSION +.SH NAME +stest \- filter a list of files by properties +.SH SYNOPSIS +.B stest +.RB [ -abcdefghlpqrsuwx ] +.RB [ -n +.IR file ] +.RB [ -o +.IR file ] +.RI [ file ...] +.SH DESCRIPTION +.B stest +takes a list of files and filters by the files' properties, analogous to +.IR test (1). +Files which pass all tests are printed to stdout. If no files are given, stest +reads files from stdin. +.SH OPTIONS +.TP +.B \-a +Test hidden files. +.TP +.B \-b +Test that files are block specials. +.TP +.B \-c +Test that files are character specials. +.TP +.B \-d +Test that files are directories. +.TP +.B \-e +Test that files exist. +.TP +.B \-f +Test that files are regular files. +.TP +.B \-g +Test that files have their set-group-ID flag set. +.TP +.B \-h +Test that files are symbolic links. +.TP +.B \-l +Test the contents of a directory given as an argument. +.TP +.BI \-n " file" +Test that files are newer than +.IR file . +.TP +.BI \-o " file" +Test that files are older than +.IR file . +.TP +.B \-p +Test that files are named pipes. +.TP +.B \-q +No files are printed, only the exit status is returned. +.TP +.B \-r +Test that files are readable. +.TP +.B \-s +Test that files are not empty. +.TP +.B \-u +Test that files have their set-user-ID flag set. +.TP +.B \-v +Invert the sense of tests, only failing files pass. +.TP +.B \-w +Test that files are writable. +.TP +.B \-x +Test that files are executable. +.SH EXIT STATUS +.TP +.B 0 +At least one file passed all tests. +.TP +.B 1 +No files passed all tests. +.TP +.B 2 +An error occurred. +.SH SEE ALSO +.IR dmenu (1), +.IR test (1) diff --git a/stest.c b/stest.c new file mode 100644 index 0000000..7a7b0bc --- /dev/null +++ b/stest.c @@ -0,0 +1,109 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include +#include + +#include "arg.h" +char *argv0; + +#define FLAG(x) (flag[(x)-'a']) + +static void test(const char *, const char *); +static void usage(void); + +static int match = 0; +static int flag[26]; +static struct stat old, new; + +static void +test(const char *path, const char *name) +{ + struct stat st, ln; + + if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ + && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ + && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ + && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ + && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ + && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ + && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ + && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ + && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ + && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ + && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ + && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ + && (!FLAG('s') || st.st_size > 0) /* not empty */ + && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ + && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ + && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */ + if (FLAG('q')) + exit(0); + match = 1; + puts(name); + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] " + "[-n file] [-o file] [file...]\n", argv0); + exit(2); /* like test(1) return > 1 on error */ +} + +int +main(int argc, char *argv[]) +{ + struct dirent *d; + char path[PATH_MAX], *line = NULL, *file; + size_t linesiz = 0; + ssize_t n; + DIR *dir; + int r; + + ARGBEGIN { + case 'n': /* newer than file */ + case 'o': /* older than file */ + file = EARGF(usage()); + if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) + perror(file); + break; + default: + /* miscellaneous operators */ + if (strchr("abcdefghlpqrsuvwx", ARGC())) + FLAG(ARGC()) = 1; + else + usage(); /* unknown flag */ + } ARGEND; + + if (!argc) { + /* read list from stdin */ + while ((n = getline(&line, &linesiz, stdin)) > 0) { + if (n && line[n - 1] == '\n') + line[n - 1] = '\0'; + test(line, line); + } + free(line); + } else { + for (; argc; argc--, argv++) { + if (FLAG('l') && (dir = opendir(*argv))) { + /* test directory contents */ + while ((d = readdir(dir))) { + r = snprintf(path, sizeof path, "%s/%s", + *argv, d->d_name); + if (r >= 0 && (size_t)r < sizeof path) + test(path, d->d_name); + } + closedir(dir); + } else { + test(*argv, *argv); + } + } + } + return match ? 0 : 1; +} diff --git a/stest.o b/stest.o new file mode 100644 index 0000000000000000000000000000000000000000..8a784affdaf889c703c02aacecb7b77ecedaa13a GIT binary patch literal 5256 zcmbW4Z){sv6~M3Uv^H($=eAi1tOKueAQR2Q&dQWdD~lUvxrTI9lx~xn)N$>XII3d@ z`@JPa>zc`SndiByps*o?x(Ox?flB#b3Dmi2*CMnJgHMwtjeV#D!D#J%)+J->U-x%ZuW-X9+un|R3WaxoGY`#fuFL>YTJ&>oN2!x0u>8r#cjyHMrz z&tVs7@Y?M-_zV8L>ZZ~DeZ2ZF{?y-wA$FSAw)YRf46pt;Gys5C|H12K$Y=Xh8m2eW zSh)TLYZ&ow3lgcH+aE?WH!|GF5+Dn(CFd2q&afF0?1x+X*$dobSQd zt9vo8t~?Kxy!1q9m}w_!Z7io`U9$;?++aPV`Dc3U0x{P({HaS=HwUIJLVQK6Vsh5F zKiFRk9vDm>N~MjN*-UoftA(QV_~OzOz+7-D>Vvpi^eeIYY->NS=Y0F1ga;tcP{Pg*d9@ts0}yFI@?eF6O=4R9X40#10G@tVf#M_lphgDWFY*|>T=?^}5f>#aT+3QRbUim}R7 zYe76Mc>M`X-P=ys8Y-2>lUsQ`=4~wxud_UtAtyGj-n4C+m!0lO4hgo0@#<^fV>n$<6!$XZtRGkXl)zTPSJOle8 z(tHWm{5X0?NzE5OXk2-bTT>>Js3?~WaFcw_Xh{YAKrgYFtS4* z(j&p4@X(I%&PbTyZ5o7z)dL31&0Gy+Brc(2qEULCj=8?}W#(E6xbE2C^__%CSdncN zbDzdO>JJP+7Zz+B?^R&O-BEvE$@2yO8*b0A+usK$>i0noV0u#69z@&S{=jqY-TuB4 zp56Xny(jJ;cy`U0KYV=cn18tB9rwQz^xW(A4@dpssDA)Vz!Y3|W32xrcpW|Ly;dh1 zPscYAV;(z8w|aa8RLIpTw67U7I6<3}ZeKSr%$p11ObeF`=qMk9hCGN0$VsPTcS4W6 zA#~z*3I00~M?NbLTPv*FowE=7+0ceC+HPxW;O%>>LVX2i(U9*T{!NeQ=CV;@To5RP#0bVzDdfPU3F| zDuZ1b*sNh@=W+&{DHsOJFBrM>T!9sgWE%TaHeWPwP|S(-;EM3xbp#&~erW%OCqBx0 ziQ||!J+IIn?@i)(-=SbUtS{nsBLqRd5gPG52tkl z{%fJ%6ulb%cH9Vv%0q(FKF{;P**!k)2f2ZIW zXM@DIN%nXzll`=0f1AX!692fwsorqiJ0$)+$sX6H=fV%9b@7^L-JeJt-`&JtmG~zl zPIZXuep2FZN$cXde4TXM1PIy(p;7!^!LgI~xvdLV>+Pb5FV~M;C%6_0#dkEuc8CFS z`retA_&PGiUN0V*HR~bC$kPfY(aFJR;nUOk$>xoHyis{h#9?_+jeQ zFRl?_C^?aTM{{gxj6K#Gp5+KMSw=+CI37L-)-bL^-&J^TpnM7%a$$zZmNUw_#Kkd_ zYJdM>Ox3?3{HZ?4AxHz!<98y)98x&>KcaA2pL%@G;8@My7O^4v4x=#SNBRg1I_2+a zOc=<3xDHft4K;uJfMLuqG+9jXsZ7r+nxplppM(h-lMVJKg}=;jtlDOQd7-JtMgHo( zX#W@sr3?*Ys{ReJ{wruCiRzEvv7PdF79aW`-0AH4!|y5br~2!(Ho9cQf%b`W#BVfg ksz1Je)Y=2Ymc@(dH>gOW<`3VAo%}x#`#&N1;!ySf50-Fd9{>OV literal 0 HcmV?d00001 diff --git a/util.c b/util.c new file mode 100644 index 0000000..fe044fc --- /dev/null +++ b/util.c @@ -0,0 +1,35 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + 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); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/util.o b/util.o new file mode 100644 index 0000000000000000000000000000000000000000..26ab857d2a56ff49969dd1704810e8d70d6b9cca GIT binary patch literal 2224 zcmbtUO=uHQ5T0$TjWtbT?V(Z+E3A@0U6N3trYg}!yGoF%6}1;llZ_4L&&{T!Dj2aI zLJ0KW(SrvM3LXl26dLs4)uSitQK*M{5R?jWX7|1Jv1~3n@R*rzzL_`s=DmG*HhHc` zl7Nc@r(nn9C_s4EjvcqmIQU=?u4s)d?a}wI2x$#dna~=uN?dCs6)o9FC^Jd4t}nOE zm_L+IZuvt=rR)zqRq9Chhn^`bmVTi;xAbde)zWX3wx!=GpHOGCX5Zl;8X6W1M%r5Q zBs%DQ(OS#OCJT&ctyv|YwGvABV?qgd5Jo*5W?}go8c?4Et$B-`kB(ckce}fts9jCC94s`5dPv|~ zy&<*_SAcOuMc?6uAL@b!yWnaUoTlzpALnHC$@{a;39-p8e5jhayqX3zITeOXPKSz_ z(G3F@vt=V!G_&X%dA$hPa@9;j8ABxl^jgjY-M$!Dm!xVdoV$Go{&Z}4#c;X{yK!;+ z4a1%M_X59P;C~iynh~$FF5q;xIKCm^BLAm=(<|WoEdlomct2er6za1V7w3B!PTxg~ z<1*SL#HYpO1T#3!=L2eGsbHq&QJaRX7r15U`IKtvH51fK%1nVeU#Wmk2zcz#Q>dOoD-O?(P5)_Si)Yk$+X|6{hy%t5r#85L6%&0Zp3gWJcc;c{MY;l z=2FclE>ZaYyk8#U`*R(~0NL~p6=jYQ_`i_Hcplg1F~DukA-(7Zx^QnGM(<3#tLQ?F z