diff --git a/config/i3/clipmenu b/config/i3/clipmenu new file mode 100755 index 0000000..89935a1 --- /dev/null +++ b/config/i3/clipmenu @@ -0,0 +1,52 @@ +#!/bin/bash + +shopt -s nullglob + +# We use this to make sure the cache files are sorted bytewise +LC_COLLATE=C + +# Some people copy/paste huge swathes of text that could slow down dmenu +line_length_limit=500 + +declare -A selections +ordered_selections=() + +files=("$HOME/.clipmenu/"*) + +# We can't use `for ... in` here because we need to add files to +# ordered_selections from last to first -- that is, newest to oldest. Incoming +# clipboard entries have a ISO datetime prefixed to the front to aid in this. +for (( i=${#files[@]}-1; i>=0; i-- )); do + file=${files[$i]} + + # We look for the first line matching regex /./ here because we want the + # first line that can provide reasonable context to the user. That is, if + # you have 5 leading lines of whitespace, displaying " (6 lines)" is much + # less useful than displaying "foo (6 lines)", where "foo" is the first + # line in the entry with actionable context. + first_line=$(sed -n '/./{p;q}' "$file" | cut -c1-"$line_length_limit") + lines=$(wc -l < "$file") + + if (( lines > 1 )); then + first_line+=" ($lines lines)" + fi + + ordered_selections+=("$first_line") + selections[$first_line]=$file +done + +# It's okay to hardcode `-l 8` here as a sensible default without checking +# whether `-l` is also in "$@", because the way that dmenu works allows a later +# argument to override an earlier one. That is, if the user passes in `-l`, our +# one will be ignored. +chosen_line=$(printf '%s\n' "${ordered_selections[@]}" | uniq | $HOME/.config/i3/dmenu_cmd -l 8 -p "Copy" "$@") + +[[ $chosen_line ]] || exit 1 + +for selection in clipboard primary; do + if type -p xsel >/dev/null 2>&1; then + xsel --logfile /dev/null -i --"$selection" < "${selections[$chosen_line]}" + else + xclip -sel "$selection" < "${selections[$chosen_line]}" + fi +done diff --git a/config/i3/clipmenud b/config/i3/clipmenud new file mode 100755 index 0000000..98edd08 --- /dev/null +++ b/config/i3/clipmenud @@ -0,0 +1,127 @@ +#!/bin/bash + +hr_msg() { + printf -- '\n--- %s ---\n\n' "$1" >&2 +} + +debug() { + if (( DEBUG )); then + printf '%s\n' "$@" >&2 + fi +} + +print_debug_info() { + # DEBUG comes from the environment + if ! (( DEBUG )); then + return + fi + + local msg="${1?}" + + hr_msg "$msg" + + hr_msg Environment + env | LC_ALL=C sort >&2 + + cgroup_path=/proc/$$/cgroup + + if [[ -f $cgroup_path ]]; then + hr_msg cgroup + cat "$cgroup_path" >&2 + else + hr_msg 'NO CGROUP' + fi + + hr_msg 'Finished debug info' +} + +print_debug_info 'Initialising' + +cache_dir=$HOME/.clipmenu/ + +# It's ok that this only applies to the final directory. +# shellcheck disable=SC2174 +mkdir -p -m0700 "$cache_dir" + +declare -A last_data +declare -A last_filename + +while sleep "${CLIPMENUD_SLEEP:-0.5}"; do + print_debug_info 'About to run selection' + + for selection in clipboard primary; do + print_debug_info "About to do selection for '$selection'" + + if type -p xsel >/dev/null 2>&1; then + debug 'Using xsel' + data=$(xsel --logfile /dev/null -o --"$selection"; printf x) + else + debug 'Using xclip' + data=$(xclip -o -sel "$selection"; printf x) + fi + + debug "Data before stripping: $data" + + # We add and remove the x so that trailing newlines are not stripped. + # Otherwise, they would be stripped by the very nature of how POSIX + # defines command substitution. + data=${data%x} + + debug "Data after stripping: $data" + + if [[ $data != *[^[:space:]]* ]]; then + debug "Skipping as clipboard is only blank" + continue + fi + + if [[ ${last_data[$selection]} == "$data" ]]; then + debug 'Skipping as last selection is the same as this one' + continue + fi + + # If we were in the middle of doing a selection when the previous poll + # ran, then we may have got a partial clip. + possible_partial=${last_data[$selection]} + if [[ $possible_partial && $data == "$possible_partial"* ]]; then + debug "$possible_partial is a possible partial of $data" + debug "Removing ${last_filename[$selection]}" + rm -- "${last_filename[$selection]}" + fi + + filename="$cache_dir/$(LC_ALL=C date +%F-%T.%N)" + + last_data[$selection]=$data + last_filename[$selection]=$filename + + debug "Writing $data to $filename" + printf '%s' "$data" > "$filename" + + if ! (( NO_OWN_CLIPBOARD )) && [[ $selection != primary ]]; then + # Take ownership of the clipboard, in case the original application + # is unable to serve the clipboard request (due to being suspended, + # etc). + # + # Primary is excluded from the change of ownership as applications + # sometimes act up if clipboard focus is taken away from them -- + # for example, urxvt will unhilight text, which is undesirable. + # + # We can't colocate this with the above copying code because + # https://github.com/cdown/clipmenu/issues/34 requires knowing if + # we would skip first. + if type -p xsel >/dev/null 2>&1; then + xsel --logfile /dev/null -o --"$selection" | xsel -i --"$selection" + else + xclip -o -sel "$selection" | xclip -i -sel "$selection" + fi + fi + + if ! (( NO_TRANSFER_CLIPBOARD )) && [[ $selection != primary ]]; then + # Copy every clipboard content into primary clipboard + if type -p xsel >/dev/null 2>&1; then + xsel --logfile /dev/null -o --"$selection" | xsel -i --primary + else + xclip -o -sel "$selection" | xclip -i -sel primary + fi + fi + done +done diff --git a/config/i3/config b/config/i3/config index b53f462..e18e78f 100644 --- a/config/i3/config +++ b/config/i3/config @@ -38,6 +38,7 @@ bindsym $mod+F2 exec --no-startup-id ~/.config/i3/dmenu_run bindsym Mod1+F2 exec --no-startup-id ~/.config/i3/dmenu_run bindsym $mod+c exec --no-startup-id ~/.config/i3/passmenu +bindsym $mod+x exec --no-startup-id ~/.config/i3/clipmenu bindsym $mod+asterisk exec --no-startup-id ~/.config/i3/sshmenu bindsym $mod+dollar exec --no-startup-id ~/.config/i3/sshmenu root @@ -246,7 +247,7 @@ for_window [window_role="task_dialog"] floating enable for_window [urgent=latest] focus # focus urgent window -bindsym $mod+x [urgent=latest] focus +#bindsym $mod+x [urgent=latest] focus # reload the configuration file bindsym $mod+Shift+c reload @@ -377,6 +378,7 @@ exec --no-startup-id numlockx on # Activate Num lock #exec --no-startup-id conky -c $HOME/.conky/status # Desktop widget exec --no-startup-id unclutter # Hide mouse cursor after some time exec --no-startup-id dunst # Notifications +exec --no-startup-id $HOME/.config/i3/clipmenud # Clipboard manager # Autostart programs #exec --no-startup-id i3-msg 'workspace $WS8; exec firefox --new-window tweetdeck.twitter.com' diff --git a/config/i3/dmenu_cmd b/config/i3/dmenu_cmd index 139f7d3..e1d3599 100755 --- a/config/i3/dmenu_cmd +++ b/config/i3/dmenu_cmd @@ -1,2 +1,2 @@ #!/bin/sh -dmenu -fn 'DejaVu Sans Mono-8' -nb '#222222' -nf '#888888' -sb '#4E9C00' -sf '#FFFFFF' "$@" +dmenu -fn 'DejaVu Sans Mono-10' -nb '#222222' -nf '#888888' -sb '#4E9C00' -sf '#FFFFFF' -l 8 -f -i -h 19 "$@"