Initial commit for public dots

This commit is contained in:
2023-07-08 15:20:41 +03:00
commit 2c53e85568
74 changed files with 5096 additions and 0 deletions

View File

@ -0,0 +1,46 @@
# monokai colors
colors:
primary:
background: "0x1c1c1c"
foreground: "0xe2e2e3"
normal:
black: "0x555555"
red: "0xfc5d7c"
green: "0x9ed072"
yellow: "0xe7c664"
blue: "0x6a7ec8"
magenta: "0xb39df3"
cyan: "0x76cce0"
white: "0xe2e2e3"
bright:
black: "0x666666"
red: "0xfc5d7c"
green: "0x9ed072"
yellow: "0xe7c664"
blue: "0x6a7ec8"
magenta: "0xb39df3"
cyan: "0x66d9ef"
white: "0xe2e2e3"
font:
normal:
family: mononoki
style: Regular
bold:
family: mononoki
style: Bold
italic:
family: mononoki
style: Italic
bold_italic:
family: mononoki
style: Bold Italic
size: 15.0
background_opacity: 0.8

60
.config/dunst/dunstrc Normal file
View File

@ -0,0 +1,60 @@
[global]
monitor = 0
follow = mouse
geometry = "500x20-12+53"
indicate_hidden = yes
shrink = no
transparency = 20
notification_height = 0
separator_height = 2
padding = 12
horizontal_padding = 12
frame_width = 2
frame_color = "#817f7f"
separator_color = frame
sort = yes
idle_threshold = 120
font = mononoki 15
line_height = 0
markup = full
format = "<b>%s</b>\n%b"
alignment = left
vertical_alignment = center
show_age_threshold = 60
word_wrap = yes
ellipsize = middle
ignore_newline = no
stack_duplicates = true
hide_duplicate_count = false
show_indicators = yes
icon_position = left
sticky_history = yes
history_length = 20
dmenu = /usr/bin/dmenu -p dunst:
browser = /usr/bin/firefox -new-tab
always_run_script = true
title = Dunst
class = Dunst
startup_notification = false
verbosity = mesg
corner_radius = 0
ignore_dbusclose = false
force_xinerama = false
mouse_left_click = close_current
mouse_middle_click = do_action, close_current
mouse_right_click = close_all
[experimental]
per_monitor_dpi = false
[urgency_low]
background = "#21242b"
foreground = "#F8F8F2"
timeout = 10
[urgency_normal]
background = "#21242b"
foreground = "#F8F8F2"
timeout = 5
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
frame_color = "#ff0000"
timeout = 0

222
.config/herbstluftwm/autostart Executable file
View File

@ -0,0 +1,222 @@
#!/usr/bin/env bash
#
# source colors
#
. "${HOME}/.cache/wal/colors.sh"
#
# initial settings
#
# hc is shorter than herbstclient :)
hc() {
herbstclient "$@"
}
# set mod keys
C=Control
S=Shift
M=Mod4
A=Mod1
# reset hooks and bindings
hc emit_hook reload
hc keyunbind --all
scripts="$XDG_CONFIG_HOME/herbstluftwm/scripts"
#
# variables
#
terminal=kitty
launcher="dmenu_run -nb $color0 -nf $color15 -sb $color1 -sf $color15"
file_manager=thunar
browser=firefox
calculator=dcalc
music_player="$terminal -e ncmpcpp"
feed_reader="$terminal -e newsboat"
screen_locker="i3lock -c 000000"
#
# movement and control
#
# basics
hc keybind $M-w close_and_remove
hc keybind $M-e close
hc keybind $M-t reload
hc keybind $M-q spawn sh $scripts/exitmenu
# focus
hc keybind $M-h focus left
hc keybind $M-j focus down
hc keybind $M-k focus up
hc keybind $M-l focus right
# shift
hc keybind $M-$S-h shift left
hc keybind $M-$S-j shift down
hc keybind $M-$S-k shift up
hc keybind $M-$S-l shift right
# split focused frame
hc keybind $M-y chain , split left 0.5 , focus left
hc keybind $M-u chain , split bottom 0.5 , focus down
hc keybind $M-i chain , split top 0.5 , focus up
hc keybind $M-o chain , split right 0.5 , focus right
# split root frame
hc keybind $M-$S-y chain , split left 0.5 '' , focus left
hc keybind $M-$S-u chain , split bottom 0.5 '' , focus down
hc keybind $M-$S-i chain , split top 0.5 '' , focus up
hc keybind $M-$S-o chain , split right 0.5 '' , focus right
# frame splitting
hc keybind $M-p split explode
hc keybind $M-r remove
# resize
resizestep=0.04
hc keybind $M-$C-h resize left $resizestep
hc keybind $M-$C-j resize down $resizestep
hc keybind $M-$C-k resize up $resizestep
hc keybind $M-$C-l resize right $resizestep
# cycle
hc keybind $M-n cycle +1
hc keybind $M-m cycle_monitor
# cycle layouts
hc keybind $M-Tab \
or , and . compare tags.focus.curframe_wcount = 2 \
. cycle_layout +1 vertical horizontal max vertical grid \
, cycle_layout +1
# rotate and mirror
hc keybind $M-apostrophe mirror vertical
hc keybind $M-semicolon mirror horizontal
hc keybind $M-slash rotate
# window attributes
hc keybind $M-$C-e floating toggle
hc keybind $M-$C-w fullscreen toggle
hc keybind $M-$C-q set_attr clients.focus.floating toggle
# toggles
hc keybind $M-$S-w spawn sh $scripts/togglemaster
# media keys
hc keybind XF86AudioMute spawn pulsemixer --toggle-mute
hc keybind XF86AudioLowerVolume spawn pulsemixer --change-volume -5
hc keybind XF86AudioRaiseVolume spawn pulsemixer --change-volume +5
hc keybind XF86MonBrightnessDown spawn light -U 5
hc keybind XF86MonBrightnessUp spawn light -A 5
# mouse
hc mouseunbind all
hc mousebind $M-Button1 move
hc mousebind $M-Button2 zoom
hc mousebind $M-Button3 resize
#
# application launchers
#
hc keybind $M-Return spawn $terminal
hc keybind $M-space spawn $launcher
hc keybind $M-grave spawn flameshot gui
hc keybind $M-1 spawn $file_manager
hc keybind $M-2 spawn $browser
hc keybind $M-3 spawn $calculator
hc keybind $M-4 spawn $music_player
hc keybind $M-5 spawn $feed_reader
hc keybind $M-6 spawn virt-manager
hc keybind $M-0 spawn $screen_locker
#
# tags
#
tag_names=(a s d f g z x c v b)
tag_keys=(a s d f g z x c v b)
hc rename default "${tag_names[0]}" || true
for i in "${!tag_names[@]}"; do
hc add "${tag_names[$i]}"
key="${tag_keys[$i]}"
if ! [ -z "$key" ]; then
hc keybind $M-$key use_index $i
hc keybind $M-$S-$key move_index $i
fi
done
#
# theme
#
hc attr theme.tiling.reset 1
hc attr theme.floating.reset 1
hc set frame_border_active_color $color8
hc set frame_border_normal_color $color0
hc set frame_border_width 0
hc set frame_transparent_width 0
hc set frame_gap 12
hc set always_show_frame off
hc set frame_bg_transparent on
hc attr theme.active.color $color8
hc attr theme.normal.color $color0
hc attr theme.urgent.color $color1
hc attr theme.inner_color black
hc attr theme.floating.outer_color black
hc attr theme.border_width 2
hc attr theme.floating.border_width 2
hc set window_gap 0
hc set frame_padding 0
hc set mouse_recenter_gap 0
hc set smart_window_surroundings off
hc set smart_frame_surroundings off
#
# rules
#
hc unrule -F
hc rule focus=on
hc rule windowtype~'_NET_WM_WINDOW_TYPE_(DIALOG|UTILITY|SPLASH)' floating=on
hc rule windowtype='_NET_WM_WINDOW_TYPE_DIALOG' focus=on
hc rule windowtype~'_NET_WM_WINDOW_TYPE_(NOTIFICATION|DOCK|DESKTOP)' manage=off
hc rule class=Cadence floating=on
hc rule class=Lxpolkit floating=on
hc rule class=Dragon-drag-and-drop floating=on
#
# settings
#
hc set focus_follows_mouse 1
#
# autolaunch
#
# panel
hc detect_monitors
panel=~/.config/herbstluftwm/panel/panel.sh
for monitor in $(hc list_monitors | cut -d: -f1); do
"$panel" "$monitor" &
done
picom --experimental-backends &
pidof dunst || dunst &
pidof mpd || mpd &
pidof flameshot || flameshot &
wal -R && ~/.fehbg

View File

