A Weird Imagination

Move window to current desktop

The problem#

Previously, I wrote a script for opening and immediately focusing xfce4-appfinder. But xfce4-appfinder will notice if it's already open and just assume you want to use the existing window. Even if it's on a different desktop. And therefore attempting to focus it will either do nothing or switch to that desktop, neither of which is desirable.

The solution#

The actual solution I settled on is more of a workaround than a solution: having xfce4-appfinder on a different desktop doesn't really make sense, so I just set it to be on all desktops (specified as the non-existent desktop -1), so it would never be on the wrong desktop:

xdotool set_desktop_for_window "$winid" -1

To actually move the window to the current desktop, replace xdotool windowactivate "$winid" with

desktop="$(xdotool get_desktop)"
win_desktop="$(xdotool get_desktop_for_window "$winid")"
if [[ "$desktop" == "$win_desktop" ]]
then
  xdotool windowactivate "$winid"
else
  xdotool set_desktop_for_window "$winid" -1
  xdotool set_desktop_for_window "$winid" "$desktop" \
          windowactivate "$winid"
fi

Although this assumes that you know the $winid of the window you want to move. If you have just the title it works just as well to use

wmctrl -R "Application Finder"

The details#

Read more…

Killing pipes from within

The problem#

Last week, I mentioned that I needed a hack to kill xprop that seemed like it should be unnecessary. Specifically, I had its output piped to a Bash while read loop and once that had found a line to act on, there was no further need to get more lines from xprop, but break or exit didn't result in xprop exiting.

The solution#

Use $BASHPID to get the actual PID of the subshell, ps to climb the process tree to the appropriate parent and pkill to kill its children:

some_pipeline |
  while read -r line
  do
    # Do whatever until ready to kill the pipe...
    # ... then kill it:
    ppid=$BASHPID
    ppid=$(ps -o ppid:1= "$ppid")
    pkill -9 -P "$ppid" 
  done

Rewrite appfinder-and-focus.sh from last time:

#!/usr/bin/bash
xprop -spy -root _NET_CLIENT_LIST | stdbuf -oL head -2 |
  while read -r l
  do
    winid="${l/#*, /}"
    if [[ $(xdotool getwindowname "$winid") == \
      "Application Finder" ]]
    then
      xdotool windowactivate "$winid"
      ppid=$BASHPID
      ppid=$(ps -o ppid:1= "$ppid")
      pkill -9 -P "$ppid" 
    fi
  done) 2>/dev/null &
xfce4-appfinder &

The details#

Read more…

Force focus new window immediately

The problem#

I have my window manager set to not focus new windows because I dislike having a new window pop up while typing and having the keystrokes surprisingly sent to the new window instead of the one I thought I was typing in. While this is usually what I want, this does mean extra clicks when I did mean to open the new window.

This is particularly bad for xfce4-appfinder (or any other application launcher), since the purpose to be able to set a global keyboard shortcut like Super+Space so you can press that combination and quickly type in the application or action you want (or, even better, type just the first few characters of its name). And since it's being intentionally launched by a keyboard shortcut, there's no real concern of it grabbing keyboard focus unexpectedly.

The solution#

Put the following script in a file appfinder-and-focus.sh and set the keyboard shortcut to run it instead of just running xfce4-appfinder directly:

#!/usr/bin/bash
(xprop -spy -root _NET_CLIENT_LIST | stdbuf -oL tail -n +2 |
  while read -r line
  do
    winid="${line/#*, /}"
    if [[ $(xdotool getwindowname "$winid") == \
        "Application Finder" ]]
    then
      xdotool windowactivate "$winid"
    fi
  done) 2>/dev/null &
xfce4-appfinder &

# Wait for window to appear, then kill xprop.
xdotool search --sync --name "Application Finder" >/dev/null
pkill -P "$(jobs -p %1)"

The details#

Read more…

Finding broken {filename} links

Posted in

The problem#

I've recently been writing more series of blog posts or otherwise linking between posts using {filename} links. And also I've been adjusting the scheduling of my future planned blog posts, which involves changing the filename as my naming scheme includes the publication date in the filename. Which means there's opportunities for not adjusting the links to match and ending up with broken links between posts.

Pelican does generate warnings like

WARNING  Unable to find './invalid.md', skipping url        log.py:89
         replacement.                                                

but currently building my entire blog takes about a minute, so I generally only do it when publishing. So I wanted a more lightweight way to just check the intra-blog {filename} links.

The solution#

I wrote the script check_filename_links.sh:

#!/bin/bash

content="${1:-.}"

find "$content" -iname '*.md' -type f -print0 | 
  while IFS= read -r -d '' filename
  do
    grep '^\[.*]: {filename}' "$filename" |
      sed 's/^[^ ]* {filename}\([^\#]*\)\#\?.*$/\1/' |
      while read -r link
      do
        if [ "${link:0:1}" != "/" ]
        then
          linkedfile="$(dirname "$filename")/$link"
        else
          linkedfile="$content$link"
        fi
        if [ ! -f "$linkedfile" ]
        then
          echo "filename=$filename, link=$link,"\
               "file does not exist: $linkedfile"
        fi
      done
  done

