It is often said that computers will make life a lot easier. In my opinion, that era still lies in the far future. Until then I will post here solutions, which emerge from the daily struggle with the silicon evil. Maybe they are even useful to others.



Xournal PDF embedding wrapper
Thursday, 28 January 2010 20:58

Xournal is a great notebook application to be used on Tablet PCs. It allows for annotation of PDF documents, with the drawing/writing exported directly to the PDF. While there is vector-drawing on top, the underlying PDF is not changed (making use of PDFs updating mechanism), giving rise to a rather high quality result.

Sometimes it turns out to be inconvenient that one ends up with three files — the original PDF, the Xournal file, and the annotated PDF. Because the annotations are just stored as an update, the original PDF can always be recovered by removing the most recent update. Also, PDF allows for embedding files within the document. So in principle, a single file can be sufficient: it appears to be the fully annotated version as exported by Xournal to any PDF viewer. Additionally, the Xournal file is included which allows for editing those annotations after removing the Xournal's PDF attachments.

By making a little script out of this, annotating a PDF (and also editing an already annotated one) is as easy as

xournalpdf document.pdf

and only a single file has to be kept.

The script makes use of PDF::API2 along with PDF:API2::Names. By giving it a non-existent file name, an empty Xournal file is generated in contrast to PDF annotation. Passing an existent Xournal filename will start Xournal and embed the source in the resulting PDF.

What might be most interesting is the code to append some file attachment to a PDF: The Root-object is cloned and the name-tree of embedded files extended by an entry describing the Xournal file and also containing its encoded content.

  1. sub append( $$$ )
  2. {
  3. my ( $pdfname, $filename, $description ) = ( shift, shift, shift );
  4. my $spdf = PDF::API2::Basic::PDF::File->open( $pdfname, 1 );
  5. $description = $filename unless $description;
  6.  
  7. $spdf->{Root}->realise;
  8. $spdf->{Root} = $spdf->new_obj( $spdf->{Root} );
  9.  
  10. if ( defined $spdf->{Root}->{Names} )
  11. {
  12. $spdf->{Root}->{Names}->realise;
  13. $spdf->{Root}->{Names} = $spdf->new_obj( $spdf->{Root}->{Names} );
  14. }
  15. else
  16. { $spdf->{Root}->{Names} = PDF::API2::Basic::PDF::Dict->new; }
  17. $spdf->{Root}->{Names}->{' parent'} = $spdf;
  18. bless $spdf->{Root}->{Names}, 'PDF::API2::Names';
  19.  
  20. my $file = PDF::API2::Basic::PDF::Dict->new;
  21. @$file{'Type', ' streamfile', 'Filter'} = (
  22. PDFName( 'EmbeddedFile' ),
  23. $filename,
  24. PDFArray( PDFName( 'FlateDecode' ) )
  25. );
  26.  
  27. my $descr = PDF::API2::Basic::PDF::Dict->new;
  28. @$descr{qw/Type F EF/} = (
  29. PDFName( 'Filespec' ),
  30. PDFStr( $filename ),
  31. PDF::API2::Basic::PDF::Dict->new
  32. );
  33. $descr->{EF}->{F} = $file;
  34.  
  35. $spdf->{Root}->{Names}->set( 'EmbeddedFiles', $description, $descr );
  36. $spdf->new_obj( $descr );
  37. $spdf->new_obj( $file );
  38. $spdf->append_file;
  39. $spdf->release;
  40. }

Download: xournalpdf

 
Yet another PDF merger
Monday, 25 January 2010 22:49

Having tried all different kinds of combinations of PDF tools to simply merge files, I have been pretty disappointed. So I took the pdf-optimize.pl example from the great (but sparsely documented) PDF::API2 and wrote my own little version that has the following features:

  • Links between merged files will be converted into internal links.
  • The same is true for outlines that point to external files.
  • Trees of outlines are merged rather than just concatenated.
  • Further outlines to external files will be removed.

Links will be preserved also when using named destinations. However, other named object might become broken. For the files I encountered so far that was not a problem at all. Changing this as well should be straight-forward.

Download: pdf-merge-links.pl

 
Matlab EPS data extraction
Wednesday, 25 November 2009 11:20

Sometimes it appears to be useful to have more detailed knowledge of point data than just the plot of it – taking a ruler and measuring out coordinates of points within a diagram is simply not an option. If this plot is available in a suitable vector format such as PostScript, then it is in principle possible to read out the original data again (up to the accuracy of the plot itself).

