This blog has my random thoughts; game-related posts go here.

Monday, November 07, 2016

Org-mode 9 was just released, and it changed the syntax for export blocks. I need to change:

#+begin_html
…
#+end_html

to

#+begin_export html
…
#+end_export

The org-mode changes file includes some elisp to change this. However, I couldn't get it to work, and I also wanted to change all my files, not run this elisp on one file at a time. Here are the commands I ran:

ack -l --type-add=org:ext:org --org '#.begin_html' | xargs -n 1 perl -pi -e 's/#\+end_html/#+end_export/'
ack -l --type-add=org:ext:org --org '#.begin_html' | xargs -n 1 perl -pi -e 's/#\+begin_html/#+begin_export html/'

I only needed this for html, but you may need to extend this for other types you use. See what else you use with:

ack --type-add=org:ext:org --org '#.begin_(html|ascii|latex|odt|markdown|md|org|man|beamer|texinfo|groff|koma-letter)'

Labels: ,

Monday, November 07, 2016

Idea: when at at Chinese restaurant, use facial recognition and/or tap into vast databases of personal information linked to your credit card to gather lots of information about the people at the table, then use deep neural networks to write a custom fortune for each person, then use focused microwaves to print these on blank pieces of heat-sensitive paper embedded inside fortune cookies. Then when you get your fortune cookie, it'll be personalized just for you. Maybe it could be a targeted ad!

Labels:

Friday, August 12, 2016

On Mac OS X, there's a fantastic tool called Karabiner (previously KeyRemap4MacBook) that lets you control how your keyboard and mouse work — things like keyboard repeat rate, mouse acceleration, arrow keys, function keys, control/escape/shift/option/fn, adding keys to enter unicode characters, and many other things. I use it primarily for remapping the function keys. On my laptop, I want F1,F2 to control brightness, F3,F4,F5,F6,F7,F8,F9 to be function keys, and F10,F11,F12 to control volume. Mac OS X offers in the System Preferences a choice of having them all be function keys or all be special functions. I want five to be special functions and seven to be function keys. Karabiner lets me do that.

Karabiner's standard settings dialog lets me swap just some of these keys. First, I told Mac OS X to make them all function keys. Then in Karabiner, I selected

  • Change F1..F19 Key & Functional Key
    • Change Functional Key
      • Fn+Functional Keys to F1..F12
        • [X] Fn+Brightness Adjust to F1,F2
        • [X] Fn+Speaker Controls to F10,F11,F12
    • Change F1..F19 Key
      • F1..F12 to Functional Keys
        • [X] F1,F2 to Brightness Adjust
        • [X] F10,F11,F12 to Speaker Controls

Great! It now behaves how I want.

Except… I want more! Once I got an external keyboard, I wanted the function keys to behave differently. The external keyboard has its own volume keys, so I don't want them swapped there. I use it with an external display, so the brightness keys aren't useful to swap.

Karabiner offers an XML configuration file when the standard settings dialog isn't enough. Reading the XML file format documentation, I saw that I can restrict certain settings to only run in some apps or some devices. I came up with this equivalent for the above settings (and then later realized all the built-in settings have XML files like this one):

  <item>
    <name>Swap F1,F2 with Brightness keys</name>
    <identifier>amitp.brightness</identifier>
    <autogen>__KeyToKey__ KeyCode::F1, ConsumerKeyCode::BRIGHTNESS_DOWN</autogen>
    <autogen>__KeyToKey__ KeyCode::F2, ConsumerKeyCode::BRIGHTNESS_UP</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::BRIGHTNESS_DOWN, KeyCode::F1</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::BRIGHTNESS_UP, KeyCode::F2</autogen>
  </item>
  <item>
    <name>Swap F10,F11,F12 with Volume keys</name>
    <identifier>amitp.volume</identifier>
    <autogen>__KeyToKey__ KeyCode::F10, ConsumerKeyCode::VOLUME_MUTE</autogen>
    <autogen>__KeyToKey__ KeyCode::F11, ConsumerKeyCode::VOLUME_DOWN</autogen>
    <autogen>__KeyToKey__ KeyCode::F12, ConsumerKeyCode::VOLUME_UP</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::VOLUME_MUTE, KeyCode::F10</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::VOLUME_DOWN, KeyCode::F11</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::VOLUME_UP, KeyCode::F12</autogen>
  </item>

