2009-10-31

Porting Perl+curses to Solaris

While porting a Curses app to Solaris (u6) the other day, I ran into a problem with the Curses::UI library. MakeMaker couldn't find the use_default_colors() function (which should be there somewhere) in the installed ncurses library headers, which Curses::UI uses by default.

It would seem that passing Curses::UI->new() an arg for -default_colors => 0 will make the library not call the use_default_colors() function, which should avoid the error. Apparently this isn't the case; I had to create my GUI with compatibility mode by using -compat => 1.

Additionally, my ncurses installation doesn't support the KEY_RESIZE constant. Fixing this was a simple tweak to the code to ignore it (the library ignores the key anyway, so it's not like I'm losing any functionality to it).

index 9ab97ae..3b39d89 100644
--- a/lib/solaris/Curses/UI.pm
+++ b/lib/solaris/Curses/UI.pm
@@ -383,9 +383,9 @@ sub do_one_event(;$)
     # ncurses sends KEY_RESIZE() key on resize. Ignore this key.
     # TODO: Try to redraw and layout everything anew
     # KEY_RESIZE doesn't seem to work right;
-    if (Curses->can("KEY_RESIZE")) {
-        $key = '-1' if $key eq KEY_RESIZE();
-    }
+#    if (Curses->can("KEY_RESIZE")) {
+#        $key = '-1' if $key eq KEY_RESIZE();
+#    }
     my ($cols,$lines) = GetTerminalSize;
     if ( ($ENV{COLS} != $cols) || ( $ENV{LINES} != $lines )) {
        $self->layout();

A quick wrapper around the original to do OS detection and pick the Curses::UI library at runtime from it, and I now have an app that'll run in curses mode on both Linux and Solaris.

:wq

2009-10-29

IRC Wars: weechat vs irssi

The other week, I switched from irssi to weechat for my console IRC client of choice. The box these run on is a FreeBSD 6.2 system, so getting everything working like I wanted with weechat was a bit of a chore.

weechat comes with a couple improvements over vanilla irssi. First, the nicklist "plugin" from irssi is included by default. I thought this was a cool feature the few times that I've seen it used on people's clients, but didn't really realize how much it helps until I started using weechat. I don't think I've done a /names in a week.

Secondly, color support is vastly improved. /set reveals a whole slew of color and styling options, without having to muck around with a separate style config and the correct magic incantations of /save, /load, and /savestyle.

Finally, weechat has a much richer filtering interface. The IRC smart filter automatically collapses people who flap into a single set of join/part messages. It also keeps the text that it actually received, so unlike irssi, you can get the data back after filtering it out.

Now, onto compiling...

I would much rather have used the port of weechat than compile my own version, but that's only 0.2.6 (current is 0.3.0), and running old versions is somewhat suboptimal.

One of the main issues was that weechat has multiple front-ends and the configure script failed to find any of them (the box is headless, so no surprises about missing gtk+/qt libs). I'm only interested in the ncurses front-end, but the default ncurses library on the system doesn't work with wide-characters. Linux-compatibility ncurses does, but only has the lib and not the headers. For most users, this probably isn't a problem, but since I use IRC in languages other than English (specifically, Japanese without wide-characters is painful), it's a must. I actually had my irssi session around still just for this.

So, I had to compile my own ncurses library (--with-widec --with-ext-colors). Unfortunately, weechat only picks up on the wide characters and doesn't have 256-color support. Not really a big deal, but I like my shinies when I can get them. Ncurses 5.7 compiled like a charm the first time, and getting weechat to recognize the new lib is just a matter of correcting CFLAGS.

Now I'm off and running full time with weechat. I guess if irssi is the client of the future, the present isn't doing too shabby, either.

:wq

2009-10-17

Browser Wars, Part 1 of ?

After Firefox 3.5 crashed for the 10th time in an hour or something like that, I finally cracked down and started on getting uzbl (pronounced "usable") working.

Install and launch uzbl, and you're likely to not see how nifty this little browser is. There's no title bar, no navigation, no keybindings; you get a blank white screen. What's the big deal? I can do that in something like 20 lines of c using the OpenGL library (it's a 3-D white screen, even).

The first thing to realize is that there is no default configuration. If you want to go to a page, you must launch the program with something like: `uzbl --uri www.uzbl.org`. Now, this isn't very usable, since you have to open a new browser every time you want to go to a new url (like I used to do in lynx before I found the 'g' command).

Thankfully, there is a very easy way to get a basic configuration. If you're used to vimperator (and you should be, if you still use Firefox), then you'll be all set by copying the sample configuration (likely under /usr/share/uzbl/examples) into your XDG_*_HOME locations. The color scheme is a bit ugly, but it gets the basic information across, and you can use all your favorite vim keybindings, except the ones that are bound incorrectly. I reconfigured mine so that top of page is gg, and bottom of page is G, instead of the defaults of << and >>, for instance. If I could indent and dedent text on the fly, those would be the commands for that.