In the case of diagrams generated with MATLAB, I can offer a little Perl-script to do this job. Appart from extracting the point sets (one CSV file is generated for each point set, PS point coordinates sit in the first two columns) it also tries to read the axes and offer a transformation from the PS coordinate system to the original one (column three and four, if available).

Keep in mind, that this is just a 15-minutes quick-and-dirty hack which works for the plots I was interested in. If you encounter any problems, write me a note. In principle, the same technique would also work for other programs such as gnuplot.

Download: matlab_extract.pl

 
Skype DBus client program for Linux
Thursday, 08 October 2009 00:08

In order to access Skype with shell scripts or Perl, I decided to use a single interface suitable for all possible uses: read/write from STDIN/STDOUT. This also allows for playing with the Skype API interactively by typing in the commands. Be careful not to enter empty lines – this may cause Skype to crash.

The program skype_dbus_client.c can be compiled with

gcc skype_dbus_client.c -o skype_dbus_client \
      `pkg-config --cflags --libs glib-2.0` \
      `pkg-config --cflags --libs dbus-glib-1`

Of course, libraries and headers of GLib and the GLib DBus bindings have to be installed.

  1. /* skype_dbus_client.c: a simple command line client to Skype's DBus interface
  2. read from STDIN and write to STDOUT */
  3.  
  4. #include <stdio.h>
  5. #include <glib.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8.  
  9. #define DBUS_API_SUBJECT_TO_CHANGE
  10. #include <dbus/dbus.h>
  11.  
  12. DBusConnection *connection;
  13.  
  14. /* iterate through DBus messages and print the string components */
  15. static void print_iter( DBusMessageIter *iter )
  16. {
  17. do
  18. {
  19. int type = dbus_message_iter_get_arg_type( iter );
  20. const char *str;
  21.  
  22. if ( type == DBUS_TYPE_INVALID ) break;
  23.  
  24. switch ( type )
  25. {
  26. case DBUS_TYPE_STRING: // it is a string message
  27. dbus_message_iter_get_basic( iter, &str );
  28. g_print( "%s\n", str );
  29. break;
  30.  
  31. case DBUS_TYPE_VARIANT: // another set of items
  32. {
  33. DBusMessageIter subiter;
  34.  
  35. dbus_message_iter_recurse( iter, &subiter );
  36. print_iter( &subiter );
  37. break;
  38. }
  39.  
  40. default:
  41. break;
  42. }
  43. }
  44. while ( dbus_message_iter_next( iter ) );
  45. }
  46.  
  47. /* the handler gets called if the DBus connection receives any data */
  48. static DBusHandlerResult notify_handler( DBusConnection *bus, DBusMessage *msg, void *user_data )
  49. {
  50. DBusMessageIter iter;
  51. dbus_message_iter_init( msg, &iter );
  52. print_iter( &iter );
  53.  
  54. return TRUE;
  55. }
  56.  
  57. /* send a string to skype */
  58. void sendToSkype( char *msg )
  59. {
  60. DBusMessageIter iter;
  61. const int reply_timeout = -1; // do not timeout -- block
  62. DBusMessage *reply;
  63. DBusMessage *message;
  64. DBusError error;
  65.  
  66. dbus_error_init( &error );
  67. message = dbus_message_new_method_call(
  68. "com.Skype.API",
  69. "/com/Skype",
  70. "com.Skype.API",
  71. "Invoke"
  72. );
  73.  
  74. if( !dbus_message_append_args( message, DBUS_TYPE_STRING, &msg, DBUS_TYPE_INVALID ) )
  75. {
  76. fprintf( stderr, "Error: reply is not except format\n" );
  77. exit( 1 );
  78. }
  79.  
  80. reply = dbus_connection_send_with_reply_and_block( connection, message, reply_timeout, &error );
  81.  
  82. if ( dbus_error_is_set( &error ) )
  83. {
  84. fprintf ( stderr, "Error: %s\n", error.message );
  85. dbus_error_free( &error );
  86. exit( 1 );
  87. }
  88.  
  89. dbus_message_iter_init( reply, &iter );
  90. print_iter( &iter );
  91.  
  92. if( dbus_error_is_set( &error ) )
  93. {
  94. fprintf( stderr, "Error: %s\n", error.message );
  95. dbus_error_free( &error );
  96. exit( 1 );
  97. }
  98. }
  99.  
  100. /* if the input is disconnected: exit the program */
  101. gboolean hangup_handler( GIOChannel *source, GIOCondition condition, gpointer data )
  102. { exit( 0 ); }
  103.  
  104. /* input waiting: read and forward to skype */
  105. gboolean input_handler( GIOChannel *source, GIOCondition condition, gpointer data )
  106. {
  107. char *b;
  108.  
  109. g_io_channel_read_line( source, &b, NULL, NULL, NULL );
  110. if ( b == NULL ) { exit( 0 ); }
  111.  
  112. sendToSkype( b );
  113. g_free( b );
  114.  
  115. return TRUE;
  116. }
  117.  
  118. int main( int argc, char **argv )
  119. {
  120. DBusObjectPathVTable vtable;
  121. GMainLoop *loop;
  122. GIOChannel *in;
  123. DBusError error;
  124.  
  125. dbus_error_init( &error );
  126. connection = dbus_bus_get( DBUS_BUS_SESSION, &error );
  127. if ( connection == NULL )
  128. {
  129. fprintf( stderr, "Failed to open connection to bus: %s\n", error.message );
  130. dbus_error_free( &error );
  131. exit( 1 );
  132. }
  133.  
  134. loop = g_main_loop_new( NULL, FALSE );
  135.  
  136. in = g_io_channel_unix_new( 0 ); // open STDIN for reading
  137. g_io_add_watch( in, G_IO_IN, input_handler, NULL ); // watch for input on STDIN
  138. g_io_add_watch( in, G_IO_HUP, hangup_handler, NULL ); // watch for hangup of STDIN
  139.  
  140. dbus_connection_setup_with_g_main( connection, NULL ); // set up te DBus connection
  141.  
  142. vtable.message_function = notify_handler; // register handler for incoming data on DBus
  143. dbus_connection_register_object_path( connection, "/com/Skype/Client", &vtable, 0 );
  144.  
  145. g_main_loop_run( loop ); // the main loop
  146.  
  147. return 0;
  148. }
 