@ -0,0 +1,222 @@
#!/usr/bin/env bash
. "${HOME}/.cache/wal/colors.sh"
quote() {
local q="$(printf '%q ' "$@")"
printf '%s' "${q% }"
}
hc_quoted="$(quote "${herbstclient_command[@]:-herbstclient}")"
hc() { "${herbstclient_command[@]:-herbstclient}" "$@"; }
monitor=${1:-0}
geometry=($(hc monitor_rect "$monitor"))
if [ -z "$geometry" ]; then
echo "Invalid monitor $monitor"
exit 1
fi
# geometry has the format W H X Y
x=$((${geometry[0]} + 12))
y=$((${geometry[1]} + 12))
panel_width=$((${geometry[2]} - 24))
panel_height=29
font="*mono*"
####
# Try to find textwidth binary.
# In e.g. Ubuntu, this is named dzen2-textwidth.
if which textwidth &>/dev/null; then
textwidth="textwidth"
elif which dzen2-textwidth &>/dev/null; then
textwidth="dzen2-textwidth"
elif which xftwidth &>/dev/null; then # For guix
textwidth="xtfwidth"
else
echo "This script requires the textwidth tool of the dzen2 project."
exit 1
fi
####
# true if we are using the svn version of dzen2
# depending on version/distribution, this seems to have version strings like
# "dzen-" or "dzen-x.x.x-svn"
if dzen2 -v 2>&1 | head -n 1 | grep -q '^dzen-\([^,]*-svn\|\),'; then
dzen2_svn="true"
else
dzen2_svn=""
fi
if awk -Wv 2>/dev/null | head -1 | grep -q '^mawk'; then
mawk needs "-W interactive" to line-buffer stdout correctly
# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=593504
uniq_linebuffered() {
awk -W interactive '$0 != l { print ; l=$0 ; fflush(); }' "$@"
}
else
# other awk versions (e.g. gawk) issue a warning with "-W interactive", so
# we don't want to use it there.
uniq_linebuffered() {
awk '$0 != l { print ; l=$0 ; fflush(); }' "$@"
}
fi
# get the current pulseaudio volume, or state incase muted
pulsegetvol() {
if [ $(pulsemixer --get-mute) == 1 ]; then
echo -ne "^fg($foreground)muted\n"
else
volume=$(pulsemixer --get-volume | awk '{print $1; exit}')
echo -n "^fg($foreground)$volume"
fi
}
# using pactl we can subscribe to pulseaudio and check for changes
pulsesub() {
pactl subscribe | grep --line-buffered "change.*sink" | while read -r line; do
echo -ne "pulseaudio\t$(pulsegetvol)\n"
done
}
# used when reloading / quitting, kills all event generators as they no longer have a use
list_descendants() {
local children=$(ps -o pid= --ppid "$1")
for pid in $children; do
list_descendants "$pid"
done
echo "$children"
}
hc pad $monitor $(($panel_height + 12))
{
### Event generator ###
# based on different input data (mpc, date, hlwm hooks, ...) this generates events, formed like this:
# <eventname>\t<data> [...]
# e.g.
# date ^fg(#efefef)18:33^fg(#909090), 2013-10-^fg(#efefef)29
#mpc idleloop player &
pulsesub &
while true; do
# "date" output is checked once a second, but an event is only
# generated if the output changed compared to the previous run.
date +$"date ^fg($foreground)%a %b %d %H:%M"
sleep 1 || break
done > >(uniq_linebuffered) &
hc --idle
} 2>/dev/null | {
IFS=$'\t' read -ra tags <<<"$(hc tag_status $monitor)"
visible=true
date=""
windowtitle=""
volume="$(pulsegetvol)"
while true; do
### Output ###
# This part prints dzen data based on the _previous_ data handling run,
# and then waits for the next event to happen.
separator="^bg()^fg($color8)|"
# draw tags
for i in "${tags[@]}"; do
case ${i:0:1} in
'#')
# tag is focused
echo -n "^bg($color2)^fg()"
;;
':')
# tag is not empty
echo -n "^bg()^fg($foreground)"
;;
'!')
# tag containts an urgent window
echo -n "^bg($color6)^fg()"
;;
'-')
# tag is viewed on a non-focused monitor
echo -n "^bg($color1)^fg()"
;;
*)
# tag is empty
echo -n "^bg()^fg($color8)"
;;
esac
if [ ! -z "$dzen2_svn" ]; then
# clickable tags if using SVN dzen
echo -n "^ca(1,$hc_quoted focus_monitor \"$monitor\" && "
echo -n "$hc_quoted use \"${i:1}\") ${i:1} ^ca()"
else
# non-clickable tags if using older dzen
echo -n " ${i:1} "
fi
done
echo -n "$separator"
echo -n "^bg()^fg() ${windowtitle//^/^^}"
# small adjustments
right="$volume $separator $date"
right_text_only=$(echo -n "$right" | sed 's.\^[^(]*([^)]*)..g')
# get width of right aligned text.. and add some space..
width=$($textwidth "$font" "$right_text_only ")
echo -n "^pa($(($panel_width - $width)))$right"
echo
### Data handling ###
# This part handles the events generated in the event loop, and sets
# internal variables based on them. The event and its arguments are
# read into the array cmd, then action is taken depending on the event
# name.
# "Special" events (quit_panel/togglehidepanel/reload) are also handled
# here.
# wait for next event
IFS=$'\t' read -ra cmd || break
# find out event origin
case "${cmd[0]}" in
tag*)
#echo "resetting tags" >&2
IFS=$'\t' read -ra tags <<<"$(hc tag_status $monitor)"
;;
date)
#echo "resetting date" >&2
date="${cmd[@]:1}"
;;
quit_panel)
kill $(list_descendants $$)
exit
;;
togglehidepanel)
currentmonidx=$(hc list_monitors | sed -n '/\[FOCUS\]$/s/:.*//p')
if [ "${cmd[1]}" -ne "$monitor" ]; then
continue
fi
if [ "${cmd[1]}" = "current" ] && [ "$currentmonidx" -ne "$monitor" ]; then
continue
fi
echo "^togglehide()"
if $visible; then
visible=false
hc pad $monitor 0
else
visible=true
hc pad $monitor $(($panel_height + 12))
fi
;;
reload)
kill $(list_descendants $$)
exit
;;
focus_changed | window_title_changed)
windowtitle="${cmd[@]:2}"
;;
pulseaudio)
volume="${cmd[@]:1}"
;;
esac
done
### dzen2 ###
# After the data is gathered and processed, the output of the previous block
# gets piped to dzen2.
} 2>/dev/null | dzen2 -w $panel_width -x $x -y $y -fn mononoki-15 -h $panel_height \
-e "button3=;button4=exec:$hc_quoted use_index -1;button5=exec:$hc_quoted use_index +1" \
-ta l -bg "$background" -fg "$foreground"

View File

@ -0,0 +1,23 @@
#!/bin/bash
dmenu="dmenu -p exit"
choice=$(echo -e "shutdown\nrestart\nlogoff\nscreenlock" | $dmenu)
case $choice in
shutdown)
sudo halt
;;
restart)
sudo shutdown -r now
;;
logoff)
herbstclient quit
;;
screenlock)
i3lock -c 000000
;;
esac

View File

@ -0,0 +1,10 @@
#!/bin/bash
dmenu="dmenu -p resize"
resizestep=$(echo -e "0.04\n0.01\n0.004\n0.001" | $dmenu)
confpath=$XDG_CONFIG_HOME/herbstluftwm/autostart
source <(awk -v RS='' '/M=Mod/' $confpath)
source <(awk -v RS='' '/key\(\)/' $confpath)
source <(grep "resizestep" $confpath | grep -v "^resizestep=")

View File

@ -0,0 +1,30 @@
#!/bin/bash
dmenu="dmenu -p toggle"
choice=$(echo -e "picom\nwindowcovering\nframeborderwidth\nresizestep" | $dmenu)
case $choice in
picom)
pidof picom && killall picom || picom --experimental-backends
;;
windowcovering)
herbstclient set hide_covered_windows toggle
;;
frameborderwidth)
frameborderwidth=$(echo -e "enter width..." | dmenu)
herbstclient set frame_border_width $frameborderwidth
;;
resizestep)
resizestep=$(echo -e "0.04\n0.01\n0.004\n0.001" | dmenu)
confpath=~/.config/herbstluftwm/autostart
source <(awk -v RS='' '/M=Mod/' $confpath)
source <(awk -v RS='' '/key\(\)/' $confpath)
source <(grep "resizestep" $confpath | grep -v "^resizestep=")
;;
esac

View File

@ -0,0 +1,6 @@
#!/bin/bash
dmenu="dmenu -p volume"
volume=$(echo -e "0\n25\n50\n75\n100" | $dmenu)
pulsemixer --set-volume $volume