The biggest difference between the uzbl and vimperator keybindings, however, is that uzbl starts in command mode, not insert mode. I've typed my username into the command bar dozens of times already, because I'm used to clicking in a text field and having it steal my input focus. Not so with uzbl! You must hit i to go to insert mode, just like in a real editor. I hear there are ways to make uzbl perform like emacs, but matters of religion aside, it's not that hard to get used to vim-like behavior.

Since uzbl subscribes to the UNIX philosophy, there's also no notion of tabs. This makes it very amenable to being embedded into gtk+ applications, since those can either implement tabbed browsing or not as appropriate. This is a little reminescent of the Microsoft way, with IE browsing-only windows able to be embedded into anything. You know, if you like having all your apps that want to render HTML be vulnerable to .NET-based attacks or something.

In order to get tabbed browsing, there is a Python wrapper called uzbl_tabbed.py to implement it. The source is pretty straightforward; it opens a new uzbl process every time you create a new tab. This can be a great way to test your configuration, since new tabs use the latest configuration, and old tabs stay the same. Of course, if you blow up uzbl_tabbed impressively enough, it won't clear out the fifos and sockets in /tmp and you'll be left with a problem resembling Firefox's "already opened" message. Except with no error message, unless you start uzbl_tabbed.py from a terminal. For me, CADIE is just about perfect for this.

Having now figured out tabbed browsing and how to make the keyboard bindings more sane, I figured the only thing that I needed to keep Firefox around for was flash. Every once in a while, there's a purpose for it, like my bank's online banking login. They still have a "deprecated" HTML version, but I'm not about to be caught locked out with a check waiting to bounce if I can avoid it. To my great surprise, uzbl supported flash right out of the box. I don't know if this is just because of my distro's package, or if it's actually in the version of webkit that uzbl is built on or what, but it means I can do online banking, and the occasional youtube video. Oh, those silly, silly people shaking babies...

So far uzbl can do everything I use Firefox for. What could there possibly be that's bad?

No (or very limited) incremental rendering. This means that clicking on a link sometimes offers absolutely no immediate feedback that something is happening. But, one of the mottos of UNIX tools is that "no news is good news." Unfortunately, the internet being what it is, this is more like a GNU tool where no news is, well, no news. Pages eventually do load, but seem to take forever, since the entire page has to load and render before anything shows up. Since it doesn't clear the old page until the new one is ready, it's easy to click a link a few times without realizing that uzbl is trying to get the page for you.

Downloading files is a little weird. GTK+ already has support for downloading files, so why build it into uzbl? This makes a lot of sense, as it makes the browser more lightweight and doesn't duplicate functionality, but even w3m has a download manager.

uzbl uses webkit. I really don't like webkit as much as Gecko; it isn't as widely-recognized as a "real" rendering engine, so sites aren't designed or tested with webkit browsers. Maybe the iPhone and Chrome are changing this, but Safari seems to have done very little to be taken seriously, so it's an uphill climb. On the other hand, at least it's not IE.

(editor's note: wow, was I right about Chrome making webkit a standard browser)

uzbl_tabbed is pretty bare-bones. Tabs aren't shortened when there isn't enough room; there's just an ellipsis to show that more tabs exist. How many more? Good question. What tabs are over there past number 3? An equally good question. I'm sure this can be fixed by hacking on the script a little, but I haven't started on that yet. There is an option for gtk+ styled tabs, which is (thankfully) turned off by default. I'm guessing that the gtk+ notebook tabs handle this much more sanely than the text tabs. Of course, they also take up more screen real estate, and I've only got 600px from top to bottom.

Bookmarks. What are those? uzbl has no concept of a bookmark. There are scripts to implement this functionality, but one of the coolest extensions I use for Firefox is Xmarks (formerly FoxMarks, formerly Google Browser Sync). Automagically, I can bookmark a page on a computer at school and it shows up on my laptop. Delete a bookmark on my laptop and it's gone from the systems at work. UNIX Firefox doesn't show it; Linux Firefox doesn't show it; Windows Firefox doesn't show it. Pretty freakin' cool. Setting the same thing up in uzbl will likely require rsync and cron and a few hours of mucking with scripts. Or learning git.

Japanese text. This is really an issue I have with gtk+ in general. Kanji are roughly half again as large as kana and extend above and below the baseline. WHY?!? It's nice that this makes the kanji bigger and therefore a little easier to read (pack 29 strokes into 16px high and it's more a Rorschach Test than reading), but it looks like a little kid's handwriting with letters all different heights. Or, since they're printed, maybe like a ransom note cut out of magazines. "If you ever want to see all your characters the same height again..."