Run it from your content/ directory or provide the path to the content/ directory as an argument and it will print out the broken links:

filename=./foo/bar.md, link=./invalid.md, file does not exist: ./foo/./invalid.md

The details#

Read more…

Recreate moves from zfs diff

Posted in

The problem#

When doing an incremental backup, any moved file on the source filesystem usually results in recopying the file to the destination filesystem. For a large file this can both be slow and possibly waste space if the destination keeps around deleted files (e.g. ZFS holding on to old snapshots). If both sides are ZFS, then you can get zfs send/recv to handle all of the details efficiently. But if only the source filesystem is ZFS or the ZFS datasets are not at the same granularity on both sides, that doesn't apply.

zfs diff gives the information about file moves from a snapshot, but its output format is a little awkward for scripting.

The solution#

Download the script I wrote, zfs-diff-move.sh and run it like

zfs-diff-move.sh /path/ /tank/dataset/ tank/dataset@base @new

The following is an abbreviated version of it:

#!/bin/bash
zfs diff -H "$3" "$4" | grep '^R' | while read -r line
do
  get_path() {
    path="$(echo -e "$(echo "$line" | cut -d$'\t' "-f$3")")"
    echo "${path/#$2/$1}"
  }

  from="$(get_path "$1" "$2" 2)"
  to="$(get_path "$1" "$2" 3)"
  mkdir -vp -- "$(dirname "$to")"
  mv -vn -- "$from" "$to" || echo "Unable to move $from"
done

The details#

Read more…

Hardlink identical directory trees

Posted in

The problem#

I will often make copies of important files onto multiple devices, and then later make backups of all of those devices onto the same drive. At which point, I now have multiple redundant copies of those files within my backup. Tools like rdfind, fdupes, and jdupes exist to deal with the general problem of searching a collection of files for duplicates efficiently, but none of them support only checking if files are identical if their filenames and/or paths match, so they end up doing a lot of extra work in this case.

The solution#

Download the script I wrote, hardlink-dups-by-name.sh and run it as follows:

hardlink-dups-by-name.sh a_backup/ another_backup/

Then all files like a_backup/some/path that are identical to the corresponding file another_backup/some/path will get hard-linked together so there will only be one copy of the data taking up space.

The details#

Read more…

Kill child jobs on script exit

Posted in

The problem#

When writing a shell script that starts background jobs, sometimes running those jobs past the lifetime of the script doesn't make sense. (Of course, sometimes background jobs really should keeping going after the script completes, but that's not the case this post is concerned with.) In the case that either the background jobs are used to do some background computation relevant to the script or the script can conceptually be thought of as a collection of processes, it makes sense for killing the script to also kill any background jobs it started.

The solution#

At the start of the script, add

cleanup() {
    # kill all processes whose parent is this process
    pkill -P $$
}

for sig in INT QUIT HUP TERM; do
  trap "
    cleanup
    trap - $sig EXIT
    kill -s $sig "'"$$"' "$sig"
done
trap cleanup EXIT

If you really want to kill only jobs and not all child processes, use the kill_child_jobs() function from all.sh or look at the other versions in the kill-child-jobs repository.

The details#

Read more…

Making :w work everywhere

Posted in

The problem#

After using Vim as my primary editor for a while, I find myself trying to use vi-style keyboard shortcuts in other contexts. Usually resulting in :w in middle of whatever I was writing as saving is a natural thing to do when pausing.

There's a few ways to fix this:

  1. Get used to the fact that not everything supports vi-style keyboard shortcuts.
  2. Change the keyboard shortcuts of the programs I do use.
  3. Use Vim for everything.

The first option is suboptimal because vi-style keyboard shortcuts are very useful. Luckily, in many cases there's ways to get them.

Read more…

Which command will be run?

Posted in

The problem#

While trying to develop a modification to the Pelican source, I was unexpectedly having my installed version of Pelican get run instead of the local version:

$ which pelican
/usr/local/bin/pelican
$ command -v pelican
/usr/bin/pelican

For some reason, which was pointing to the executable I was expecting bash to run, but the Bash builtin command was telling me that bash was running the installed version instead.

The solution#

Use the hash builtin to clear bash's cache of the location of pelican:

$ hash -d pelican
$ command -v pelican
/usr/local/bin/pelican

Read more…

Reverse sequence for tr

The problem#

If you take the word wizard, reverse the order of the letters and reverse the alphabet:

From: abcdefghijklmnopqrstuvwxyz
To:   ZYXWVUTSRQPONMLKJIHGFEDCBA

then you get the word wizard back, an observation made at least as early as 1972.

Now let's write a shell script to verify this so we can find other words with similar interesting properties. The obvious shell script to verify this

echo wizard | tr a-z z-a | rev

unfortunately fails with the error

tr: range-endpoints of 'z-a' are in reverse collating sequence order

The error is by design: it's not clear what a sequence in reverse order should mean, so POSIX actually requires that it not work.

Read more…