WakeLift
wakelift / posts /

Zoom into selections on a thinkpad with fn+space [updated]

Recently I've started putting more and more functions on my keyboard by using the fn modifier. Some of those extra keys on my keyboard will emit X events and some will emit ACPI events. This post is about the combination fn+space.

Update: Look below to find an improved version with automatic line breaking enhanced by a friend of mine. Also note the new dependency, par.

spaaaaaaaace

Implementation

The implementation is made of two parts and has the following dependencies:

  • acpid for catching the ACPI event that's fired by the combo
  • acpi_listen from the acpid package for finding out what the key event is
  • xclip (or 100% compatible) for getting the clipboard content
  • sm ("screen-message") for displaying the clipboard content really big
  • python for bridging acpid and xclip/screen-message
  • par for cleverly formatting the text.

ACPI

First, to find out, what event to listen for, we run acpi_listen and hit fn+space. On my x200s i get this output:

button/zoom ZOOM 00000080 00000000

Then we create a plaintext file show_clipboard in /etc/acpi/events/ with the following contents:

event=button/zoom ZOOM 00000080 00000000
action=/etc/acpi/do_show_clipboard.sh

And we create a short simple shell script with the name do_show_clipboard.sh in /etc/acpi/ with the following contents:

#!/bin/bash
kill -SIGUSR1 `cat /tmp/sc.pid`

And make it executable, so that acpi can run it.

Gluecode

The next step is the sc bridge, which is a simple python script with the following contents:

#!/usr/bin/env python
# license: CC0
import signal
import os
import sys
import math
from subprocess import Popen, PIPE

def check_pid(pid):
    try: os.kill(pid, 0)
    except OSError: return False
    else: return True

def check_output(cmdparts, inp=""):
    proc = Popen(cmdparts, stdout=PIPE, stdin=PIPE)
    out = proc.communicate(inp)[0]
    proc.wait()
    return out

def par(text):
    # Characters are nearly twice as high as wide, screens 1.5 times wider than
    # high, so sqrt(numchars) characters per line would give you something that
    # is nearly two times higher than wide. Making it wider by a factor of 2.75
    # should work out for most screens.
    maxlen = max(15, int(2.75*math.sqrt(len(text))))
    call = ['par', str(maxlen)]
    return check_output(call, text)

def do_sc(signum, frame):
    text = par(check_output(['xclip', '-o'])).strip()
    check_output(['sm', text])

signal.signal(signal.SIGUSR1, do_sc)

try:
    with open('/tmp/sc.pid', 'r') as pidfile:
        pid = pidfile.read()
    if check_pid(int(pid)):
        print 'sc already running at %s' % pid
        sys.exit(1)
except IOError: pass

with open('/tmp/sc.pid', 'w') as pidfile:
    pidfile.write(str(os.getpid()))

try:
    while True: signal.pause()
finally:
    os.unlink('/tmp/sc.pid')

Set up this program to run when you start your X session and restart acpi. The next time you select text and hit fn+space, it should display the text in big, friendly letters.

Happyness

This script is very useful in many different situations at least two situations!

  • Quickly display your ip-address to your friends, by running ip -4 a (or ip -6 a if that's your kind of thing (not that I have a problem with ipv6. in fact, I have many friends, who are ipv6!)), marking the IP and hiting fn+space.
  • Highlighting snippets of, say, emails or forum posts is much cooler when they fill the screen with big friendly letters than when you just mark them and turn the laptop to your neighbour in a lecture or whatnot

Room for Improvement

  • Add a url-shortener with url-detection
  • you name it!