47
.config/kitty/kitty.conf Normal file
View File

@ -0,0 +1,47 @@
# Monokai
background #1c1c1c
foreground #e2e2e3
cursor #f8f8f2
selection_background #f8f8f2
selection_foreground #272822
active_tab_background #75715e
active_tab_foreground #272822
active_border_color #75715e
inactive_tab_background #272822
inactive_tab_foreground #75715e
inactive_border_color #75715e
url_color #f8f8f2
# 16 Color Space
# light colors
color0 #555555
color1 #fc5d7c
color2 #9ed072
color3 #e7c664
color4 #6a7ec8
color5 #b39df3
color6 #76cce0
color7 #e2e2e3
# dark colors
color8 #666666
color9 #fc5d7c
color10 #9ed072
color11 #e7c664
color12 #6a7ec8
color13 #b39df3
color14 #66d9ef
color15 #e2e2e3
enable_audio_bell no
placement_strategy top-left
font_size 14.0
font_family mononoki Nerd Font Mono
draw_minimal_borders yes
window_padding_width 0
background_opacity 0.8
# uncomment for pywal
include ~/.cache/wal/colors-kitty.conf

29
.config/mpd/mpd.conf Normal file
View File

@ -0,0 +1,29 @@
bind_to_address "127.0.0.1"
music_directory "~/docs/music"
#music_directory "/run/media/HDD/New\ FL\ Backup"
playlist_directory "~/.config/mpd/playlists"
db_file "~/.cache/mpd/database"
log_file "~/.cache/mpd/log"
pid_file "~/.cache/mpd/pid"
state_file "~/.cache/mpd/state"
sticker_file "~/.cache/mpd/sticker.sql"
audio_output {
type "pulse"
name "pulse audio"
device "pulse"
mixer_type "hardware"
}
# audio_output {
# type "jack"
# name "jack audio"
# device "hw:M2,0"
# }
audio_output {
type "fifo"
name "my_fifo"
path "/tmp/mpd.fifo"
format "44100:16:2"
}

22
.config/ncmpcpp/bindings Normal file
View File

@ -0,0 +1,22 @@
def_key "k"
scroll_up
def_key "shift-k"
select_item
scroll_up
def_key "j"
scroll_down
def_key "shift-j"
select_item
scroll_down
def_key "="
volume_up
def_key "+"
show_clock
def_key "space"
pause

40
.config/ncmpcpp/config Normal file
View File

@ -0,0 +1,40 @@
#
# files
#
ncmpcpp_directory = "~/.config/ncmpcpp"
lyrics_directory = "~/.cache/ncmpcpp/"
mpd_music_dir = "~/docs/music"
#
# options
#
seek_time = 5
mouse_list_scroll_whole_page = "no"
lines_scrolled = "3"
#
# colors
#
song_list_format = " %f $R %b %l "
playlist_display_mode = classic
progressbar_color = white:b
progressbar_elapsed_color = cyan:b
main_window_color = white
visualizer_color = cyan
empty_tag_color = cyan
header_window_color = cyan
volume_color = cyan:b
state_line_color = cyan
statusbar_color = cyan
statusbar_time_color = cyan:b
player_state_color = cyan:b
# discard_colors_if_item_is_selected = no
alternative_ui_separator_color = cyan
window_border_color = cyan

18
.config/newsboat/config Normal file
View File

@ -0,0 +1,18 @@
auto-reload yes
bind-key j next
bind-key k prev
bind-key J next-feed
bind-key K prev-feed
bind-key j down article
bind-key k up article
bind-key J next article
bind-key K prev article
color background white default
color listnormal white default
color listfocus white cyan bold
color listnormal_unread white default bold
color listfocus_unread white cyan bold
color info cyan default bold
color article white default

9
.config/nvim/init.lua Normal file
View File

@ -0,0 +1,9 @@
_G.load = function(file)
require("plenary.reload").reload_module(file, true)
return require(file)
end
require("plugins")
load("options")
load("mappings")
load("disablebuiltin")

View File

@ -0,0 +1,24 @@
local disabled_built_ins = {
"netrw",
"netrwPlugin",
"netrwSettings",
"netrwFileHandlers",
"gzip",
"zip",
"zipPlugin",
"tar",
"tarPlugin",
"getscript",
"getscriptPlugin",
"vimball",
"vimballPlugin",
"2html_plugin",
"logipat",
"rrhelper",
"spellfile_plugin",
"matchit"
}
for _, plugin in pairs(disabled_built_ins) do
vim.g["loaded_" .. plugin] = 1
end

View File

@ -0,0 +1,111 @@
local function map(mode, lhs, rhs, opts)
local options = {noremap = false}
if opts then options = vim.tbl_extend('force', options, opts) end
vim.api.nvim_set_keymap(mode, lhs, rhs, options)
end
local function noremap(mode, lhs, rhs, opts)
local options = {noremap = true}
if opts then options = vim.tbl_extend('force', options, opts) end
vim.api.nvim_set_keymap(mode, lhs, rhs, options)
end
-- leader
vim.g.mapleader = ' '
-- visual block indenting
noremap('v', '<', '<gv')
noremap('v', '>', '>gv')
-- centered cursor
noremap('n', 'n', 'nzz')
noremap('n', 'N', 'Nzz')
noremap('n', 'J', 'mzJ`z')
-- escape terminal mode
noremap('t', '<Esc>', '<C-\\><C-n>')
-- newline without entering insert mode
noremap('n', '<C-J>', 'o<Esc>')
noremap('n', '<C-K>', 'O<Esc>')
-- movement
noremap('n', '<leader>h', ':wincmd h<cr>')
noremap('n', '<leader>j', ':wincmd j<cr>')
noremap('n', '<leader>k', ':wincmd k<cr>')
noremap('n', '<leader>l', ':wincmd l<cr>')
-- splits and buffers
noremap('n', '<leader>sv', ':vsplit<CR>')
noremap('n', '<leader>sz', ':split<CR>')
noremap('n', '<leader>st', ':tabnew<CR>')
noremap('n', '<leader>sb', ':Telescope buffers<CR>')
noremap('n', '<leader>sh', ':tabprevious<cr>')
noremap('n', '<leader>sl', ':tabnext<cr>')
-- basics
noremap('n', '<leader>q', ':q<CR>')
noremap('n', '<leader>Q', ':qa<CR>')
noremap('n', '<leader>x', ':q!<CR>')
noremap('n', '<leader>X', ':qa!<CR>')
noremap('n', '<leader>w', ':w<CR>')
noremap('n', '<leader>W', ':wa<CR>')
noremap('n', '<leader>f', ':NvimTreeToggle<CR>')
noremap('n', '<leader>F', ':Telescope find_files<CR>')
-- toggle term
noremap('n', '<leader>c', ':ToggleTermToggleAll<CR>')
noremap('n', '<leader>C', ':ToggleTerm<CR>')
-- frequent actions
noremap('n', '<leader>ar', ':source ~/.config/nvim/init.lua<CR>')
noremap('n', '<leader>ac', ':cd ~/.config/nvim/<CR>')
-- keymap switches
noremap('n', '<leader>me', ':set keymap=<CR>')
noremap('n', '<leader>mh', ':set keymap=hebrew<CR>')
-- telescope
noremap('n', '<leader>tt', ':Telescope<CR>')
noremap('n', '<leader>tl', ':Telescope lsp_dynamic_workspace_symbols<CR>')
noremap('n', '<leader>to', ':Telescope oldfiles<CR>')
noremap('n', '<leader>tg', ':Telescope live_grep<CR>')
noremap('n', '<leader>ts', ':Telescope treesitter<CR>')
noremap('n', '<leader>tm', ':Telescope git_status<CR>')
noremap('n', '<leader>tb', ':Telescope git_branches<CR>')
noremap('n', '<leader>tc', ':Telescope git_commits<CR>')
noremap('n', '<leader>tf', ':Telescope git_files<CR>')
-- code
noremap('n', '<leader>gf', ':Neoformat<CR>')
noremap('n', '<leader>gd', ':Telescope lsp_definitions<CR>')
noremap('n', '<leader>gi', ':Telescope lsp_implementations<CR>')
noremap('n', '<leader>gr', ':Telescope lsp_references<CR>')
noremap('n', '<leader>ga', ':Telescope lsp_code_actions<CR>')
noremap('n', '<leader>gq', ':Telescope lsp_workspace_diagnostics<CR>')
noremap('n', '<leader>gD', ':lua vim.lsp.buf.declaration()<CR>')
noremap('n', '<leader>gk', ':lua vim.lsp.buf.hover()<CR>')
noremap('n', '<leader>gt', ':lua vim.lsp.buf.type_definition()<CR>')
noremap('n', '<leader>gn', ':lua vim.lsp.buf.rename()<CR>')
noremap('n', '<leader>ge', ':lua vim.lsp.diagnostic.show_line_diagnostics()<CR>')
noremap('n', '[d', ':lua vim.lsp.diagnostic.goto_prev()<CR>')
noremap('n', ']d', ':lua vim.lsp.diagnostic.goto_next()<CR>')
-- debug mappings
noremap('n', '<leader>dc', ":lua require'dap'.continue()<CR>")
noremap('n', '<leader>dn', ":lua require'dap'.step_over()<CR>")
noremap('n', '<leader>di', ":lua require'dap'.step_into()<CR>")
noremap('n', '<leader>do', ":lua require'dap'.step_out()<CR>")
noremap('n', '<leader>db', ":lua require'dap'.toggle_breakpoint()<CR>")
noremap('n', '<leader>dl', ":lua require'dap'.run_last()<CR>")
noremap('n', '<leader>dq', ":lua require'dap'.close()<CR>")
noremap('n', '<leader>dv', ':Telescope dap variables<CR>')
noremap('n', '<leader>dh', ':Telescope dap commands<CR>')
noremap('n', '<leader>dp', ':Telescope dap list_breakpoints<CR>')
noremap('n', '<leader>du', ":lua require'dapui'.toggle()<CR>")
-- vsnip jumpable mappings
map('i', '<Tab>', "vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'", {expr = true})
map('s', '<Tab>', "vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'", {expr = true})
map('i', '<S-Tab>', "vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-next)' : '<S-Tab>'", {expr = true})
map('s', '<S-Tab>', "vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-next)' : '<S-Tab>'", {expr = true})