However, I want to swap these keys only on the laptop keyboard. I used Karabiner's Event Viewer helper application to figure out that my laptop keyboard's device id was 0x0259. I could then modify the settings to only apply to my laptop keyboard:

  <deviceproductdef>
    <productname>Apple_laptop</productname>
    <productid>0x0259</productid>
  </deviceproductdef>
  <item>
    <name>Swap F1,F2 with Brightness keys</name>
    <appendix>For laptop keyboard only</appendix>
    <identifier>amitp.brightness</identifier>
    <device_only>DeviceVendor::APPLE_COMPUTER, DeviceProduct::Apple_laptop</device_only>
    <autogen>__KeyToKey__ KeyCode::F1, ConsumerKeyCode::BRIGHTNESS_DOWN</autogen>
    <autogen>__KeyToKey__ KeyCode::F2, ConsumerKeyCode::BRIGHTNESS_UP</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::BRIGHTNESS_DOWN, KeyCode::F1</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::BRIGHTNESS_UP, KeyCode::F2</autogen>
  </item>
  <item>
    <name>Swap F10,F11,F12 with Volume keys</name>
    <identifier>amitp.volume</identifier>
    <appendix>For laptop keyboard only</appendix>
    <device_only>DeviceVendor::APPLE_COMPUTER, DeviceProduct::Apple_laptop</device_only>
    <autogen>__KeyToKey__ KeyCode::F10, ConsumerKeyCode::VOLUME_MUTE</autogen>
    <autogen>__KeyToKey__ KeyCode::F11, ConsumerKeyCode::VOLUME_DOWN</autogen>
    <autogen>__KeyToKey__ KeyCode::F12, ConsumerKeyCode::VOLUME_UP</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::VOLUME_MUTE, KeyCode::F10</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::VOLUME_DOWN, KeyCode::F11</autogen>
    <autogen>__KeyToKey__ ConsumerKeyCode::VOLUME_UP, KeyCode::F12</autogen>
  </item>

It works great!

I also solved another annoyance. My external keyboard always sends number keys from the number pad, and I'd rather send arrow keys. Karabiner can do that too. I had to find the device vendor and product id from the event viewer, and then I used these settings:

  <devicevendordef>
    <vendorname>KBTalking</vendorname>
    <vendorid>0x099a</vendorid>
  </devicevendordef>

  <deviceproductdef>
    <productname>Bluetooth_keyboard</productname>
    <productid>0x0100</productid>
  </deviceproductdef>
  
  <item>
    <name>Amit's Keypad</name>
    <appendix>Amit's keypad settings</appendix>
    <identifier>amitp.keypad</identifier>
    <device_only>DeviceVendor::KBTalking, DeviceProduct::Bluetooth_keyboard</device_only>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_5, KeyCode::F19</autogen>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_2, KeyCode::CURSOR_DOWN</autogen>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_4, KeyCode::CURSOR_LEFT</autogen>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_6, KeyCode::CURSOR_RIGHT</autogen>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_8, KeyCode::CURSOR_UP</autogen>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_1, KeyCode::END</autogen>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_3, KeyCode::PAGEDOWN</autogen>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_7, KeyCode::HOME</autogen>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_9, KeyCode::PAGEUP</autogen>
    <autogen>__KeyToKey__ KeyCode::KEYPAD_DOT, KeyCode::FORWARD_DELETE</autogen>
  </item>

These small things have made my computing more pleasant. There's so much more that Karabiner can do, but I haven't explored most of its capabilities.

P.S. If you find Karabiner useful, consider donating.

Labels: ,

Thursday, July 28, 2016

In the U.S. we have 1¢ 5¢ 10¢ 25¢ 50¢ coins. We have $1 $2 $5 $10 $20 $50 $100 bills. The EU has 1c 2c 5c 10c 20c 50c €1 €2 for coins and €5 €10 €20 €50 €100 €200 €500 for bills.

I think there are too many types. I don't use 10¢, 50¢ coins, nor do I use $2 $10 $50 bills. Why?

I think what I want is for each size to be 4 or 5 times as big as the previous size. My simplicity-preferring brain doesn't like to see a $2 bill when two $1 bills is not that much more to carry. I don't like to use a $10 bill instead of two $5 bills. Carrying five $1 bills is a bit much and then I'm happy to carry a $5 bill instead. Two or three isn't enough to trigger this desire; four or five is enough.

If we started with 1¢ and stepped up by a factor of 4 or 5 each time, then the sequence would be 1¢ 5¢ 25¢ $1 $5 $20 $100. These are the ones I actually use in practice. For Euros we could have 1c 5c 20c €1 €5 €20 €100 €500.

Let's drop 10¢ coins and $10 bills and treat them like 50¢ coins, $2 bills, $50 bills — they exist but we don't use them widely.

Labels:

Wednesday, July 27, 2016

Back in 2014 I posted my emacs setup for finding files globally. It acts like switch-to-buffer, if all the files on my system were already open in buffers. I can type in a substring of any filename I'm likely to work on and switch to that buffer. Since them, helm has changed its interface, and I've updated my code, adding a few extra quality-of-life features.

I use the same hourly cron job to take inventory of all files I am likely to edit, putting recently modified files near the top, as they are most likely to be the best match. I save this list to ~/.global-file-list.txt.