Public access to Ordnance Survey and IGN maps
Tuesday, 17 March 2009 08:08

Ordnance Survey (the UK mapping agency) as well as IGN (the french pendant) opened their topographic maps to the public. The two services called OS OpenSpace and API Geoportail allow for integration of high-quality up-to-date topographic maps of the UK and France into private web-sites.

This integration is in both cases based on modified versions of OpenLayers which use an authenticated WMS layer (a registration key has to be used to validate the access from a specific location) as base images. Therefore it is quite easy to build special OS and GP layers to integrate these public services into standard OpenLayers projects.

For the OpenSpace layer have a look at an example of OS integration or OpenLayers sandbox.

I have implemented an IGN Geoportail layer for OpenLayers, to be downloaded here. At the moment only the maps for the french main land are included and no satellite photos. A new new layer is created by

var layer = new OpenLayers.Layer.Geoportail( "Geoportail Main Land", key );

where key contains the API key for the web page you want to access the API from. The key is transmitted on a regular basis to ensure that subsequent requests are all authenticated.

 
Toshiba's battery replacement programme
Friday, 14 November 2008 15:18

For the last years Sony stocks suffered a bit from the huge replacement calls for defect battery cells. Also Toshiba uses Sony cells for its laptop batteries. From time to time they renew the information on the website of the programme (US | Europe), appending additional models and serial numbers.

When one of my batteries (for the Portégé M400) decided not to stop loading and overheated a lot (I am glad that I was home), I discovered that this type of batteries for the M400 was actually affected as well.

Having read this on a US Toshiba site, bought from Toshiba UK and because I am residing in Germany at the moment, I had a lot of fun with three different support services on the phone:

  • The US division confirmed that these batteries should be replaced and their program identified one of them to be affected. However, shipment to Europe was not possible, this is entirely the territory of Toshiba Europe.
  • Although the european identification program did not recognise my batteries, that they are affected was confirmed by the UK support center. However – again – shipment to Germany can only be triggered by the german support.
  • In dozens of calls and emails the German support service always told me the same: The batteries are not affected and will not be replaced.
  • Finally, after quoting the statements of the other divisions in a new request, the German support center sent the batteries – without a further comment.
 
Suspend terminal sessions
Monday, 04 February 2008 18:08