View File

@ -0,0 +1,31 @@
local cmd = vim.cmd
local opt = vim.opt
local g = vim.g
-- sensible settings
opt.autoread = true
opt.clipboard = 'unnamedplus'
opt.conceallevel = 0
opt.hlsearch = false
opt.lazyredraw = true
opt.mouse = 'a'
opt.nu = true
opt.rnu = true
opt.scrolloff = 10
opt.shortmess = 'c'
opt.showcmd = true
opt.showmode = false
opt.sw = 4
opt.swapfile = false
opt.timeoutlen = 500
opt.ts = 4
-- opt.wrap = false
-- :FormatJson command using jq
cmd 'autocmd FileType json :command! FormatJson %!jq .'
-- trigger autoread on file change
cmd "autocmd FocusGained,BufEnter,CursorHold,CursorHoldI * if mode() != 'c' | checktime | endif"
-- neovim-qt font
opt.guifont = 'DejaVu\\ Sans\\ Mono:h14'

View File

@ -0,0 +1,217 @@
require('packer').startup(function()
use 'wbthomason/packer.nvim'
--
-- LSP
--
-- lspconfig
use {
'neovim/nvim-lspconfig',
event = 'BufEnter',
config = function() require('plugins.lsp') end
}
-- nvim lint
use {
'mfussenegger/nvim-lint',
event = 'BufWrite',
config = function() require('plugins.lint') end
}
--
-- completion
--
-- cmp
use {
'hrsh7th/nvim-cmp',
event = 'InsertEnter',
config = function() require('plugins.cmp') end
}
use {
'hrsh7th/cmp-nvim-lsp',
after = 'nvim-cmp'
}
use {
'hrsh7th/cmp-vsnip',
after = 'nvim-cmp'
}
use {
'hrsh7th/cmp-buffer',
after = 'nvim-cmp'
}
use {
'hrsh7th/cmp-path',
after = 'nvim-cmp'
}
-- snippets
use {
'hrsh7th/vim-vsnip',
after = 'nvim-cmp'
}
use {
'rafamadriz/friendly-snippets',
after = 'vim-vsnip'
}
-- autopairs
use {
'windwp/nvim-autopairs',
after = 'nvim-cmp',
config = function() require('plugins.autopairs') end
}
-- treesitter
use {
'nvim-treesitter/nvim-treesitter',
event = 'BufEnter',
config = function() require('plugins.treesitter') end
}
-- dap
use {
'mfussenegger/nvim-dap',
module = 'dap',
config = function() require('plugins.dap') end
}
use {
'nvim-telescope/telescope-dap.nvim',
after = 'nvim-dap'
}
use {
'rcarriga/nvim-dap-ui',
after = 'nvim-dap',
config = function() require('plugins.dapui') end
}
-- toggleterm
use {
'akinsho/toggleterm.nvim',
cmd = 'ToggleTerm*',
config = function() require('plugins.toggleterm') end
}
-- neoformat
use {
'sbdchd/neoformat',
cmd = 'Neoformat',
config = function() require('plugins.neoformat') end
}
-- comment.nvim
use {
'numToStr/Comment.nvim',
event = 'BufRead',
config = function() require('plugins.comment') end
}
-- fugitive
use {
'tpope/vim-fugitive',
cmd = 'G*'
}
--
-- files (and more)
--
-- telescope
use {
'nvim-telescope/telescope.nvim',
cmd = 'Telescope',
requires = {'nvim-lua/plenary.nvim'},
config = function() require('plugins.telescope') end
}
-- nvim tree
use {
'kyazdani42/nvim-tree.lua',
requires = 'kyazdani42/nvim-web-devicons',
config = function() require('plugins.nvim-tree') end
}
--
-- looks
--
-- lualine
use {
'hoob3rt/lualine.nvim',
config = function() require('plugins.lualine') end
}
--
-- colorscheme
--
use {
-- 'crusoexia/vim-monokai',
'dylanaraps/wal.vim',
config = function() require('plugins.colors') end,
after = 'nvim-treesitter'
}
--
-- qol
--
-- blankline
use {
'lukas-reineke/indent-blankline.nvim',
event = 'VimEnter'
}
-- highlight yank
use {
'machakann/vim-highlightedyank',
event = 'TextYankPost'
}
-- tabular
use {
'godlygeek/tabular',
cmd = 'Tabularize'
}
-- maximizer
use {
'szw/vim-maximizer',
cmd = 'MaximizerToggle'
}
-- whichkey
use {
'folke/which-key.nvim',
config = function() require('plugins.whichkey') end
}
--
-- filetype specific
--
-- csv
use {
'chrisbra/csv.vim',
ft = 'csv'
}
-- latex
use {
'lervag/vimtex',
ft = 'tex',
config = function() require('plugins.vimtex') end
}
--
-- misc
--
use {
'vimwiki/vimwiki',
config = function() require('plugins.vimwiki') end
}
end)

View File

@ -0,0 +1,6 @@
-- autopairs
require('nvim-autopairs').setup{}
-- insert () after selecting functions
local cmp_autopairs = require('nvim-autopairs.completion.cmp')
local cmp = require('cmp')
cmp.event:on('confirm_done', cmp_autopairs.on_confirm_done({ map_char = { tex = '' } }))

View File

@ -0,0 +1,27 @@
vim.opt.completeopt = { 'menuone', 'noselect' }
local cmp = require'cmp'
cmp.setup({
snippet = {
expand = function(args)
vim.fn["vsnip#anonymous"](args.body)
end,
},
mapping = {
['<C-d>'] = cmp.mapping.scroll_docs(-4),
['<C-f>'] = cmp.mapping.scroll_docs(4),
['<C-Space>'] = cmp.mapping.complete(),
['<C-e>'] = cmp.mapping.close(),
['<CR>'] = cmp.mapping.confirm({
behavior = cmp.ConfirmBehavior.Replace,
select = true
})
},
sources = {
{ name = 'path' },
{ name = 'nvim_lsp' },
{ name = 'buffer' },
{ name = 'vsnip' }
}
})

View File

@ -0,0 +1,4 @@
-- colorscheme
vim.cmd 'colorscheme wal'
-- vim.cmd 'colorscheme monokai'
vim.cmd 'highlight Pmenu ctermbg=black'

View File

@ -0,0 +1 @@
require("Comment").setup()

View File