All in all, uzbl is pretty neat so far. I dislike being dependent on Python in general (run a Python 2.4 script in a 2.6 interpreter to see why), so instead of hacking on the tabbed browsing script, I may just write my own. Then again, that would mean I'd have to restart my uzbl session, and that's been going for longer than I've gotten out of Firefox since upgrading. Plus, Firefox seems to have amnesia lately and won't restore my tabs from last time, except when it crashes (which, yes, is embarrasing, but leave the witty commentary to Chrome).

If I ever get Chromium working on Linux (I haven't tried in months, so it could be a fun project for next weekend), expect Browser Wars to continue for some time; with more than just Firefox and lynx, I actually get a choice now for my Linux browser. It's about time we caught up to Windows on this one.

:wq

2009-10-14

SSH over LPR

A couple weeks ago, somebody asked if it were possible to get printers from school to work through a local cups client (ala, just putting the print server in client.conf). Some google-fu landed me this script for making a cups backend that prints via ssh. Of course, like most things on the intarwebs, it almost does what I wanted it to do.

A little tweaking and I got the script to run on my laptop, using ssh-agent so that I don't have to have passwordless root keys in order to print anywhere (still need them for my fuse-sshfs/autofs hack, though). Since archlinux runs cups as daemon instead of root, the script needs an suid wrapper in order to work properly (daemon can't su to $user).

The instructions are basically the same as for the original script. Install (or install suid wrapper) to /usr/lib/cups/backend . Then restart cups and add the printer. Cups will not show the remote and local users, but they are stored and will work correctly.

Exit codes can be modified to make the "printer" respond differently based on various conditions. For example, `exit 3` causes the queue to be stopped if the print server is not responding.

#!/bin/sh

# SSH-LPR Backend

# The purpose of the back end is to send print jobs to a remote system
# through ssh and lpr on the remote side. It requires that the user who
# wishes to print have a key-based authentication set up for the server in
# question. The printer URI is sshlpr://[ruser[:luser]@]server/queue where
# ruser is the name of remote user if it is different from the local use
# name, luser is the local user account with the SSH key, if different
# from the user who actually printed, server is the host and queue is the
# print queue name as it would get passed to lpr -P queue.

# With no parameters, we need to tell CUPS what we are.
if [ $# -le 0 ]
then
        echo "network sshlpr \"Unknown\" \"LPR thorugh SSH\""
        exit 0
fi

HOME='/root'
export HOME

# If we get the correct number of arguments, as per:
#       $1=job-id $2=user $3=title $4=copies $5=options $6=[file]
[ $# -lt 5 -o $# -gt 6 ] && exit 1

# Parse URL
user=$2
localuser=$2

[ -z "$DEVICE_URI" ] && exit 1

server="`echo $DEVICE_URI | cut -f 3 -d /`"
printer="`echo $DEVICE_URI | cut -f 4 -d /`"
if echo $server | grep '@' > /dev/null 2> /dev/null
then
        user="`echo $server | cut -f 1 -d '@' `"
        server="`echo $server | cut -f 2 -d '@' `"
fi

if echo $user | grep ':' > /dev/null 2> /dev/null
then
        localuser="`echo $user | cut -f 1 -d ':' `"
        user="`echo $user | cut -f 2 -d ':' `"
fi

# sanitize input for title
title=`echo $3 | sed -e "s/'//g" -e 's/"//g'`

# find a running SSH_AGENT
for i in /tmp/ssh-*/agent.* ; do
        if [ -r $i ] ; then
                ssh_auth_old=$SSH_AUTH_SOCK
                SSH_AUTH_SOCK=$i
                export SSH_AUTH_SOCK
                lines=`ssh-add -l | wc -l`
                if [ $lines -eq 0 ] ; then
                        SSH_AUTH_SOCK=$ssh_auth_old
                        export SSH_AUTH_SOCK
                fi
        fi
done

echo "Using SSH_AUTH_SOCK of $SSH_AUTH_SOCK" >&2

LPR="/usr/bin/lpr -U ${user} -P ${printer} -J '${title}' -# $4"

/bin/ping -c 1 -W 1 $server >/dev/null 2>&1
if [ $? -ne 0 ] ; then
        echo "Host is down: $server"
        exit 3
fi

echo "Doing cat $6 | su $localuser -c \"ssh $user@$server \\\"$LPR\\\"\"" >&2

# Has CUPS given us a file in $6?
if [ -r "$6" ] ; then
        /bin/cat $6 | su $localuser -c "/usr/bin/ssh -q -o BatchMode=yes ${user}@${server} \"${LPR}\""
        if [ $? -eq 0 ] ; then exit 0; fi
        exit 3
else
        echo "Cannot read file: $6" >&2
        exit 2
fi

:wq