xdotool comes to the rescue

In a previous post I explained how I implemented a method for adding my current location and the local time to my e-mail signature wherever I happen to be in the World, irrespective of the time on the laptop’s hardware clock and system clock. In that post I described how I created a keyboard shortcut using the Linux application AutoKey. Unfortunately AutoKey has not been updated for several years and no longer works properly in KDE Plasma 5 on my laptops. Therefore I decided to replace it with a KDE keyboard shortcut, and this is to explain how I did it.

First create a custom shortcut in KDE:

  1. ‘System Settings’ > ‘Shortcuts’ > ‘Custom Shortcuts’
  2. ‘Edit’ > ‘New’ > ‘Global Shortcut’ > ‘Command/URL’, and name the New Action ‘Insert current time’
  3. On the Comment pane for ‘Insert current time’, add the comment ‘Insert current time at specified location’ (without the quotes)
  4. On the Trigger pane, configure the shortcut to be Ctrl+Alt+Space
  5. On the Action pane, enter the Command/URL as ‘/home/fitzcarraldo/timezone_signature_GeoNames.sh‘ (without the quotes)
  6. Click ‘Apply’

Next modify the Bash script timezone_signature_GeoNames.sh so that it contains the following (obviously change the username and path to suit):

#!/bin/bash

place=$(kdialog --title "Current Location" --inputbox "Enter your location:")

placetime=$(perl /home/fitzcarraldo/now1.pl $place)

# xdotool does not output a space in a string, so we have to extract each field from the string
# and print each field individually, separated by a space character.

city=$(echo $placetime | awk -F "|" '{print $1}')
country=$(echo $placetime | awk -F "|" '{print $2}' | sed 's/[)(]//g')
region=$(echo $placetime | awk -F "|" '{print $4}')

datetime=$(/usr/bin/zdump $region | awk -F " " '{print $2" "$3" "$4" "$5" "$6" "$7}')
dayofweek=$(echo $datetime | awk -F " " '{print $1}')
month=$(echo $datetime | awk -F " " '{print $2}')
day=$(echo $datetime | awk -F " " '{print $3}')
time=$(echo $datetime | awk -F " " '{print $4}')
year=$(echo $datetime | awk -F " " '{print $5}')
timezone=$(echo $datetime | awk -F " " '{print $6}')

activewindow=$(xdotool getactivewindow)

xdotool type --window $activewindow "Sent from:"
for oneword in $city; do
    xdotool key --window $activewindow space
    sleep 0.1s
    xdotool type --window $activewindow --delay 100 $oneword
done
xdotool key --window $activewindow comma
for oneword in $country; do
    xdotool key --window $activewindow space
    sleep 0.1s
    xdotool type --window $activewindow --delay 100 $oneword