@ -0,0 +1,121 @@
local dap = require('dap')
--
-- go
--
-- dap.adapters.go = function(callback, config)
-- local stdout = vim.loop.new_pipe(false)
-- local handle local pid_or_err local port = 38697
-- local opts = {
-- stdio = {nil, stdout},
-- args = {"dap", "-l", "127.0.0.1:" .. port},
-- detached = true
-- }
-- handle, pid_or_err = vim.loop.spawn("dlv", opts, function(code)
-- stdout:close()
-- handle:close()
-- if code ~= 0 then
-- print('dlv exited with code', code)
-- end
-- end)
-- assert(handle, 'Error running dlv: ' .. tostring(pid_or_err))
-- stdout:read_start(function(err, chunk)
-- assert(not err, err)
-- if chunk then
-- vim.schedule(function()
-- require('dap.repl').append(chunk)
-- end)
-- end
-- end)
-- vim.defer_fn(
-- function()
-- callback({type = "server", host = "127.0.0.1", port = port})
-- end,
-- 100)
-- end
dap.adapters.go = {
type = "server",
host = "127.0.0.1",
port = 38697,
}
-- https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_dap.md
dap.configurations.go = {
{
type = "go",
name = "Debug",
request = "launch",
program = "${fileDirname}"
},
{
type = "go",
name = "Debug test", -- configuration for debugging test files
request = "launch",
mode = "test",
program = "${file}"
},
-- works with go.mod packages and sub packages
{
type = "go",
name = "Debug test (go.mod)",
request = "launch",
mode = "test",
program = "./${relativeFileDirname}"
}
}
--
-- python
--
dap.adapters.python = {
type = 'executable';
command = 'python';
args = { '-m', 'debugpy.adapter' };
}
dap.configurations.python = {
{
type = 'python';
request = 'launch';
name = "Launch file";
program = "${file}";
pythonPath = function()
local cwd = vim.fn.getcwd()
if vim.fn.executable(cwd .. '/venv/bin/python') == 1 then
return cwd .. '/venv/bin/python'
elseif vim.fn.executable(cwd .. '/.venv/bin/python') == 1 then
return cwd .. '/.venv/bin/python'
else
return '/usr/bin/python'
end
end;
},
}
--
-- C
--
dap.adapters.lldb = {
type = 'executable',
command = '/usr/bin/lldb-vscode',
name = "lldb"
}
dap.configurations.c = {
{
name = "Launch",
type = "lldb",
request = "launch",
program = function()
return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file')
end,
cwd = '${workspaceFolder}',
stopOnEntry = false,
args = {},
runInTerminal = false,
},
}

View File

@ -0,0 +1 @@
require("dapui").setup()

View File

@ -0,0 +1,5 @@
require('lint').linters_by_ft = {
python = {'mypy'}
}
vim.cmd("au BufWrite <buffer> lua require('lint').try_lint()")

View File

@ -0,0 +1,17 @@
local lsp = require('lspconfig')
-- language servers
lsp.pyright.setup{
settings = {
python = {
analysis = {
typeCheckingMode = "off"
}
}
}
}
lsp.gopls.setup{}
lsp.clangd.setup{}
lsp.texlab.setup{}
lsp.rust_analyzer.setup{}
lsp.jdtls.setup{ cmd = { "jdtls" } }

View File

@ -0,0 +1,8 @@
-- lualine colorscheme
require('lualine').setup{
options = {
theme = 'auto',
section_separators = {'', ''},
component_separators = {'', ''}
}
}

View File

@ -0,0 +1 @@
vim.g.neoformat_enabled_python = {'yapf'}

View File

@ -0,0 +1,8 @@
-- nvim tree
require'nvim-tree'.setup{
auto_close = true,
update_cwd = true,
diagnostics = {
enable = true,
},
}

View File

@ -0,0 +1,8 @@
-- telescope settings
require('telescope').setup{
defaults = {
-- sorting
file_sorter = require'telescope.sorters'.get_fzy_sorter,
generic_sorter = require'telescope.sorters'.get_fzy_sorter,
}
}

View File

@ -0,0 +1,2 @@
vim.o.hidden = true
require("toggleterm").setup{}

View File

@ -0,0 +1,5 @@
require'nvim-treesitter.configs'.setup {
highlight = {
enable = true,
},
}

View File

@ -0,0 +1,5 @@
-- ignore certain errors
vim.g.vimtex_quickfix_ignore_filters = { 'Underfull', 'Overfull', 'babel' }
-- use zathura as the pdf viewer
vim.g.vimtex_view_general_viewer = 'zathura'

View File

@ -0,0 +1,2 @@
vim.cmd("let g:vimwiki_list = [{'path': '~/misc/vimwiki', 'syntax': 'markdown', 'ext': '.md'}]")
vim.cmd("let g:vimwiki_key_mappings = { 'global': 0, }")

View File

@ -0,0 +1,76 @@
local wk = require("which-key")
wk.register({
a = {
name = "Actions",
r = "Reload Config"
},
d = {
name = "Debug",
b = "Toggle Breakpoint",
c = "Continue",
h = "Telescope",
i = "Step Into",
l = "Run Last",
n = "Step Over",
o = "Step Out",
p = "List Breakpoints",
q = "Stop",
u = "Toggle UI",
v = "Show Variables"
},
g = {
name = "Language",
D = "Go To Declaration",
a = "Code Actions",
d = "Go To Definition",
e = "Show Line Diagnostics",
f = "Format Code",
i = "Show Implementations",
k = "Hover",
n = "Rename",
q = "Show Diagnostics",
r = "Show References",
t = "Show Type Definition"
},
m = {
name = "Keymap",
e = "English",
e = "Hebrew"
},
s = {
name = "Splits & Buffers",
v = "Vertical Split",
z = "Horizontal Split Split",
t = "New Tab",
b = "Show Buffers",
h = "Left Tab",
l = "Right Tab",
},
t = {
name = "Telescope",
l = "Lsp Workspace Symbols",
o = "Old Files",
g = "Grep",
s = "Treesitter",
m = "Git Status",
b = "Git Branches",
c = "Git Commits",
f = "Git Files"
},
C = "Terminal",
F = "Fuzzy Files",
Q = "Quit All",
S = "Save All",
X = "Quit All Without Saving",
c = "Toggle All Terminal",
f = "File Manager",
f = "File Manager",
h = "Left Split",
j = "Down Split",
k = "Up Split",
l = "Right Split",
q = "Quit",
s = "Save",
x = "Quit Without Saving"
}, { prefix = "<leader>" })
wk.setup{}

43
.config/picom.conf Normal file
View File

@ -0,0 +1,43 @@
opacity-rule = [
"80:class_g = 'tabbed'",
"80:class_g = 'dzen'",
];
# Blur
blur:
{
method = "dual_kawase";
strength = 8;
}
wintypes:
{
normal = { blur-background = true; };
splash = { blur-background = false; };
};
# Fading
fading = false;
fade-in-step = 0.07;
fade-out-step = 0.07;
fade-exclude = [ ];
# Other
mark-wmwin-focused = true;
mark-ovredir-focused = true;
detect-rounded-corners = true;
detect-client-opacity = true;
refresh-rate = 0;
vsync = true;
dbe = false;
unredir-if-possible = true;
detect-transient = true;
detect-client-leader = true;
invert-color-include = [ ];
# GLX backend
backend = "glx";
glx-no-stencil = true;
glx-copy-from-front = false;
use-damage = true
glx-no-rebind-pixmap = true;

7
.config/qtile/autostart.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
wal -R &
# picom --experimental-backends
pidof dunst || dunst &
pidof mpd || mpd &
pidof flameshot || flameshot &
feh --bg-fill ~/.config/ricer/backgrounds/background.png && rm ~/.fehbg &

211
.config/qtile/config.py Normal file
View File

