QZ qz thoughts
a blog from Eli the Bearded
Tag search results for how-to Page 1 of 1

Simple Book Binding


Stack of bound PDF print-outs

I like printed documentation. You can add sticky bookmarks, highlight material (I typically use colored pencils for that), annotate the text, and have multiple things open at once easily.

I'd really like to find an easy way to turn a Gutenberg Project book into a decently bound volume through some print on demand service. Getting things like page numbers, headers, footers, and proper margins (need more space at spine than at fore edge!), to say nothing of cover, illustrations, and table of contents makes it hard.

But for smaller works, say things that can print on a few dozen sheets of duplexed paper, that's achievable. First get the document ready for printing. Things that start as PDFs are usually good. Things that are HTML pages are sometimes good, and sometimes need the HTML paired down some.

I print my stuff on a "business" Brother laser printer, the HL L6200DW. In general the monochrome laser printers (single purpose, as opposed to "all in one" scanner/copier/printer ones which are more hit-and-miss) from Brother are a good deal. Thanks to the large amount of printing my wife does for her business, we've run through quite a few. After about 60,000 pages the replacement parts needed start to cost more than buying a new one. Most people won't have that problem. The Brother printers are cheap and Just Work(TM) with Mac, Windows, and Linux. And they print double sided (duplex) automatically.

So print the material duplexed and then neatly collate the output. If you've got less than 25 sheets (50 pages), an ordinary stapler will work to bind the edges. If you have more than that, you need a heavier duty stapler and extra long staples. Swingline makes a few options, but be sure to be careful about staple selection.

Staple close to the edge, six or seven times along the spine. This will make the printout function like a book.

Binding process

Then use a medium thick tape to cover the stapes and bound edge. You can use one peice wide enough to fold around, or multiple strips. This is important to prevent the staples from catching on things (eg fingers) and improves the look of the binding considerably.

You can quite easily print some parts on larger paper and fold them so only one edge staples in. For my Vectrex Service Manual here, I printed the schematics single sided on legal (8.5"x14" paper) in landscape orientation. This is probably close to how the original was done.

Extra wide foldout sheets

Manual restore of Firefox sessions


Recently I needed to downgrade my Firefox install from 87 to 84 due to a bug. With multiple windows open, I was intermitantly finding some events were being sent to the wrong window. An example is open a new (private) window and start to navigate to a new page, but discover mouse clicks and other events were still being sent to the original window. I started seeing this in FF86 and FF87 did not fix it. By that point the bug was testing my patience, and worse FF87 seemed to have new (unrelated) bugs specifically with one site I use.

So downgrade.

It's been a long time since some Firefox bug has been serious enough to force me to install a different version, particularly a downgrade. So I was surprised to find that Firefox now marks every profile with a browser version and does not let older browsers use profiles from newer ones. Originally when profiles introduced compatibility changes it was somewhat infrequent, and downgrades were usually easy to do.

There are three things I really wanted to preserve:

  1. My open tabs (actual state in the tab, not as critical).
  2. My (few) bookmarks. Of 40 or so bookmarks, about 30 I had created (the others shipped with Firefox), and about half of those I still want.
  3. My preferences.

These have varying degrees of difficulty uncovering from the files in a profile. Session tabs ended up being the most complicated. Working backwards in that list.

Changed preferences are stored one-per-line in prefs.js in the profile directory. The chief complication is the number of entries. Many of them are related to extensions or printing and can just be ignored. Many more are related to internal settings like toolkit.telemetry.* or most of the browser.* ones.

The bookmarks are available in compressed JSON files in the bookmarkbackups profile subdirectory. The compression format is a bit of an oddball. The actual compression type is not so rare, LZ4, but apparently the Mozilla implementation is slightly non-standard. There are a lot of tools out there which can apply the standard LZ4 algorithm to the .jsonlz4 files Firefox makes. I used lz4jsoncat by Andi Kleen. Here ls -rt finds the most recent backup file to operate on.