done
xdotool key --window $activewindow Return
xdotool type --window $activewindow "Local time now: "
xdotool type --window $activewindow $dayofweek
xdotool type --window $activewindow " "
xdotool type --window $activewindow $month
xdotool type --window $activewindow " "
xdotool type --window $activewindow $day
xdotool type --window $activewindow " "
xdotool type --window $activewindow $time
xdotool type --window $activewindow " "
xdotool type --window $activewindow $year
xdotool type --window $activewindow " "
if [ ${timezone:0:1} = "-" ]; then
    timezone="UTC-"${timezone#*-}
elif [ ${timezone:0:1} = "+" ]; then
    timezone="UTC+"${timezone#*+}
fi
xdotool type --window $activewindow $timezone
xdotool type --window $activewindow " "
xdotool key --window $activewindow Return
xdotool key --window $activewindow Return
echo

The Perl script now1.pl is listed in my my earlier post. Notice that the script timezone_signature_GeoNames.sh in my earlier post was much simpler. This was because the AutoKey shortcut took care of sending the text to the currently active window. Without AutoKey, I now had to do this myself in the script timezone_signature_GeoNames.sh, and the command xdotool came to the rescue. The developer explains what xdotool does as follows:

This tool lets you simulate keyboard input and mouse activity, move and resize windows, etc. It does this using X11’s XTEST extension and other Xlib functions.

Additionally, you can search for windows and move, resize, hide, and modify window properties like the title. If your window manager supports it, you can use xdotool to switch desktops, move windows between desktops, and change the number of desktops.

So I installed xdotool via the Gentoo package manager:

# emerge xdotool
# eix xdotool
[I] x11-misc/xdotool
     Available versions:  3.20150503.1-r1^t ~3.20160805.1^t {examples}
     Installed versions:  3.20150503.1-r1^t(22:51:30 02/04/17)(-examples)
     Homepage:            http://www.semicomplete.com/projects/xdotool/
     Description:         Simulate keyboard input and mouse activity, move and resize windows

Anyway, my Bash script using xdotool works a treat with Thunderbird (and KWrite, LibreOffice Writer, etc.). I used to experience a problem with certain characters, for example a colon was printed as a semi-colon (see the xdotool bug report xdotool writes the wrong case #121), but that no longer happens in my current KDE Plasma 5 installation:

Sent from: Galeão International Airport, Brazil
Local time now: Thu Jul 6 15:11:40 2017 UTC-03

What a useful tool xdotool is!

Advertisements

Using a keyboard shortcut in Linux to add an e-mail signature giving current location and local time

In my previous post I showed how to find the current time at any town or city Worldwide from the command line in Gentoo Linux. My interest in a command to do this is not to use it on the command line per se, but to use the command in a keyboard shortcut to insert a signature at the end of my e-mails.

I have to travel internationally frequently because of my work, but I leave my laptop’s hardware clock set to UTC and the system clock set to the local time of my home town. This means that, irrespective of where I am in the World, the e-mail client (Thunderbird, in my case) uses the local time of my home town in e-mail headers and calenders. It is not practical to reconfigure Linux for each timezone I happen to be in (see my post Configuring the Linux clock), and, in any case, I want the file system’s timestamps to use one timezone only and all the timestamps in my e-mails and the e-mail client’s calender to use one timezone only, so there is less chance of me getting confused. I could have configured the installation to use UTC for the system clock, but I prefer the system clock to use the timezone of my home town. Of course, even though the system clock is always set to the timezone of my home town, on the Panel clock I select the timezone of the location where I happen to be, so that the Panel clock displays the local time in that timezone.

I wanted to be able to insert a signature at the end of each e-mail, stating my current location and the current time at that location, so that the person receiving the e-mail could tell from where in the World I sent the e-mail and the local time it was sent, as that local time could differ from the time shown in the e-mail header. For example, let us assume that Jane, who lives in the UK and whose system clock is configured for the timezone Europe/London, is making a brief visit to Perth, Australia and sends an e-mail to Dave in the UK at 06:36 on 11 October (Perth time). The e-mail below illustrates the type of signature I wanted to achieve.

Subject: Site visit
From: Jane <jane@acompany.com>
To: Dave <dave@acompany.com>
Date: Sat Oct 10 2015 23:36:40 GMT+0100 (BST)

Hello Dave,

This is to let you know that I have just arrived in Perth and will be
visiting site at 09:00 local time to speak to the client. Tomorrow p.m.
I have a meeting scheduled with our local project manager, so I would
appreciate it if you would e-mail the latest documentation to me. I will
not have spare time until I’m in my room at the hotel tonight but will
read the documents tomorrow a.m. in readiness for the meeting with
the local project manager. Thanks in advance.

Regards,
Jane
Current location: Perth (Australia)
Local time now: Sat Oct 11 06:36:31 2015 AWST

As you can see above, because the OS on Jane’s and Dave’s laptops is configured for the timezone Europe/London, the e-mail header shows the current time in the UK when the e-mail was sent, which was October 10, 23:36 British Summer Time (22:36 UTC), and the signature shows the corresponding local time in Perth, Australia, which was October 11, 06:36 Australian Western Standard Time. It becomes even more confusing if the computer of the person receiving the e-mail is configured for a third timezone. For example, let’s say Dave is based in Seattle, USA rather than the UK. His e-mail client would then display the time in that timezone when the e-mail was sent. This is usually my case, i.e. my Linux installation is configured for Timezone1 but I happen to be in Timezone2 when I send an e-mail to someone who is based in Timezone3 and whose OS is configured for that timezone.

I wanted to use a keyboard shortcut to add a signature to the end of my e-mails, as shown above. I therefore created the Bash script listed below, which I named timezone_signature_GeoNames.sh:

#!/bin/bash

location=$(kdialog --title "Current Location" --inputbox "Enter your location:")

localtime=$(perl /home/fitzcarraldo/now1.pl $location)
place=`echo $localtime | cut -d'|' -f1`
place=$place" "`echo $localtime | cut -d'|' -f2`
timezone=`echo $localtime | cut -d'|' -f4`

if [ $location != "" ]; then
  echo -n "Current location: "
  echo $place
  echo -n "Local time now:"
  /usr/sbin/zdump ${timezone} | cut -d' ' -f2-
fi
echo

Notice that the Bash script uses the GUI dialogues utility kdialog to display a pop-up window prompting me to enter the name of a town/city. As I am using KDE I opted to use a dialogues utility developed for use in KDE, but I could have used Zenity instead.

The Perl script now1.pl is a variant of the Perl script now.pl described in my previous post, modified very slightly in order to facilitate formatting of the output by the Bash script, and is listed below.

#!perl

use strict;
use warnings;

use DateTime;
use Geo::GeoNames;
use URI::Escape;
use Encode;

binmode STDOUT, ':encoding(UTF-8)';

my $city = decode("UTF-8", @ARGV ? shift : 'London');
# N.B. Replace London with your home town/city.

my $geo = Geo::GeoNames->new( username => '************' );
# N.B. Replace the asterisks with your GeoNames user name.

my $result = $geo->search(
q       => uri_escape_utf8($city),
maxRows => 1,
style   => 'FULL'
);

defined $result->[0] or die "Unrecognized city '$city'\n";

my $city_name    = $result->[0]->{name};
my $country_name = $result->[0]->{countryName};
my $time_zone    = $result->[0]->{timezone}{content};
my $time_now     = DateTime->now( time_zone => $time_zone );

#print "$city_name ($country_name) $time_now ($time_zone)\n";

print "$city_name|($country_name)|$time_now|$time_zone\n";

exit 0;

The only thing remaining was to configure a keyboard shortcut to launch the Bash script. I opted to use the key combination Ctrl-Alt-z for the shortcut. As I am using KDE I could have used KDE’s ‘System Settings’ > ‘Shortcuts and Gestures’> ‘Custom Shortcuts’ to specify the shortcut and the name of the script it launches. However, as I also use AutoKey for various shortcuts, I opted to use that instead, so I used the AutoKey GUI to create a shortcut named ‘Insert Current Time’ to use the following command:

output = system.exec_command("/home/fitzcarraldo/timezone_signature_GeoNames.sh")
keyboard.send_keys(output)

Use

I compose my e-mails as usual, and, after entering my name at the end of the e-mail, I press Ctrl-Alt-z. A window then pops up prompting me to enter my current location, which I do and then click on ‘OK’. The location and current time at that location are then added to the end of the e-mail, and it just remains for me to click on the ‘Send’ button in the e-mail client’s window. As the Perl script now1.pl uses the Internet to access the GeoNames database, my laptop must be connected to the Internet when I use the shortcut.

KDialog window that pops up when I use the keyboard shortcut

KDialog window that pops up when I use the keyboard shortcut

If the town/city name consists of more than one word (Rio de Janeiro, for example) then replace spaces with hyphens when you enter the location name in the pop-up window (Rio-de-Janeiro, for example) and then the keyboard shortcut will return the correct location and local time:

Current location: Rio de Janeiro (Brazil)
Local time now: Fri Oct 16 09:28:19 2015 BRT

Find the time now at any town or city Worldwide from the command line in Gentoo Linux

In my hunt for a command to return the current time at any town or city in the World, I recently found a Perl script now.pl posted in 2012 by Jim Monty on grokbase. The script uses a Perl module to access the database of the GeoNames Web site. To use the module you need to have a user account at the GeoNames Web site and be connected to the Internet. The script also uses the modules URI::Escape and DateTime.

In the case of Gentoo Linux, ebuilds for some of the Perl modules used by now.pl are not available in the main Portage tree, so I installed them from a Portage local overlay, as explained below.

First I created in my local overlay an ebuild for the Perl module Geo::GeoNames and then merged it:

# mkdir -p /usr/local/portage/dev-perl/Geo-GeoNames
# cd /usr/local/portage/dev-perl/Geo-GeoNames
# nano -w Geo-GeoNames-1.01.ebuild
# ebuild Geo-GeoNames-1.01.ebuild manifest
# emerge --ask Geo-GeoNames

The ebuild Geo-GeoNames-1.01.ebuild I created is listed below:

EAPI=5

MODULE_AUTHOR=BDFOY
inherit perl-module

DESCRIPTION="Provides a perl interface to the webservices found at http://api.geonames.org"

SLOT="0"
KEYWORDS="alpha amd64 ~arm hppa ia64 ~mips ppc ppc64 ~s390 ~sh sparc x86 ~amd64-linux ~x86-linux ~ppc-macos ~x86-macos ~sparc-solaris"
IUSE=""

RDEPEND="
        dev-perl/Module-Build
        "
DEPEND="${RDEPEND}"

SRC_TEST=do

I’m not sure if I declared the correct dependencies in DEPEND and RDEPEND, but the Geo::GeoNames module is merged in my installation and functions correctly.

The Perl script also uses the Perl module URI::Escape, which I found out is part of the package dev-perl/URI in the Portage main tree and was already installed, so I did not need to do anything further as far as that was concerned.

Next I needed to install the Perl module Date::Time. Fortunately there is an ebuild for dev-perl/DateTime in the main Portage tree, so I merged that package directly:

# emerge --ask DateTime

Then I surfed to the GeoNames Web site and registered for a user account. My thanks go to the people who provide and maintain the site and database.

I then created a file /home/fitzcarraldo/now.pl containing the Perl script listed in Jim Monty’s post of Aug 19, 2012 on the Web page: [DateTime] Is there timezone data for any Indian cities such as Mumbai, Dehli, &c.?. My thanks also go to Jim Monty for posting his script.

My initial attempts at running now.pl resulted in an error message warning about a missing Mojo::UserAgent Perl module. I therefore needed to install the package Mojolicious but, unfortunately, the main Portage tree does not have an ebuild for it. I could have either added a third-party overlay (e.g. srcshelton) which contains a Mojolicious ebuild or downloaded the ebuild and put it in my local overlay. I opted for the latter, and merged it:

# mkdir -p /usr/local/portage/dev-perl/Mojolicious
# cd /usr/local/portage/dev-perl/Mojolicious
# cp /home/fitzcarraldo/Downloads/Mojolicious-5.30.ebuild .
# ebuild Mojolicious-5.30.ebuild manifest
# emerge --ask Mojolicious

Update July 24, 2017: the package Mojolicious is now in the Portage main tree, so you can now merge that instead of adding it to a local overlay.

Despite the examples given in Jim Monty’s post using now.pl with place names containing diacritics, in my case the script could not handle them, so I made a couple of small modifications, and the script I’m using is show below:

#!perl
use strict;
use warnings;

use DateTime;
use Geo::GeoNames;
use URI::Escape;
use Encode;

binmode STDOUT, ':encoding(UTF-8)';

my $city = decode("UTF-8", @ARGV ? shift : 'London');

my $geo = Geo::GeoNames->new( username => '***********' );
# N.B. Replace the asterisks with your GeoNames user name.

my $result = $geo->search(
q       => uri_escape_utf8($city),
maxRows => 1,
style   => 'FULL'
);

defined $result->[0] or die "Unrecognized city '$city'\n";

my $city_name    = $result->[0]->{name};
my $country_name = $result->[0]->{countryName};
my $time_zone    = $result->[0]->{timezone}{content};
my $time_now     = DateTime->now( time_zone => $time_zone );

print "$city_name ($country_name) $time_now ($time_zone)\n";

exit 0;

I made the script executable and ensured my user account could use it:

# chmod +x /home/fitzcarraldo/now.pl
# chown fitzcarraldo:fitzcarraldo /home/fitzcarraldo/now.pl

Now if I enter the name of a town or city anywhere in the World while my laptop is connected to the Internet, the script prints the town/city name, country, local time and the time zone’s name as given in the zoneinfo database (a.k.a. ‘Olsen database’, ‘tz database‘ and ‘IANA time zone database’):

$ cd
$ perl now.pl
London (United Kingdom) 2015-09-25T23:10:16 (Europe/London)
$ perl now.pl "London Canada"
London (Canada) 2015-09-25T18:10:22 (America/Toronto)
$ perl now.pl Paris
Paris (France) 2015-09-26T00:10:29 (Europe/Paris)
$ perl now.pl "New York"
New York (United States) 2015-09-25T18:10:41 (America/New_York)
$ perl now.pl Tokyo
Tokyo (Japan) 2015-09-26T07:10:50 (Asia/Tokyo)
$ perl now.pl "Mexico City"
Mexico City (Mexico) 2015-09-25T17:10:59 (America/Mexico_City)
$ perl now.pl "Kuala Lumpur"
Kuala Lumpur (Malaysia) 2015-09-26T06:11:09 (Asia/Kuala_Lumpur)
$ perl now.pl "São Paulo"
São Paulo (Brazil) 2015-09-25T19:11:29 (America/Sao_Paulo)
$ perl now.pl Maceio
Maceió (Brazil) 2015-09-25T19:11:39 (America/Maceio)
$ perl now.pl Maceió
Maceió (Brazil) 2015-09-25T19:11:48 (America/Maceio)
$ perl now.pl "Várzea Grande"
Várzea Grande (Brazil) 2015-09-25T18:12:04 (America/Cuiaba)
$ perl now.pl "Mos Eisley"
Unrecognized city 'Mos Eisley'

now.pl works by first using the Geo::GeoNames module to look up in the GeoNames database via the Internet the time zone for the town/city you have specified, then using the Date::Time module to look up the time in that time zone from the zoneinfo data in your installation, based on the time now in your installation’s system clock. In other words, if your system clock is, for example, 3 minutes ahead of actual time then the time returned by now.pl for the relevant time zone would also be 3 minutes fast. But if your system clock is correct, the script would return an accurate time for the requested town/city.

My interest in finding a command that returns the current time at any town or city around the Globe was because I wanted to create a keyboard shortcut to insert a signature in my e-mails, displaying my current location and the local time wherever I happened to be (I have to travel internationally frequently because of my work). In my next post I will explain how I created such an e-mail signature.