@ -0,0 +1,211 @@
import os
import subprocess
from typing import List # noqa: F401
from libqtile import bar, layout, widget
from libqtile.config import Click, Drag, Group, Match, Screen
from libqtile.lazy import lazy
from libqtile.config import EzKey
from libqtile import hook
#
# variables
#
mod = "mod4"
terminal = "kitty"
launcher = "dmenu_run"
file_manager = "thunar"
browser = "firefox"
calculator = "dcalc"
music_player = f"{terminal} -e ncmpcpp"
feed_reader = f"{terminal} -e newsboat"
screen_locker = "i3lock -c 000000"
scripts = os.path.expanduser('~/.config/qtile/scripts')
#
# keybinds
#
keys = [
# basics
EzKey("M-w", lazy.window.kill()),
EzKey("M-t", lazy.restart()),
EzKey("M-q", lazy.spawn(os.path.join(scripts, "exitmenu"))),
# focus
EzKey("M-h", lazy.layout.left()),
EzKey("M-j", lazy.layout.down()),
EzKey("M-k", lazy.layout.up()),
EzKey("M-l", lazy.layout.right()),
# shift
EzKey("M-S-h", lazy.layout.shuffle_left()),
EzKey("M-S-j", lazy.layout.shuffle_down()),
EzKey("M-S-k", lazy.layout.shuffle_up()),
EzKey("M-S-l", lazy.layout.shuffle_right()),
# resize
EzKey("M-C-h", lazy.layout.grow_left()),
EzKey("M-C-j", lazy.layout.grow_down()),
EzKey("M-C-k", lazy.layout.grow_up()),
EzKey("M-C-l", lazy.layout.grow_right()),
# cycle
EzKey("M-n", lazy.group.next_window()),
# window attributes
EzKey("M-C-q", lazy.window.toggle_floating()),
EzKey("M-C-w", lazy.window.toggle_fullscreen()),
# cycle layouts
EzKey("M-<Tab>", lazy.next_layout()),
# toggles
EzKey("M-S-w", lazy.spawn(os.path.join(scripts, "togglemaster"))),
# # media
# EzKey("XF86AudioMute", lazy.spawn("pulsemixer --toggle-mute")),
# EzKey("XF86AudioLowerVolume", lazy.spawn("pulsemixer --change-volume -5")),
# EzKey("XF86AudioRaiseVolume", lazy.spawn("pulsemixer --change-volume +5")),
# EzKey("XF86MonBrightnessDown", lazy.spawn("light -U 5")),
# EzKey("XF86MonBrightnessUp", lazy.spawn("light -A 5")),
# app launchers
EzKey("M-<Return>", lazy.spawn(terminal)),
EzKey("M-<space>", lazy.spawn(launcher)),
EzKey("M-<grave>", lazy.spawn("flameshot gui")),
EzKey("M-1", lazy.spawn(file_manager)),
EzKey("M-2", lazy.spawn(browser)),
EzKey("M-3", lazy.spawn(calculator)),
EzKey("M-4", lazy.spawn(music_player)),
EzKey("M-5", lazy.spawn(feed_reader)),
EzKey("M-6", lazy.spawn("virt-manager")),
EzKey("M-0", lazy.spawn(screen_locker)),
]
#
# groups
#
groups = [Group(i) for i in "asdfgzxcvb"]
for i in groups:
keys.extend([
EzKey(f"M-{i.name}", lazy.group[i.name].toscreen()),
EzKey(f"M-S-{i.name}", lazy.window.togroup(i.name)),
])
#
# theme
#
# load wal colors
colors = []
walpath = os.path.expanduser('~/.cache/wal/colors')
with open(walpath, 'r') as file:
for _ in range(8):
colors.append(file.readline().strip())
colors.append('#ffffff')
lazy.reload()
layout_theme = {
"border_width": 2,
"margin": 6,
"border_on_single": True,
"border_focus": colors[8],
"border_normal": colors[0],
}
#
# layouts
#
layouts = [
layout.Columns(**layout_theme),
layout.Bsp(**layout_theme, fair=False),
layout.Max(**layout_theme),
]
#
# bar
#
widget_defaults = dict(
font='mononoki',
fontsize=15,
padding=6,
)
extension_defaults = widget_defaults.copy()
group_box_settings = dict(
disable_drag=True,
highlight_method="block",
borderwidth=0,
# group is focused
this_current_screen_border=colors[2],
other_current_screen_border=colors[2],
block_highlight_text_color="ffffff",
active="ffffff",
# tag containts an urgent window
urgent_border=colors[6],
# tag is viewed on a non-focused monitor
this_screen_border=colors[1],
other_screen_border=colors[1],
# tag is empty
inactive="817f7f",
)
screens = []
for _ in range(2):
screens.append(
Screen(top=bar.Bar(
[
widget.GroupBox(**group_box_settings),
widget.Sep(),
widget.WindowName(),
widget.Spacer(),
widget.PulseVolume(),
widget.Sep(),
widget.Clock(format='%a %b %d %H:%M'),
],
29,
background='#0e0e0e',
margin=[6, 6, 6, 6],
), ), )
# Drag floating layouts.
mouse = [
Drag([mod],
"Button1",
lazy.window.set_position_floating(),
start=lazy.window.get_position()),
Drag([mod],
"Button3",
lazy.window.set_size_floating(),
start=lazy.window.get_size()),
Click([mod], "Button2", lazy.window.bring_to_front())
]
dgroups_key_binder = None
dgroups_app_rules = [] # type: List
follow_mouse_focus = True
bring_front_click = False
cursor_warp = False
floating_layout = layout.Floating(float_rules=[
# Run the utility of `xprop` to see the wm class and name of an X client.
*layout.Floating.default_float_rules,
Match(wm_class='confirmreset'), # gitk
Match(wm_class='makebranch'), # gitk
Match(wm_class='maketag'), # gitk
Match(wm_class='ssh-askpass'), # ssh-askpass
Match(title='branchdialog'), # gitk
Match(title='pinentry'), # GPG key password entry
])
# autostart script
@hook.subscribe.startup_complete
def autostart():
autostart = os.path.expanduser('~/.config/qtile/autostart.sh')
subprocess.call([autostart])

22
.config/qtile/scripts/exitmenu Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
choice=$(echo -e "shutdown\nrestart\nlogoff\nscreenlock" | dmenu)
case $choice in
shutdown)
sudo shutdown now
;;
restart)
sudo shutdown -r now
;;
logoff)
pkill qtile
;;
screenlock)
i3lock -c 000000
;;
esac

View File

@ -0,0 +1,30 @@
MIT/X Consortium License
© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
© 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 Markus Schnalke <meillo@marmaro.de>
© 2009 Evan Gates <evan.gates@gmail.com>
© 2010-2012 Connor Lane Smith <cls@lubutu.com>
© 2014-2020 Hiltjo Posthuma <hiltjo@codemadness.org>
© 2015-2019 Quentin Rameau <quinq@fifth.space>
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.

View File

@ -0,0 +1,64 @@
# 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
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 $(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
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
.PHONY: all options clean dist install uninstall

View File

@ -0,0 +1,24 @@
dmenu - dynamic menu
====================
dmenu is an efficient dynamic menu for X.
Requirements
------------
In order to build dmenu you need the Xlib header files.
Installation
------------
Edit config.mk to match your local setup (dmenu is installed into
the /usr/local namespace by default).
Afterwards enter the following command to build and install dmenu
(if necessary as root):
make clean install
Running dmenu
-------------
See the man page for details.

View File

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

View File

@ -0,0 +1,27 @@
/* 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 */
static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */
/* -fn option overrides fonts[0]; default X11 font or font set */
static const char *fonts[] = {
"monospace:size=10"
};
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", "#005577" },
[SchemeOut] = { "#000000", "#00ffff" },
};
/* -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[] = " ";
/* Size of the window border */
static const unsigned int border_width = 5;

View File

@ -0,0 +1,26 @@
/* 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 */
static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */
/* -fn option overrides fonts[0]; default X11 font or font set */
static const char *fonts[] = {
"mononoki:size=15"
};
static const char *prompt = NULL; /* -p option; prompt to the left of input field */
static const char *colors[SchemeLast][2] = {
/* fg bg */
[SchemeNorm] = { "#817f7f", "#0e0e0e" },
[SchemeSel] = { "#eeeeee", "#817f7f" },
[SchemeOut] = { "#000000", "#00ffff" },
};
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
static unsigned int lines = 8;
/*
* Characters not considered part of a word while deleting words
* for example: " /?\"&[]"
*/
static const char worddelimiters[] = " ";
static const unsigned int border_width = 2;

View File

@ -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) -lm
# 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

View File

@ -0,0 +1,25 @@
diff -up dmenu-4.9-b/config.def.h dmenu-4.9-a/config.def.h
--- dmenu-4.9-b/config.def.h 2019-02-02 13:55:02.000000000 +0100
+++ dmenu-4.9-a/config.def.h 2019-05-19 02:10:12.740040403 +0200
@@ -21,3 +21,6 @@ static unsigned int lines = 0;
* for example: " /?\"&[]"
*/
static const char worddelimiters[] = " ";
+
+/* Size of the window border */
+static const unsigned int border_width = 5;
diff -up dmenu-4.9-b/dmenu.c dmenu-4.9-a/dmenu.c
--- dmenu-4.9-b/dmenu.c 2019-02-02 13:55:02.000000000 +0100
+++ dmenu-4.9-a/dmenu.c 2019-05-19 02:11:20.966710117 +0200
@@ -654,9 +654,10 @@ setup(void)
swa.override_redirect = True;
swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
- win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
+ win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
+ XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel);
XSetClassHint(dpy, win, &ch);
/* open input methods */

View File

@ -0,0 +1,56 @@
diff --git a/dmenu.c b/dmenu.c
index 5e9c367..2268ea9 100644
--- a/dmenu.c
+++ b/dmenu.c
@@ -88,6 +88,15 @@ calcoffsets(void)
break;
}
+static int
+max_textw(void)
+{
+ int len = 0;
+ for (struct item *item = items; item && item->text; item++)
+ len = MAX(TEXTW(item->text), len);
+ return len;
+}
+
static void
cleanup(void)
{
@@ -598,6 +607,7 @@ setup(void)
bh = drw->fonts->h + 2;
lines = MAX(lines, 0);
mh = (lines + 1) * bh;
+ promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
#ifdef XINERAMA
i = 0;
if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
@@ -624,9 +634,9 @@ setup(void)
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;
+ mw = MIN(MAX(max_textw() + promptw, 100), info[i].width);
+ x = info[i].x_org + ((info[i].width - mw) / 2);
+ y = info[i].y_org + ((info[i].height - mh) / 2);
XFree(info);
} else
#endif
@@ -634,11 +644,10 @@ setup(void)
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;
+ mw = MIN(MAX(max_textw() + promptw, 100), wa.width);
+ x = (wa.width - mw) / 2;
+ y = (wa.height - mh) / 2;
}
- promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
inputw = MIN(inputw, mw/3);
match();