What's changed is how I set up helm. In 2014 I was using helm-recentf and temporarily binding the recentf file list to my own list. This was a quick & dirty way to do it, and it worked, except when I opened a file, it didn't get put onto the recentf list, since I had let-bound that list to something else. Oops. I learned more helm (by reading the source code and John Kitchin's blog posts) and now construct my own helm source instead of abusing helm-recentf:

(defun amitp/helm-all-files ()
  "Global filename match, over all files I typically open"
  (interactive)
  (helm
   :sources '(amitp/helm-source-my-files helm-source-locate)
   :buffer "*helm all files*"))

Helm will look at this source to get the filename list:

(defvar amitp/helm-source-my-files
  (helm-build-sync-source "My files"
    :candidates #'amitp/helm-global-file-list
    :filtered-candidate-transformer #'amitp/helm-filter-my-files
    :keymap helm-generic-files-map
    :action 'helm-type-file-actions))

This source tells helm to call amitp/helm-global-file-list. This function returns the most recently modified files at the top, then the currently open files, then files in the current folder, then recently opened files, then the global file list. (I'm not happy with this order and am still experimenting.) Some filenames will be in more than one list so I eliminate duplicates.

(defun amitp/helm-global-file-list ()
  "Files to list in amitp/helm-all-files"
  ;; delete-dups much faster than cl-remove-duplicates
  (delete-dups
   (mapcar 'abbreviate-file-name
           (append
            (read-file-into-lines "~/.recent-file-list.txt")
            (amitp/buffer-file-names)
            (helm-skip-boring-files
             (directory-files default-directory t))
            recentf-list
            amitp/global-file-list))))

I also use a filter to remove some filenames from the list. Why? Normally I want to show only the “source” and not the “compiled” version of something. For example I want to show .el files but not .elc files. I want to show .c files but not .o files. I can hide these with helm-boring-file-regexp-list. However, for my web pages, some of them are directly written as .html files (*.html should be included) but others are written in org-mode or markdown or something else (*.html should be excluded). To decide whether a file ending in .html is source or not, I need to look at the other filenames in the list. I use a “filtered candidate transformer” (see the documentation for helm-source) to take the currently matching filenames and filter them further. If the filenames contain both $something.org and $something.html then I know that the html is not a source file, so I hide it.

(defun dominated-by-filename (regexp replacement filename candidates)
  "True if FILENAME with REGEXP replaced by REPLACEMENT is already in CANDIDATES"
  (let ((new-filename (replace-regexp-in-string regexp replacement filename)))
    (and (not (equal new-filename filename))
         (member new-filename candidates))))

(defun amitp/helm-filter-my-files (candidates _source)
  "Ignore a build target if a build source exists in the candidates"
  (cl-loop for filename in candidates
      unless
      (or
       (dominated-by-filename "\\.html$" ".org" filename candidates)
       (dominated-by-filename "\\.html$" ".md" filename candidates))
      ;; (I have more rules but you get the idea)
      collect filename))

I don't currently colorize the output. I've considered coloring by type (source code, prose, build file, etc.) and by origin (recentf, current folder, open buffer, global list) but neither of these seems particularly appealing. I'll continue to experiment.

The last change since 2014 is that my global file list doesn't have everything, and it is annoyingly missing any new files I've created in the past hour. In 2014 I set up C-l to switch to helm-locate so that if I was unable to find what I wanted, I could have it search more. However, I never remembered to use it.

I now have two ways to solve that problem. First, I augment my hourly cron job with a quick cron job that runs every minute, and reports back any files edited recently. I had started with any files added recently but realized if I changed it to files edited recently, I could put those files at the top of my list. I run this every minute:

mdfind -onlyin $HOME "kMDItemFSContentChangeDate > \$time.now(-7200)
   && kMDItemContentTypeTree = 'public.text'" >$HOME/.recent-file-list.txt

In the helm source, I read ~/.recent-file-list.txt (a very short list) and put those items ahead of others.

The second solution is to use helm-source-locate as a source in the main interface, even though the UI is incompatible (for regular results you can use space to separate words, but for locate you can't). It will show the locate results below the main results. For times when my global file list and my recent file list don't show anything, locate might find it.

(setq helm-locate-command "/Users/amitp/bin/locate %.0s %s")

I use Mac's mdfind (which is updated in real time) to find the base filename and then I filter that through grep to handle folders. For example, if I run locate foo/bar then I use mdfind to find files named bar, then in the results I grep for foo/bar. However if I run locate ar without a folder name then I should find bar as a substring match. The shell script isn't perfect but it's “good enough” for now.

#!/bin/bash
rawquery="$*"
suffix=$(basename -- "$rawquery")
pattern="${suffix}*"

if [ "$rawquery" = "$suffix" ]; then
    # If there's no folder then it should be a substring
    pattern="*${suffix}*"
    # NOTE: double wildcards are slower!
fi

mdfind -onlyin $HOME "kMDItemFSName = '$pattern'cd" \
    | sed -e "s:^$HOME:~:" \
    | fgrep -i -- "$rawquery" \
    | postprocess

So far I've not needed the locate results, so I might end up removing that part of the code. I think the minutely cron job might be all I need.

I'm much happier with my setup now compared to the original, but there's always more tweaking to do. It's Emacs after all!

Labels: ,