lz4jsoncat $(ls -rt bookmarkbackups/*.jsonlz4) | jq . | grep '"uri"'

If you are unaware, jq is a JSON Query tool. In the simple usage there, the query is for the whole structure and functions as a JSON pretty printer. Why not use jq to slice and dice the uri entries out? Because they exist in several groups (probably for bookmark folders) and grep was way faster than figuring out the correct jq query to use.

But that brings us to the remaining item of interest for me: the URLs of all my tabs in the session data. There are several files in the sessionstore-backups profile subdirectory. In my inspection they all appeared to be valid sessions from different times, and recovery.jsonlz4 seemed to be the most recent one. No need to resort to time sorted file lists here.

The JSON structure holds a lot of data, from cookies to apparently thumbnail images of the page (base64 encoded for safely storing in JSON). Getting just tab URLs was not easily done with grep, and I needed to work out a jq query. There is a windows[] array with an entry for each window, inside that there is a tabs[] array with a state for each tab, and inside that an entries[] array with the history for each tab. The history is a stack with latest entry first.

lz4jsoncat sessionstore-backups/recovery.jsonlz4 |
    jq -r '.windows[].tabs[].entries[0].url'

For each item ([]) in windows array,
  for each item ([]) in tabs array within it,
    for the first ([0]) item in entries array within it,
      print the url field.
The -r makes the output "raw", which is to say without quotes.

Not as neat and clean as actual session recovery, but a lot better than trying to recover my two dozen tabs from memory.

udev for the win


So I have an Arduino Uno now and some parts to begin working on my alarm clock project but I'm still new to Arduino and need to get comfortable with programming it. So first thing I do is fire up the standard Arduino GUI programming tool ("IDE" in the argot of those that like such tools).

You need to be added to the "dialout" group...

And I'm ready to Ackchyually it.

Being added to the dialout group is a pretty good fix. But it's not the only way to do that, so "need" is a strong word. The advantage of being in the dialout group is it works pretty generally and without any extra tooling. The disadvantage is group membership is inherited from login, and I don't want to log out and log in again. A logout is as good as a reboot, and usually it is the reboot that forces the logout. These days I try to limit it those to once a month or less, depending on important kernel updates. It's been less than two weeks now, way too soon for that much disruption.

So here follows an alternative route with udev rules.

First I need to identify the device to apply those rules. I'll start with watching what's going on in /var/log/syslog when I plug the device in:

Nov  7 19:49:59 slate-asus kernel: [947954.307562] usb 1-3: new full-speed USB device number 8 using xhci_hcd
Nov  7 19:49:59 slate-asus kernel: [947954.458285] usb 1-3: New USB device found, idVendor=2341, idProduct=0043
Nov  7 19:49:59 slate-asus kernel: [947954.458291] usb 1-3: New USB device strings: Mfr=1, Product=2, SerialNumber=220
Nov  7 19:49:59 slate-asus kernel: [947954.458294] usb 1-3: Manufacturer: Arduino (www.arduino.cc)
Nov  7 19:49:59 slate-asus kernel: [947954.458296] usb 1-3: SerialNumber: 8593731353731234A012
Nov  7 19:49:59 slate-asus mtp-probe: checking bus 1, device 8: "/sys/devices/pci0000:00/0000:00:15.0/usb1/1-3"
Nov  7 19:49:59 slate-asus mtp-probe: bus: 1, device: 8 was not an MTP device
Nov  7 19:49:59 slate-asus kernel: [947954.532024] cdc_acm 1-3:1.0: ttyACM0: USB ACM device

That's all I really need there, but there's other ways to get that information. If you are not used to reading that log, here's the general format of what it is showing:

  • Nov 7 19:49:59 date and time in system default timezone (US Pacific for me).
  • slate-asus computer name. I uniquely name all my computers, and this one doesn't have an obvious model name on it, so color - maker.
  • kernel: / mtp-probe: the name of the component creating the message. Messages from the kernel include the uptime in seconds, eg [947954.532024], which is one week, three days, twenty-three hours, nineteen minutes and a bit over fourteen seconds. Way too soon to reboot.
  • checking bus 1, device 8: "/sys/devices/pci0000:00/0000:00:15.0/usb1/1-3 This message from the media transport protocol probe has one way to identify the device.
  • cdc_acm 1-3:1.0: ttyACM0: USB ACM device This contains the other way. cdc: communications device class; acm: abstract control model. Essentially this is a modem-like serial device on USB (the "universial serial bus"). And modem-like is why the group is dialout.
  • Another way to find the device name is look in the /dev/ directory and find the new file that was created when plugging the device in (here, /dev/ttyACM0) and have the udev system tell us what it is with the udevadm info -a -n /dev/ttyACM0 command (verbose output partially removed):

    # udevadm info -a -n /dev/ttyACM0
    Udevadm info starts with the device specified by the devpath and then
    walks up the chain of parent devices. It prints for every device
    found, all possible attributes in the udev rules key format.
    A rule to match, can be composed by the attributes of the device
    and the attributes from one single parent device.
    
      looking at device '/devices/pci0000:00/0000:00:15.0/usb1/1-3/1-3:1.0/tty/ttyACM0':
        KERNEL=="ttyACM0"
        SUBSYSTEM=="tty"
        DRIVER==""
    
      looking at parent device '/devices/pci0000:00/0000:00:15.0/usb1/1-3/1-3:1.0':
    [...]
      looking at parent device '/devices/pci0000:00/0000:00:15.0/usb1/1-3':
    [...]
      looking at parent device '/devices/pci0000:00/0000:00:15.0/usb1':
    [...]
      looking at parent device '/devices/pci0000:00/0000:00:15.0':
        KERNELS=="0000:00:15.0"
        SUBSYSTEMS=="pci"
        DRIVERS=="xhci_hcd"
        ATTRS{broken_parity_status}=="0"
        ATTRS{class}=="0x0c0330"
        ATTRS{consistent_dma_mask_bits}=="64"
        ATTRS{d3cold_allowed}=="1"
        ATTRS{dbc}=="disabled"
        ATTRS{device}=="0x5aa8"
        ATTRS{dma_mask_bits}=="64"
        ATTRS{driver_override}=="(null)"
        ATTRS{enable}=="1"
        ATTRS{irq}=="123"
        ATTRS{local_cpulist}=="0-1"
        ATTRS{local_cpus}=="3"
        ATTRS{msi_bus}=="1"
        ATTRS{numa_node}=="-1"
        ATTRS{revision}=="0x0b"
        ATTRS{subsystem_device}=="0x201f"
        ATTRS{subsystem_vendor}=="0x1043"
        ATTRS{vendor}=="0x8086"
    
      looking at parent device '/devices/pci0000:00':
    [...]
    

    We'd get the same relevant information from the other identifier using the udevadm info -a /sys/devices/pci0000:00/0000:00:15.0 command. Every single thing in that list of device info can be used as a key for trigging a udev script. The ATTRS{subsystem_device} or ATTRS{subsystem_vendor} is a good choice for being relatively specific to plugging in an Uno and getting a device.

    So now we know what to use as a trigger, let's use that to run a script. In /etc/udev/rules.d/ live *.rules files for what to do for devices. On my system the highest numbered existing rule is 70-snap.snapd.rules, so I'll pick a two digit number higher than that and create my new rules file.

    $ cat /etc/udev/rules.d/80-arduino.rules
    ATTRS{subsystem_device}=="0x201f", ACTION=="add", RUN+="/usr/local/bin/plugin uno"
    $
    

    Each line is a rule. This one says when the subsystem_device attribute is 0x201f and the action happening is an add, then add to the list of things to be run /usr/local/bin/plugin uno. The argument isn't really needed for udev, because there are enough environment variables to use for context, but could be handy for hand running it. I (or you) could match on more than one attribute by just adding them to the list: ATTRS{subsystem_vendor}=="0x1043", ATTRS{subsystem_device}=="0x201f", ...

    With the rule in place, we need to reload the udev configuration in order to have the rule take effect. This is much easier than a reboot.

    # udevadm control --reload
    #
    

    A note on prompts: in my copies of terminal output, a prompt of $ means that it is a command to be run as a regular user, and a prompt of # means it requires superuser privileges. I usually use a superuser shell, but sudo in front of the command is the common way these days.

    Now we have a rule to run something when the device is plugged in, we should figure out what to run. A stub script is very helpful here:

    $ cat /usr/local/bin/plugin
    #!/bin/sh
    
    log=/var/log/plugin-udev.log
    exec >> "$log" 2>&1     # "exec" changes stdout, etc, while running
    
    env
    $
    

    Plug the Uno in, and then look at the plugin-udev.log file.

    $ cat /var/log/plugin-udev.log
    ID_BUS=usb
    DEVNAME=/dev/ttyACM0
    ACTION=add
    ID_VENDOR_FROM_DATABASE=Arduino SA
    ID_PCI_INTERFACE_FROM_DATABASE=XHCI
    ID_SERIAL_SHORT=8593731353731234A012
    ID_USB_DRIVER=cdc_acm
    ID_USB_CLASS_FROM_DATABASE=Communications
    DEVPATH=/devices/pci0000:00/0000:00:15.0/usb1/1-2/1-2.1/1-2.1:1.0/tty/ttyACM0
    ID_MODEL_ENC=0043
    ID_USB_INTERFACES=:020201:0a0000:
    ID_MODEL=0043
    SUBSYSTEM=tty
    ID_MODEL_ID=0043
    MINOR=0
    ID_MODEL_FROM_DATABASE=Uno R3 (CDC ACM)
    ID_PATH=pci-0000:00:15.0-usb-0:2.1:1.0
    ID_MM_CANDIDATE=1
    ID_VENDOR_ENC=Arduino\x20\x28www.arduino.cc\x29
    [...]
    

    There's a lot in there, so I could use ID_MODEL_FROM_DATABASE or ID_MODEL_FROM_DATABASE instead of uno as an argument, but I'm going to stick with that method to flag what to do. But I will have DEVNAME tell me what to act on. Next version of the plugin script:

    #!/bin/sh
    
    what="$1"
    
    # device to try for "uno" if $DEVNAME is not set
    uno_dev=/dev/ttyACM0
    
    log=/var/log/plugin-udev.log 
    if [ ! -w "$log" ] ; then
      echo "$0: Intended to be run as root from udev scripts."
      exit
    fi
    
    exec 3>&1               # create 3 as copy of stdout
    exec >> "$log" 2>&1     # make orig stdout/stderr logfile
    
    now=$(date +%Y/%m/%dT%H:%M:%S)
    
    case "$1" in
      uno) device=${DEVNAME:-$uno_dev} 
           if [ -c "$device" ] ; then
             echo "$now - chowning $device now"
             chown username "$device"
           else
             echo "$now - can't find $device"
           fi
           ;;
      ?*) echo "$now - unrecognized plugin event: $1"
          echo        "unrecognized plugin event: $1" >&3
           ;;
      *)  echo "$now - need a udev event, eg 'uno'"
          echo        "need a udev event, eg 'uno'" >&3
           ;;
    esac

    I juggle stdout/stderr a bit here. First I open file descriptor 3 as a copy of stdout, then I change stdout and stderr to go to the logfile. This means anything normally sent to stdout or stderr will go in the log, and I can capture output and errors there, but because if I want to print to the original stdout (probably a terminal), I need to do it explicitly through file descriptor 3. Any program I run, will use the logfile for output but I can also print to the terminal. I use that for the unexpected usage cases. If someone with privileges runs /usr/local/bin/plugin that gets logged in the logfile and gets a message on the terminal. (Someone without privileges probably gets stopped when I check if the logfile is writable with [ -w "$log" ].)

    So, let's try it out. Plug in the USB cable from the Arduino and:

    $ ls -l /dev/ttyA*     
    crw-rw---- 1 username dialout 166, 0 Nov  8 13:39 /dev/ttyACM0
    $ groups
    username adm cdrom sudo dip plugdev lpadmin sambashare docker rvm
    $ tail -1 /var/log/plugin-udev.log
    2020/11/07T21:51:46 - chowning /dev/ttyACM0 now
    $
    

    Presto, the device "belongs" to me, I can write to it without being in group dialout.

    People often use udev rules for dealing with permissions related to thumbdrives, and there are a bunch of tutorials on doing that sort of thing, but udev is more useful and I wanted to show that.