Just like with X11 sessions which can be suspended by using Xvfb and xmove, suspending terminal sessions without stopping the running processes is of interest when using interactive programs where scripting is not an option.

To setup the environment all that has to be done is to run

screen

When the programs are running, the session can be put into suspend (note, that the processes continue to run and the output is buffered) by hitting ctrl-a d.

To resume the session (which could be after closing the ssh connection and re-establishing it later) simply run

screen -r
 
X11 Remote Control
Monday, 28 January 2008 15:36

x2x is a brilliant little remote control for X11. For example when you are working at different computers simultaneously it can make life much easier. Mouse and keyboard commands are sent to other xservers as well as it copies the clipboard. The standard X11 protocol is used, so the usual tricks and ssh tunnels work fine.

I have a shortcut on my laptop to “invite” the keyboard and mouse of my desktop at work. To avoid exposing the xserver, a bit of ssh tunneling is done:

ssh -f -n -X desktop.at.work
  'export DISPLAY=:0.0;ssh -n -f -X cap@`echo $SSH_CLIENT | cut -d" " -f1` x2x -west -to :1.0';
echo "connecting X server ..." |
  osd_cat -A right -s 2 -f -*-helvetica-bold-r-*-*-20-*-*-*-*-*-*-*

After that, the mouse pointer can leave the desktop screen on the left (west) and will enter the laptop screen on the right. Whichever screen the mouse is residing in will receive the keyboard events from the desktop keyboard. osd_cat is merely used to print a notification of what is happening on the display.

 
Crop PDFs
Monday, 28 January 2008 14:22

I came across a lot of ebooks in PDF format which are in principle suitable for display on the CyBook (e.g., Planet PDF), but feature margins that are way too large. Also, headers and footers, and advertisement are not really required. To crop a PDF file and maintain the structure like bookmarks and links can be done by replacing the MediaBox or CropBox and repairing the XRef table with pdftk (pdfcrop2):

#!/bin/bash
 
perl -pe "s/(Crop|Media)Box\s*\[(.+?)\]/\$1Box\[$2\]/g;" $1 | pdftk - output $3

The script takes three arguments:

  1. Input file
  2. New crop box, i.e. "left bottom right top" in postscript pixels starting from the top left, e.g. "0 0 612 792". To obtain this one could use xv or any other image manipulation program that can open PDF pages without looking at their CropBox after.
  3. Output file
 
Automatic ssh tunnel
Sunday, 27 January 2008 14:32

The drawback of high security guidelines and policies against spam is that access to an SMTP server may only be granted from inside a company's or university's intranet. If you want to use this server from the outside, the easiest solution is to set up an ssh tunnel to a computer available inside the company's intranet:

ssh -L localport:mailserver:25 computer.in.the.intranet

This tunnel has to be set up always before sending emails. For automation the xinetd (or inetd) can be used. Xinetd will be configured to listen at a local port (which will be our local SMTP port) and relay the traffic to/from stdin/stdout of an ssh tunnel. ssh, in turn, relays the traffic to a netcat (or nc) instance, running on the trusted computer, finally forwarding everything to the mailserver. For security on our side, xinetd will only listen for local connections, but the system can be used in principle as a replacement for a VPN where only few ports are of interest.

All that is needed is a proper configuration of xinetd. Here an example for an automatic tunnel, listening on localhost:25 (/etc/xinetd.conf/mailtunnel):

service smtp
{
  port        = 25
  socket_type = stream
  protocol    = tcp
  only_from   = localhost
  interface   = 127.0.0.1
  wait        = no
  user        = local_user_with_appropriate_ssh_key
  group       = the_users_group
  flags       = REUSE
  server      = /usr/bin/ssh
  server_args = -x -a -q -T computer.in.the.intranet netcat mailserver 25
}
 
Move windows between XServers
Saturday, 26 January 2008 22:31

Who has ever been working on more than one Linux computer simultaneously knows the problem: I need this window on the other computer's screen.

The solution: xmove. Usage is quite simple: start another XServer on port :1 by issueing the command

xmove -port 1

All windows that shall be movable to other displays should use this server. By default xmove displays them on the active XServer. To move them to another display type

xmovectrl :1 -moveall :10

where :10 denotes the 10th XServer. Because the standard X protocol is used, this also works over ssh or together with Xnest.

 
«StartPrev12NextEnd»

Page 1 of 2