Part of the reason that I'm writing this blog post is to make sure I properly configured sourcecode syntax highlighting for my blog.
I created a project with utilities for authoring blog posts in reStructuredText syntax so I can write my blog articles in reStructuredText syntax using vim, preview as html with a simple vim command, and translate to html for publication.
Not too long ago, I tinkered with a very simple program to integrate the gnu screen buffer and xwindows CLIPBOARD. I ended up rewriting it in lua, perl and c as an experiment to see the relative performance differences. I pasted the 3 versions below to verify that syntax highlighting looks good with all 3 languages.
There were other examples of screen buffer <-> xwindows clipboard integration on the net, but none of them worked for me. To get it working, I had to add a 10ms sleep after copying /tmp/screen_exchange to my xwindows clipboard with xsel -bi. I'm not sure why the sleep is necessary, but I could not get it to work consistently without at least a 10ms sleep. Eventually I will peek into the screen and xsel code to better understand the apparent race condition, but for now working around the problem with a 10ms sleep is fine.
So now I can enter Copy Mode in screen, mark the start of my selection as usual with the space bar, and press . to close the selection. The final . exits Copy Mode and copies the screen buffer to my xwindows clipboard with a single keypress.
For reference, here is my ~/.screenrc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | startup_message off
hardstatus alwayslastline
### Support for "tabs" in the screen status line:
hardstatus string '%{= kG}[ %H ][ %{= kw}%-w%{= bk}%n*%t%{= kw}%+w %= %{g}][%{B} %{W}%c %{g}]'
shelltitle " "
### In copy mode, map '.' to copy the selection to the Xwindows clipboard:
# explanation:
#
# stuff ' ' -- this enters a space character in your terminal,
# effectively ending Copy Mode and putting your
# selection in the screen paste buffer
#
# writebuf -- Writes the screen paste buffer out to a file
# (default is /tmp/screen-exchange )
#
# exec 'screen_buff_copy' -- executes screen_buff_copy, which is a program
# that will read /tmp/screen_buff_copy and write
# it to the Xwindows CLIPBOARD with xsel -bi
#
bindkey -m . eval "stuff ' '" "writebuf" "exec '/home/greg/scripts/screen/screen_buff_copy'"
|
During my initial troubleshooting, I decided to put the screen_exchange -> xsel step into an external screen_buff_copy program so I could invoke the program outside of screen to minimize the moving parts. Once I discovered the sleep workaround, I decided to try to make my screen_buff_copy program as lightweight as possible because it executes every time I want to copy text to my clipboard. I settled on lua, perl and c for my implementation trials because they have less startup overhead for short programs than, e.g., java or python.
I first wrote the program in lua:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #!/usr/bin/lua
-- NOTE: the socket does not ship as part of the lua std libs
-- You have to install it separately.
require('io')
require('socket')
pipe = io.popen("/usr/bin/xsel -bi", "w")
fp = io.open("/tmp/screen-exchange")
-- write file contents to pipe
pipe:write(fp:read("*all"))
-- close pipe and file pointer
pipe:close()
fp:close()
-- There is some race condition in the entire screen slurping/exec/whatever
-- process. Without a sleep, it does not work consistently. There is also a lag
-- of about 1 second in the stuff, writebuf and exec steps before this script is
-- properly executed and the time that the clipboard is actually set
-- sleep for 0.01 seconds:
socket.select(nil, nil, 0.01)
|
I was not thrilled that I had to load a library that is not part of the std libs (socket), and I quickly rewrote it in perl, curious to see perl's relative performance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #!/usr/bin/perl
#
# This is very very marginally more lightweight than the lua version,
# and one nice thing about it is that it uses all perl builtins without
# requiring external libs (in lua's case, the external socket lib is required
# for sleep)
#
open(F, "/tmp/screen-exchange");
open(P, "| xsel -bi");
print P <F>;
close F;
close P;
### Sleep (needed due to weird race condition)
my $sleep_seconds = 0.01;
select(undef, undef, undef, $sleep_seconds );
|
And then I decided to write it in C just to see if the overhead was perceptibly lower. Interestingly, it really was not. The C version is not faster than the perl version, but it requires about twice as many lines of code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #include <stdio.h>
#include <unistd.h> /* for usleep */
/*************************************************************
*
* screen_buff_copy.c
*
* It is totally unnecessary to do this in C. The performance
* is almost indistinguishable from the perl version.
*
*************************************************************/
/* Sleep for 10 milliseconds (seems like the min needed for reliability) */
#define MILLISECOND_IN_USEC 1000
#define USLEEP_TIME MILLISECOND_IN_USEC * 10
#define BUFF_SIZE 80
int main(int argc, char **argv) {
FILE *pipe;
FILE *fp;
size_t num_read; /* number of items read */
char buff[BUFF_SIZE];
pipe = popen("/usr/bin/xsel -bi", "w");
fp = fopen("/tmp/screen-exchange", "r");
while( (num_read = fread(buff, sizeof(char), sizeof(buff), fp)) > 0 ) {
fwrite(buff, sizeof(char), num_read, pipe);
}
fflush(pipe);
fclose(fp);
pclose(pipe);
/* Yes, we need to sleep */
usleep(USLEEP_TIME);
return(0);
}
|
A simple benchmark program (another opportunity for syntax higlighting another lang):
1 2 3 4 5 6 7 8 9 10 | #!/bin/sh
ITER=100
sbc=screen_buff_copy
for p in ./$sbc ./${sbc}.pl ./${sbc}.lua; do
echo "Timing $ITER iterations of ${p}: "
time for n in `seq $ITER`; do $p; done
echo
done
|
And the results -- perl and c are basically identical. Most of the time is spent in the usleep anyway. Perl 5's super-low startup latency for short scripts is amazing (ditto with lua, although lua takes a tiny hit here when it loads its external socket library).
Timing 100 iterations | |||
---|---|---|---|
c | perl | lua | |
real | 0m2.304s | 0m2.263s | 0m2.596s |
user | 0m0.608s | 0m0.512s | 0m0.692s |
sys | 0m0.444s | 0m0.332s | 0m0.432s |