View File

@ -0,0 +1,163 @@
From 94353eb52055927d9079f3d9e33da1c954abf386 Mon Sep 17 00:00:00 2001
From: aleks <aleks.stier@icloud.com>
Date: Wed, 26 Jun 2019 13:25:10 +0200
Subject: [PATCH] Add support for fuzzy-matching
---
config.def.h | 1 +
config.mk | 2 +-
dmenu.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 91 insertions(+), 1 deletion(-)
diff --git a/config.def.h b/config.def.h
index 1edb647..51612b9 100644
--- a/config.def.h
+++ b/config.def.h
@@ -2,6 +2,7 @@
/* Default settings; can be overriden by command line. */
static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
+static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */
/* -fn option overrides fonts[0]; default X11 font or font set */
static const char *fonts[] = {
"monospace:size=10"
diff --git a/config.mk b/config.mk
index 0929b4a..d14309a 100644
--- a/config.mk
+++ b/config.mk
@@ -20,7 +20,7 @@ FREETYPEINC = /usr/include/freetype2
# includes and libs
INCS = -I$(X11INC) -I$(FREETYPEINC)
-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS)
+LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm
# flags
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
diff --git a/dmenu.c b/dmenu.c
index 6b8f51b..96ddc98 100644
--- a/dmenu.c
+++ b/dmenu.c
@@ -1,6 +1,7 @@
/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <locale.h>
+#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -32,6 +33,7 @@ struct item {
char *text;
struct item *left, *right;
int out;
+ double distance;
};
static char text[BUFSIZ] = "";
@@ -210,9 +212,94 @@ grabkeyboard(void)
die("cannot grab keyboard");
}
+int
+compare_distance(const void *a, const void *b)
+{
+ struct item *da = *(struct item **) a;
+ struct item *db = *(struct item **) b;
+
+ if (!db)
+ return 1;
+ if (!da)
+ return -1;
+
+ return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
+}
+
+void
+fuzzymatch(void)
+{
+ /* bang - we have so much memory */
+ struct item *it;
+ struct item **fuzzymatches = NULL;
+ char c;
+ int number_of_matches = 0, i, pidx, sidx, eidx;
+ int text_len = strlen(text), itext_len;
+
+ matches = matchend = NULL;
+
+ /* walk through all items */
+ for (it = items; it && it->text; it++) {
+ if (text_len) {
+ itext_len = strlen(it->text);
+ pidx = 0; /* pointer */
+ sidx = eidx = -1; /* start of match, end of match */
+ /* walk through item text */
+ for (i = 0; i < itext_len && (c = it->text[i]); i++) {
+ /* fuzzy match pattern */
+ if (!fstrncmp(&text[pidx], &c, 1)) {
+ if(sidx == -1)
+ sidx = i;
+ pidx++;
+ if (pidx == text_len) {
+ eidx = i;
+ break;
+ }
+ }
+ }
+ /* build list of matches */
+ if (eidx != -1) {
+ /* compute distance */
+ /* add penalty if match starts late (log(sidx+2))
+ * add penalty for long a match without many matching characters */
+ it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
+ /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
+ appenditem(it, &matches, &matchend);
+ number_of_matches++;
+ }
+ } else {
+ appenditem(it, &matches, &matchend);
+ }
+ }
+
+ if (number_of_matches) {
+ /* initialize array with matches */
+ if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
+ die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
+ for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
+ fuzzymatches[i] = it;
+ }
+ /* sort matches according to distance */
+ qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
+ /* rebuild list of matches */
+ matches = matchend = NULL;
+ for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \
+ it->text; i++, it = fuzzymatches[i]) {
+ appenditem(it, &matches, &matchend);
+ }
+ free(fuzzymatches);
+ }
+ curr = sel = matches;
+ calcoffsets();
+}
+
static void
match(void)
{
+ if (fuzzy) {
+ fuzzymatch();
+ return;
+ }
static char **tokv = NULL;
static int tokn = 0;
@@ -702,6 +789,8 @@ main(int argc, char *argv[])
topbar = 0;
else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
fast = 1;
+ else if (!strcmp(argv[i], "-F")) /* grabs keyboard before reading stdin */
+ fuzzy = 0;
else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
fstrncmp = strncasecmp;
fstrstr = cistrstr;
--
2.22.0

View File

@ -0,0 +1,194 @@
.TH DMENU 1 dmenu\-VERSION
.SH NAME
dmenu \- dynamic menu
.SH SYNOPSIS
.B dmenu
.RB [ \-bfiv ]
.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
.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)

View File

@ -0,0 +1,870 @@
/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#ifdef XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#include <X11/Xft/Xft.h>
#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)
/* enums */
enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
struct item {
char *text;
struct item *left, *right;
int out;
double distance;
};
static char text[BUFSIZ] = "";
static char *embed;
static int bh, mw, mh;
static int inputw = 0, promptw;
static int lrpad; /* sum of left and right padding */
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 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
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 int
max_textw(void)
{
int len = 0;
for (struct item *item = items; item && item->text; item++)
len = MAX(TEXTW(item->text), len);
return len;
}
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;
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]);
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");
}
int
compare_distance(const void *a, const void *b)
{
struct item *da = *(struct item **) a;
struct item *db = *(struct item **) b;
if (!db)
return 1;
if (!da)
return -1;
return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
}
void
fuzzymatch(void)
{
/* bang - we have so much memory */
struct item *it;
struct item **fuzzymatches = NULL;
char c;
int number_of_matches = 0, i, pidx, sidx, eidx;
int text_len = strlen(text), itext_len;
matches = matchend = NULL;
/* walk through all items */
for (it = items; it && it->text; it++) {
if (text_len) {
itext_len = strlen(it->text);
pidx = 0; /* pointer */
sidx = eidx = -1; /* start of match, end of match */
/* walk through item text */
for (i = 0; i < itext_len && (c = it->text[i]); i++) {
/* fuzzy match pattern */
if (!fstrncmp(&text[pidx], &c, 1)) {
if(sidx == -1)
sidx = i;
pidx++;
if (pidx == text_len) {
eidx = i;
break;
}
}
}
/* build list of matches */
if (eidx != -1) {
/* compute distance */
/* add penalty if match starts late (log(sidx+2))
* add penalty for long a match without many matching characters */
it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
/* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
appenditem(it, &matches, &matchend);
number_of_matches++;
}
} else {
appenditem(it, &matches, &matchend);
}
}
if (number_of_matches) {
/* initialize array with matches */
if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
fuzzymatches[i] = it;
}
/* sort matches according to distance */
qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
/* rebuild list of matches */
matches = matchend = NULL;
for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \
it->text; i++, it = fuzzymatches[i]) {
appenditem(it, &matches, &matchend);
}
free(fuzzymatches);
}
curr = sel = matches;
calcoffsets();
}
static void
match(void)
{
if (fuzzy) {
fuzzymatch();
return;
}
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;
/* 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();
}
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
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;
/* 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 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], 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;
promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
#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;
mw = MIN(MAX(max_textw() + promptw, 100), info[i].width);
x = info[i].x_org + ((info[i].width - mw) / 2);
y = info[i].y_org + ((info[i].height - mh) / 2);
XFree(info);
} else
#endif
{
if (!XGetWindowAttributes(dpy, parentwin, &wa))
die("could not get embedding window attributes: 0x%lx",
parentwin);
mw = MIN(MAX(max_textw() + promptw, 100), wa.width);
x = (wa.width - mw) / 2;
y = (wa.height - mh) / 2;
}
inputw = MIN(inputw, mw/3);
match();
/* create menu window */
swa.override_redirect = True;
swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel);
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 [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
" [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
exit(1);
}
int
main(int argc, char *argv[])
{
XWindowAttributes wa;
int i, fast = 0;
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], "-F")) /* grabs keyboard before reading stdin */
fuzzy = 0;
else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
fstrncmp = strncasecmp;
fstrstr = cistrstr;
} 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 (!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);
drw = drw_create(dpy, screen, root, wa.width, wa.height);
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 */
}

View File

