Akkana's Musings on Open Source Computing and Technology, Science, and Nature.
Sun, 14 Sep 2014
Global key bindings in emacs. What's hard about that, right?
Just something simple like
(global-set-key "\C-m" 'newline-and-indent)
and you're all set.
global-set-key gives you a nice key binding
that works ... until the next time you load a mode that wants
to redefine that key binding out from under you.
For many years I've had a huge collection of mode hooks that run when
specific modes load. For instance, python-mode defines \C-c\C-r, my
binding that normally runs revert-buffer, to do something called run-python.
I never need to run python inside emacs -- I do that in a shell window.
But I fairly frequently want to revert a python file back to the last
version I saved. So I had a hook that ran whenever python-mode loaded
to override that key binding and set it back to what I'd already set
(defun reset-revert-buffer ()
(define-key python-mode-map "\C-c\C-r" 'revert-buffer) )
(setq python-mode-hook 'reset-revert-buffer)
That worked fine -- but you have to do it for every mode that
overrides key bindings and every binding that gets overridden.
It's a constant chase, where you keep needing to stop editing
whatever you wanted to edit and go add yet another mode-hook to
.emacs after chasing down which mode is causing the problem.
There must be a better solution.
A web search quickly led me to the StackOverflow discussion
override key bindings. I tried the techniques there; but they
It took a lot of help from the kind folks on #emacs, but after an hour
or so they finally found the key:
documented -- the key there is "The “active” keymaps in each alist
are used before minor-mode-map-alist and minor-mode-overriding-map-alist" --
and there seem to be no examples anywhere on the web for how to use it.
It's a list of alists mapping names to keymaps. Oh, clears it right up! Right?
Okay, here's what it means. First you define a new keymap and add your
bindings to it:
(defvar global-keys-minor-mode-map (make-sparse-keymap)
(define-key global-keys-minor-mode-map "\C-c\C-r" 'revert-buffer)
(define-key global-keys-minor-mode-map (kbd "C-;") 'insert-date)
Now define a minor mode that will use that keymap. You'll use that
minor mode for basically everything.
"A minor mode so that global key settings override annoying major modes."
t "global-keys" 'global-keys-minor-mode-map)
Now build an alist consisting of a list containing a single dotted
pair: the name of the minor mode and the keymap.
;; A keymap that's supposed to be consulted before the first
(defconst global-minor-mode-alist (list (cons 'global-keys-minor-mode
Finally, set emulation-mode-map-alists to a list containing only
(setf emulation-mode-map-alists '(global-minor-mode-alist))
There's one final step. Even though you want these bindings to be
global and work everywhere, there is one place where you might not
want them: the minibuffer. To be honest, I'm not sure if this part
is necessary, but it sounds like a good idea so I've kept it.
(defun my-minibuffer-setup-hook ()
(add-hook 'minibuffer-setup-hook 'my-minibuffer-setup-hook)
Whew! It's a lot of work, but it'll let me clean up my .emacs file and
save me from endlessly adding new mode-hooks.
[ 16:46 Sep 14, 2014
More linux/editors |
permalink to this entry |
Thu, 11 Sep 2014
I don't use web forums, the kind you have to read online, because they
don't scale. If you're only interested in one subject, then they work fine:
you can keep a browser tab for your one or two web forums perenially open
and hit reload every few hours to see what's new.
If you're interested in twelve subjects, each of which has several
different web forums devoted to it -- how could
you possibly keep up with that? So I don't bother with forums unless
they offer an email gateway, so they'll notify me by email when new
discussions get started, without my needing to check all those web
pages several times per day.
LinkedIn discussions mostly work like a web forum.
But for a while, they had a reasonably usable email gateway. You
could set a preference to be notified of each new conversation.
You still had to click on the web link to read the conversation so far,
but if you posted something, you'd get the rest of the discussion
emailed to you as each message was posted.
Not quite as good as a regular mailing list, but it worked pretty well.
I used it for several years to keep up with the very active Toastmasters
About a year ago, something broke in their software, and they lost
the ability to send email for new conversations. I filed a trouble
ticket, and got a note saying they were aware of the problem and
working on it. I followed up three months later (by filing another
ticket -- there's no way to add to an existing one) and got a response
saying be patient, they were still working on it. 11 months later,
I'm still being patient, but it's pretty clear they have no intention
of ever fixing the problem.
Just recently I fiddled with something in my LinkedIn prefs, and
started getting "Popular Discussions" emails every day or so.
The featured "popular discussion" is always something stupid that
I have no interest in, but it's followed by a section headed
"Other Popular Discussions" that at least gives me some idea what's
been posted in the last few days. Seemed like it might be worth
clicking on the links even though it means I'd always be a few days
late responding to any conversations.
Except -- none of the links work. They all go to a generic page with a red
header saying "Sorry it seems there was a problem with the link you followed."
I'm reading the plaintext version of the mail they send out. I tried
viewing the HTML part of the mail in a browser, and sure enough, those
links worked. So I tried comparing the text links with the HTML:
Well, that's clear as mud, isn't it?
HTML entity substitution
I pasted both links one on top of each other, to make it easier to
compare them one at a time. That made it fairly easy to find the first
Time to die laughing: they're doing HTML entity substitution on the
plaintext part of their email notifications, changing & to &
everywhere in the link.
If you take the link from the text email and replace & with &,
the link works, and takes you to the specific discussion.
Except you can't actually read the discussion. I went to a discussion
that had been open for 2 days and had 35 responses, and LinkedIn only
showed four of them. I don't even know which four they are -- are
they the first four, the last four, or some Facebook-style "four
responses we thought you'd like". There's a button to click on to
show the most recent entries, but then I only see a few of the most
recent responses, still not the whole thread.
Hooray for the web -- of course, plenty of other people have had this
problem too, and a little web searching unveiled a solution. Add a
pagination token to the end of the URL that tells LinkedIn to show
1000 messages at once.
It won't actually show 1000 (or all) responses -- but
if you start at the beginning of the page and scroll down reading
responses one by one, it will auto-load new batches.
Yes, infinite scrolling pages
can be annoying
, but at least it's a way to read a LinkedIn
conversation in order.
Making it automatic
Okay, now I know how to edit one of their URLs to make it work. Do I
want to do that by hand any time I want to view a discussion? Noooo!
Time for a script! Since I'll be selecting the URLs from mutt, they'll
be in the X PRIMARY clipboard. And unfortunately, mutt adds newlines so
I might as well strip those as well as fixing the LinkedIn problems.
(Firefox will strip newlines for me when I paste in a multi-line URL,
but why rely on that?)
Here's the important part of the script:
import subprocess, gtk
primary = gtk.clipboard_get(gtk.gdk.SELECTION_PRIMARY)
if not primary.wait_is_text_available() :
link = primary.wait_for_text()
link = link.replace("\n", "").replace("&", "&") + \
subprocess.call(["firefox", "-new-tab", link])
And here's the full script:
on GitHub. I also added it to
the script I call from Openbox to open a URL in Firefox
when I middle-click on the desktop.
Now I can finally go back to participating in those discussions.
[ 13:10 Sep 11, 2014
More tech/web |
permalink to this entry |
Sun, 07 Sep 2014
I read about cool computer tricks all the time. I think "Wow, that
would be a real timesaver!" And then a week later, when it actually
would save me time, I've long since forgotten all about it.
After yet another session where I wanted to open a frequently opened
file in emacs and thought "I think I made a
for that a while back", but then decided it's easier to type the whole
long pathname rather than go re-learn how to use emacs bookmarks,
I finally decided I needed a reminder system -- something that would
poke me and remind me of a few things I want to learn.
I used to keep cheat sheets and quick reference cards on my desk;
but that never worked for me. Quick reference cards tend to be
50 things I already know, 40 things I'll never care about and 4 really
great things I should try to remember. And eventually they get
burned in a pile of other papers on my desk and I never see them again.
My new system is working much better. I created a file in my home
directory called .reminders, in which I put a few -- just a few
-- things I want to learn and start using regularly. It started out
at about 6 lines but now it's grown to 12.
Then I put this in my .zlogin (of course, you can do this for any
shell, not just zsh, though the syntax may vary):
if [[ -f ~/.reminders ]]; then
Now, in every login shell (which for me is each new terminal window
I create on my desktop), I see my reminders. Of course, I don't read
them every time; but I look at them often enough that I can't forget
the existence of great things like
emacs bookmarks, or
diff <(cmd1) <(cmd2).
And if I forget the exact
keystroke or syntax, I can always
cat ~/.reminders to
remind myself. And after a few weeks of regular use, I finally have
internalized some of these tricks, and can remove them from my
It's not just for tech tips, either; I've used a similar technique
for reminding myself of hard-to-remember vocabulary words when I was
studying Spanish. It could work for anything you want to teach yourself.
Although the details of my .reminders are specific to Linux/Unix and zsh,
of course you could use a similar system on any computer. If you don't
open new terminal windows, you can set a reminder to pop up when you
first log in, or once a day, or whatever is right for you. The
important part is to have a small set of tips that you see regularly.
[ 21:10 Sep 07, 2014
More tech |
permalink to this entry |
Tue, 02 Sep 2014
I was using strace to figure out how to set up a program, lftp, and
a friend commented that he didn't know how to use it
and would like to learn. I don't use strace often, but when I do,
it's indispensible -- and it's easy to use. So here's a little tutorial.
My problem, in this case, was that I needed to find out what
configuration file I needed to modify in order to set up an alias
in lftp. The lftp man page tells you how to define an alias, but doesn't
tell you how to save it for future sessions; apparently you have
to edit the configuration file yourself.
But where? The man page suggested
a couple of possible config file locations -- ~/.lftprc and
~/.config/lftp/rc -- but neither of those existed. I wanted
to use the one that already existed. I had already set up bookmarks
in lftp and it remembered them, so it must have a config file already,
somewhere. I wanted to find that file and use it.
So the question was, what files does lftp read when it starts up?
strace lets you snoop on a program and see what it's doing.
strace shows you all system calls being used by a program.
What's a system call? Well, it's anything in section 2 of the Unix manual.
You can get a complete list by typing:
man 2 syscalls
(you may have to install developer man pages first -- on Debian that's
the manpages-dev package). But the important thing is that most
file access calls -- open, read, chmod, rename, unlink (that's how you
remove a file), and so on -- are system calls.
You can run a program under strace directly:
$ strace lftp sitename
Interrupt it with Ctrl-C when you've seen what you need to see.
Pruning the output
And of course, you'll see tons of crap you're not interested in,
like rt_sigaction(SIGTTOU) and fcntl64(0, F_GETFL). So let's get rid
of that first. The easiest way is to use grep. Let's say I want to know
every file that lftp opens. I can do it like this:
$ strace lftp sitename |& grep open
I have to use |& instead of just | because strace prints its
output on stderr instead of stdout.
That's pretty useful, but it's still too much. I really don't care
to know about strace opening a bazillion files in
/usr/share/locale/en_US/LC_MESSAGES, or libraries like
In this case, I'm looking for config files, so I really only want to know
which files it opens in my home directory. Like this:
$ strace lftp sitename |& grep 'open.*/home/akkana'
In other words, show me just the lines that have either the word "open"
or "read" followed later by the string "/home/akkana".
Digression: grep pipelines
Now, you might think that you could use a simpler pipeline with two greps:
$ strace lftp sitename |& grep open | grep /home/akkana
But that doesn't work -- nothing prints out. Why? Because grep, under
certain circumstances that aren't clear to me, buffers its output, so
in some cases when you pipe grep | grep, the second grep will wait
until it has collected quite a lot of output before it prints anything.
(This comes up a lot with
tail -f as well.)
You can avoid that with
$ strace lftp sitename |& grep --line-buffered open | grep /home/akkana
but that's too much to type, if you ask me.
Back to that strace | grep
Okay, whichever way you grep for open and your home directory,
open("/home/akkana/.local/share/lftp/bookmarks", O_RDONLY|O_LARGEFILE) = 5
open("/home/akkana/.netrc", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/home/akkana/.local/share/lftp/rl_history", O_RDONLY|O_LARGEFILE) = 5
open("/home/akkana/.inputrc", O_RDONLY|O_LARGEFILE) = 5
Now we're getting somewhere! The file where it's getting its bookmarks
-- and I probably can't use that
to set my alias.
But wait, why doesn't it show lftp trying to open those other config files?
Using script to save the output
At this point, you might be sick of running those grep pipelines over
and over. Most of the time, when I run strace, instead of piping it
through grep I run it under script to save the whole output.
script is one of those poorly named, ungoogleable commands, but it's
incredibly useful. It runs a subshell and saves everything that appears
in that subshell, both what you type and all the output, in a file.
Start script, then run lftp inside it:
$ script /tmp/lftp.strace
Script started on Tue 26 Aug 2014 12:58:30 PM MDT
$ strace lftp sitename
After the flood of output stops, I type Ctrl-D or Ctrl-C to exit lftp,
then another Ctrl-D to exit the subshell script is using.
Now all the strace output was in /tmp/lftp.strace and I can
grep in it, view it in an editor or anything I want.
So, what files is it looking for in my home directory and why don't
they show up as open attemps?
$ grep /home/akkana /tmp/lftp.strace
Ah, there it is! A bunch of lines like this:
access("/home/akkana/.lftprc", R_OK) = -1 ENOENT (No such file or directory)
stat64("/home/akkana/.lftp", 0xbff821a0) = -1 ENOENT (No such file or directory)
mkdir("/home/akkana/.config", 0755) = -1 EEXIST (File exists)
mkdir("/home/akkana/.config/lftp", 0755) = -1 EEXIST (File exists)
access("/home/akkana/.config/lftp/rc", R_OK) = 0
So I should have looked for access and stat as well as
Now I have the list of files it's looking for. And, curiously,
it creates ~/.config/lftp if it doesn't exist already, even though
it's not going to write anything there.
So I created ~/.config/lftp/rc and put my alias there. Worked fine.
And I was able to edit my bookmark in ~/.local/share/lftp/bookmarks
later when I had a need for that. All thanks to strace.
[ 13:06 Sep 02, 2014
More linux/cmdline |
permalink to this entry |
Thu, 28 Aug 2014
For the last several months, I repeatedly find myself in a mode where
my terminal isn't working quite right. In particular, Ctrl-C doesn't
work to interrupt a running program. It's always in a terminal where
I've been doing web work. The site I'm working on sadly has only ftp
access, so I've been using ncftp to upload files to the site, and git
and meld to do local version control on the copy of the site I keep on
my local machine. I was pretty sure the problem was coming from either
git, meld, or ncftp, but I couldn't reproduce it.
reset fixed the problem. But since I didn't know
what program was causing the problem, I didn't know when I needed to
The first step was to find out which of the three programs was at fault.
Most of the time when this happened, I wouldn't notice until hours
later, the next time I needed to stop a program with Ctrl-C.
I speculated that there was probably some way to make zsh run a check
after every command ... if I could just figure out what to check.
Terminal modes and stty -a
It seemed like my terminal was getting put into raw mode.
In programming lingo, a terminal is in raw mode when characters
from it are processed one at a time, and special characters like
Ctrl-C, which would normally interrupt whatever program is running,
are just passed like any other character.
You can list your terminal modes with stty -a:
$ stty -a
speed 38400 baud; rows 32; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ;
eol2 = ; swtch = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
ignbrk -brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -ixon -ixoff
-iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig icanon -iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
But that's a lot of information. Unfortunately there's no single flag
for raw mode; it's a collection of a lot of flags.
I checked the interrupt character:
intr = ^C, just like it should be. So what was
I saved the output with
stty -a >/tmp/stty.bad, then
I started up a new xterm and made a copy of what it should
look like with
stty -a >/tmp/stty.good. Then I looked
meld /tmp/stty.good /tmp/stty.bad.
I saw these flags differing in the bad one: ignbrk ignpar -iexten -ixon,
while the good one had -ignbrk -ignpar iexten ixon. So I should be
able to run:
$ stty -ignbrk -ignpar iexten ixon
and that would fix the problem. But it didn't. Ctrl-C still didn't work.
Setting a trap, with precmd
However, knowing some things that differed did give me something to
test for in the shell, so I could test after every command and find
out exactly when this happened. In zsh, you do that by defining a
precmd function, so here's what I did:
stty -a | fgrep -- -ignbrk > /dev/null
if [ $? -ne 0 ]; then
echo "STTY SETTINGS HAVE CHANGED \!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!"
Pardon all the exclams. I wanted to make sure I saw the notice when it happened.
And this fairly quickly found the problem: it happened when I suspended
ncftp with Ctrl-Z.
stty sane and isig
Okay, now I knew the culprit, and that if I switched to a different ftp
client the problem would probably go away. But I still wanted to know
why my stty command didn't work, and what the actual terminal
Somewhere in my web searching I'd stumbled upon some pages suggesting
stty sane as an alternative to
I tried it, and it worked.
man stty, stty sane is equivalent to
$ stty cread -ignbrk brkint -inlcr -igncr icrnl -iutf8 -ixoff -iuclc -ixany imaxbel opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
Eek! But actually that's helpful. All I had to do was get a bad
terminal (easy now that I knew ncftp was the culprit), then try:
$ stty cread
$ stty -ignbrk
$ stty brkint
... and so on, trying Ctrl-C each time to see if things were back to normal.
Or I could speed up the process by grouping them:
$ stty cread -ignbrk brkint
$ stty -inlcr -igncr icrnl -iutf8 -ixoff
... and so forth. Which is what I did. And that quickly narrowed it
down to isig
. I ran reset, then ncftp again to get the terminal
in "bad" mode, and tried:
$ stty isig
and sure enough, that was the difference.
I'm still not sure why meld didn't show me the isig difference.
But if nothing else, I learned a bit about debugging stty settings,
stty sane, which is a much nicer way of
resetting the terminal than
reset since it doesn't
clear the screen.
[ 15:41 Aug 28, 2014
More linux |
permalink to this entry |
Sun, 24 Aug 2014
I love this Adopt-a-Highway sign on Highway 4 on the way back down from
I have no idea who it is (I hope to find out, some day), but it gives
me a laugh every time I see it.
[ 10:50 Aug 24, 2014
More humor |
permalink to this entry |
Wed, 20 Aug 2014
We caught another mouse! I shot a movie of its release.
Like the previous mouse we'd caught, it was nervous about coming out
of the trap: it poked its nose out, but didn't want to come the rest
of the way.
Dave finally got impatient, picked up the trap and turned it opening down,
so the mouse would slide out.
It turned out to be the world's scruffiest mouse, which immediately
darted toward me. I had to step back and stand up to follow it on camera.
(Yes, I know my camera technique needs work. Sorry.)
Then it headed up the hill a ways before finally lapsing into the
high-bounding behavior we've seen from other mice and rats we've released.
I know it's hard to tell in the last picture -- the photo is so small --
but look at the distance between the mouse and its shadow on the ground.
Very entertaining! I don't understand why anyone uses killing traps --
even if you aren't bothered by killing things unnecessarily, the
entertainment we get from watching the releases is worth any slight
extra hassle of using the live traps.
Here's the movie:
[ 17:10 Aug 20, 2014
More nature |
permalink to this entry |
Fri, 15 Aug 2014
A few weeks ago I wrote about building a simple
camera intervalometer to take repeat photos with my DSLR.
I'd been entertained by watching the clouds build and gather and dissipate
again while I stepped through all the false positives in my
and I wanted to try capturing them intentionally so I could make cloud
Of course, you don't have to build an Arduino device.
A search for
timer remote control or
will find lots of good options around $20-30. I bought one
so I'll have a nice LCD interface rather than having to program an
Arduino every time I want to make movies.
Setting the image size
Okay, so you've set up your camera on a tripod with the intervalometer
hooked to it. (Depending on how long your movie is, you may also want
an external power supply for your camera.)
Now think about what size images you want.
If you're targeting YouTube, you probably want to use one of
preferred settings, bitrates and resolutions, perhaps 1280x720 or
1920x1080. But you may have some other reason to shoot at higher resolution:
perhaps you want to use some of the still images as well as making video.
For my first test, I shot at the full resolution of the camera.
So I had a directory full of big ten-megapixel photos with
filenames ranging from img_6624.jpg to img_6715.jpg.
I copied these into a new directory, so I didn't overwrite the originals.
You can use ImageMagick's mogrify to scale them all:
mogrify -scale 1280x720 *.jpg
I had an additional issue, though: rain was threatening and I didn't
want to leave my camera at risk of getting wet while I went dinner shopping,
so I moved the camera back under the patio roof. But with my fisheye lens,
that meant I had a lot of extra house showing and I wanted to crop
that off. I used GIMP on one image to determine the x, y, width and height
for the crop rectangle I wanted.
You can even crop to a different aspect ratio from your target,
and then fill the extra space with black:
mogrify img_6624.jpg -crop 2720x1450+135+315 -scale 1280 -gravity center -background black -extent 1280x720 *.jpg
If you decide to rescale your images to an unusual size, make sure
both dimensions are even, otherwise avconv will complain that
they're not divisible by two.
Finally: Making your movie
I found lots of pages explaining how to stitch
together time-lapse movies using mencoder, and a few
using ffmpeg. Unfortunately, in Debian, both are deprecated.
Mplayer has been removed entirely.
The ffmpeg-vs-avconv issue is apparently a big political war, and
I have no position on the matter, except that Debian has come down
strongly on the side of avconv and I get tired of getting nagged at
every time I run a program. So I needed to figure out how to use avconv.
I found some pages on avconv, but most of them didn't actually work.
Here's what worked for me:
avconv -f image2 -r 15 -start_number 6624 -i 'img_%04d.jpg' -vcodec libx264 time-lapse.mp4
Adjust the start_number and filename appropriately for the files you have.
Avconv produces an mp4 file suitable for uploading to youtube.
So here is my little test movie:
Time Lapse Clouds.
[ 12:05 Aug 15, 2014
More photo |
permalink to this entry |