@ -0,0 +1,781 @@
/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#ifdef XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#include <X11/Xft/Xft.h>
#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)
/* 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;
static int lrpad; /* sum of left and right padding */
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 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
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 int
max_textw(void)
{
int len = 0;
for (struct item *item = items; item && item->text; item++)
len = MAX(TEXTW(item->text), len);
return len;
}
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;
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]);
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;
/* 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();
}
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
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;
/* 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 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], 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;
promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
#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;
mw = MIN(MAX(max_textw() + promptw, 100), info[i].width);
x = info[i].x_org + ((info[i].width - mw) / 2);
y = info[i].y_org + ((info[i].height - mh) / 2);
XFree(info);
} else
#endif
{
if (!XGetWindowAttributes(dpy, parentwin, &wa))
die("could not get embedding window attributes: 0x%lx",
parentwin);
mw = MIN(MAX(max_textw() + promptw, 100), wa.width);
x = (wa.width - mw) / 2;
y = (wa.height - mh) / 2;
}
inputw = MIN(inputw, mw/3);
match();
/* create menu window */
swa.override_redirect = True;
swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel);
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 [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
" [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
exit(1);
}
int
main(int argc, char *argv[])
{
XWindowAttributes wa;
int i, fast = 0;
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 (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 (!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);
drw = drw_create(dpy, screen, root, wa.width, wa.height);
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 */
}

View File

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

View File

@ -0,0 +1,2 @@
#!/bin/sh
dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &

View File

@ -0,0 +1,436 @@
/* See LICENSE file for copyright and license details. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#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)
{
Drw *drw = ecalloc(1, sizeof(Drw));
drw->dpy = dpy;
drw->screen = screen;
drw->root = root;
drw->w = w;
drw->h = h;
drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
drw->gc = XCreateGC(dpy, root, 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, DefaultDepth(drw->dpy, drw->screen));
}
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.");
}
/* Do not allow using color fonts. This is a workaround for a BadLength
* error from Xft with color glyphs. Modelled on the Xterm workaround. See
* https://bugzilla.redhat.com/show_bug.cgi?id=1498269
* https://lists.suckless.org/dev/1701/30932.html
* https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349
* and lots more all over the internet.
*/
FcBool iscol;
if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) {
XftFontClose(drw->dpy, xfont);
return NULL;
}
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)
{
if (!drw || !dest || !clrname)
return;
if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen),
clrname, dest))
die("error, cannot allocate color '%s'", clrname);
}
/* 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
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,
DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen));
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);
FcPatternAddBool(fcpattern, FC_COLOR, FcFalse);
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);
}

View File

@ -0,0 +1,57 @@
/* 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;
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);
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);
Clr *drw_scm_create(Drw *drw, const char *clrnames[], 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);

View File

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

View File

@ -0,0 +1,109 @@
/* See LICENSE file for copyright and license details. */
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}

View File

@ -0,0 +1,35 @@
/* See LICENSE file for copyright and license details. */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

View File

@ -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);

View File

@ -0,0 +1,28 @@
alsa-plugins
ams-lv2
ardour
aubio
bshapr
cadence
calf
carla
distrho-ports
dragonfly-reverb
ebumeter
eq10q
fluidsynth
geonkick
guitarix
jack_mixer
lsp-plugins
ninjas2
noise-repellent
samplv1
sfizz
surge
tap-plugins
vamp-plugin-sdk
wolf-shaper
x42-plugins
zam-plugins
zyn-fusion

36
.config/ricer/packagelist Normal file
View File

@ -0,0 +1,36 @@
alacritty
colorpicker
dunst
dzen2
engrampa
feh
firefox
flameshot
fzf
herbstluftwm
htop
kitty
lxqt-policykit
lxsession-gtk3
mpd
ncmpcpp
neovim-qt
nerd-fonts-mononoki
newsboat
papirus-icon-theme
picom-git
pulseaudio
pulseaudio-alsa
pulsemixer
python-pywal
qtile
ripgrep
sxiv
themix-theme-oomox-git
thunar
thunar-archive-plugin
ttf-dejavu
ttf-mononoki
xorg
xorg-server
xorg-xinit

View File

@ -0,0 +1 @@
maim -s | xclip -selection clipboard -t image/png

View File

@ -0,0 +1,2 @@
#!/bin/bash
streamlink https://www.twitch.tv/$1 best --player mpv

View File

@ -0,0 +1,6 @@
#!/bin/zsh
selection=$(fzf)
selection=$(echo $selection | sed 's,/[^/]*$,,')
cd $selection

15
.config/scripts/util/cdto Normal file
View File

@ -0,0 +1,15 @@
#!/bin/bash
paths="
/home/meq/hdd
/home/meq/hdd/download
/home/meq/hdd/prod/projects
/home/meq/hdd/prod/ssimdsw
/home/meq/hdd/prod/resources
/home/meq/hdd/prod/splice
"
selection=$(echo $paths | fzf)
if [[ ! -z $selection ]]; then
cd $selection
fi

23
.config/scripts/util/dcalc Executable file
View File

@ -0,0 +1,23 @@
#!/bin/bash
choice=$(echo -e " " | dmenu -p "calc")
while :; do
case $choice in
"exit")
break
;;
"copy")
echo $answer | xclip -selection clipboard
break
;;
*)
answer=$(qalc $choice)
;;
esac
choice=$(echo -e "copy\nexit " | dmenu -p "$answer")
done

4
.config/scripts/util/fzfsample Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
selection=$(fzf --preview 'mpv --no-video {}' --preview-window=up:20%)
dragon-drag-and-drop --and-exit "$selection"

View File

@ -0,0 +1,7 @@
#!/bin/bash
sudo modprobe evdi initial_device_count=2
xrandr --setprovideroutputsource 1 0 --setprovideroutputsurce 2 0
echo "EXAMPLE"
echo "xrandr --addmode DVI-I-1-1 1920x1080"
echo "xrandr --output DVI-I-1-1 --mode 1920x1080 --right-of HDMI-0"
echo "x11vnc -clip 1920x1080+2560+0 -usepw -rfbport 5901 -display :0"

1
.config/zsh/.zshrc Normal file
View File

@ -0,0 +1 @@
source $ZDOTDIR/zshrc

37
.config/zsh/aliases Normal file
View File

@ -0,0 +1,37 @@
#!/bin/zsh
# time savers
alias rezsh="source ~/.config/zsh/zshrc"
alias xclipboard="xclip -selection clipboard"
alias dotfiles='/usr/bin/git --git-dir=$HOME/docs/git/dotfiles.git --work-tree=$HOME'
alias cdto='source ~/.config/scripts/util/cdto'
alias fzfcd='source ~/.config/scripts/util/cdfzf'
mpvyt() {mpv https://youtu.be/$1}
mc() {mkdir -p $1 && cd $1}
chtsh() {curl cht.sh/$1}
# applications
alias n="nvim"
alias c="clear"
alias p="pikaur"
alias nq="nvim-qt"
alias py="python"
alias hc="herbstclient"
alias pm="pulsemixer"
# flags
alias cp="cp -i -r"
alias mv="mv -i"
alias rm="rm -Ir"
alias ll="ls -lah"
alias cl="clear && ls"
alias cll="clear && ls -lah"
alias grep="grep --color=auto"
# cd ..
alias ..="cd .."
alias ...="cd ../.."
alias ....="cd ../../.."
# I actually use this one
alias yeet="pikaur -Rsn"

51
.config/zsh/zshrc Normal file
View File

@ -0,0 +1,51 @@
# prompt
autoload -U colors && colors
PS1="%B%{$fg[cyan]%}%n%{$reset_color%}@%M %~ $%b "
# vi mode
bindkey -v
# history config
HISTFILE=$XDG_CACHE_HOME/zsh/histfile
HISTSIZE=5000
SAVEHIST=5000
setopt histignorespace
# history search
autoload -U history-search-end
autoload -Uz compinit && compinit
zle -N history-beginning-search-backward-end history-search-end
zle -N history-beginning-search-forward-end history-search-end
bindkey "^[[A" history-beginning-search-backward-end
bindkey "^[[B" history-beginning-search-forward-end
#
# paths & variables
#
export PATH=$HOME/.local/bin:$PATH
export PATH=$HOME/.local/share/go/bin:$PATH
export PATH=$HOME/tools/nvim:$PATH
export PATH=$HOME/.config/scripts/applications:$PATH
export PATH=$HOME/.config/scripts/util:$PATH
export TERM=xterm # for ssh
export EDITOR=nvim
#
# cleanup
#
# these are useless to me
rm -rf ~/.thumbnails
rm -rf ~/.nv
rm -rf ~/.tooling
rm -rf ~/.wget-hsts
rm -rf ~/.java
rm -rf ~/.rubberband.wisdom.d
rm -rf ~/.urxvt
rm -rf ~/.python_history
rm -rf ~/.pylint.d
# sources
source $ZDOTDIR/aliases
source $XDG_DATA_HOME/zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh