Arduino Project: Platform Independent Clipboard

The Platform Independent Clipboard

The source code for the project is available on the GitHub: https://github.com/bitblueprint/Arduino-UniversalClipboard – this article references line numbers in the 596944 commit. This source code is released under an open MIT License, allowing you to copy, modify and redistribute it with your changes, commercially or not. If you like the project – please ping me on Twitter, I love when people get something out of whatever I publish.

Who would need such a thing?

The idea of a platform independent clipboard first came to me when a relative working at the state national police force of Denmark, introduced me to a challenge of theirs.

When a police officer is dealing with a case, it involves repeatedly entering name and birthdates into several computer systems. Some of these systems are different applications on the same computer, some are on different physical computers and some of these have entirely different operating systems. This part of the officer’s daily work has been identified by consultants as time consuming and error prone – resulting in additional time wasted, when correcting data or not finding the correct persons, due to mistakes made when typing.

Word of caution – this is almost password sniffing key-logger

With minor changes of the device’s source code, it could act as a key-logging device, that could potentially snoop/sniff/eavesdrop on classified information if hidden from the person using the computer. It is essentially a device performing a man in the middle attack.

Therefore it is important to permanently force the activation pin low, once this device is introduced in into a real police station. This way tinkers have a much harder time connecting to the Arduino in a state where it is reprogrammable. Alternatively change the source code to not wait for an activation signal before acting as a keyboard towards the computer.

Introduction

This project aimed at designing and implementing a product prototype, utilizing both hardware and software, to propose a viable solution for the aforementioned problem.

The product should help the police officer transfer confidential data such as names and birthdates from one physical machine to another, reducing the time spent and the errors occurring when retyping the same data into multiple computer systems.

Requirements

From an initial conversation with two officers, I formulated an ordered list of functional requirements – an initial backlog of user stories:

  1. As an officer, I want to be able to use the keyboard as usual, so that I can perform my daily work without any additional challenges introduced by technology.
  2. As an officer, I want to selectively start recording a sequence of keystrokes starting from the press of “a special key” until the press of the another or the same special key so that I can control from when to start the recording of my keystrokes.
  3. As an officer, I want to be able to replay a sequence of keystrokes previously recorded, so that I can retype names, numbers, etc. without introducing typing errors.
  4. As an officer, I want to be able to record and playback multiple sequences keystrokes so that I can store and replay more than one sequence of keystrokes.
  5. As an officer, I want to be able to transfer the recorded keystrokes between physical computers on a medium so that I can reuse the a recorded sequence from one machine on another machine.
  6. As an officer, I want to be able to encrypt and password protect my transfer medium (NFC chip) so that I do not have to worry that confidential data is compromised if I loose the transfer medium.

As qualitative / non-functional requirements, the following observations are made

  1. The cost of the device matters – we want a solution that is not too expensive in production. 80 euro (~ 600 DKK) per device seems to be a reasonable upper limit.
  2. Perhaps needless to say, the device has to be user friendly. Meaning that an officer should be able to operate the universal clipboard with little to no training and with no more instructions that what can be written on a sticky label on-top of the device.

What hardware is needed?

Why not just implement this in software? Well, as the problem suggests, this may take a long time as we are talking about a couple of different applications on a couple of different operating systems. Figuring out the plumbing, how to write a slick user interface across the broad range of configurations would probably end up taking too much time.

So I thought to myself – could this be implemented independently of the software on the PC? This was what I wanted to do.

These is the complete list of parts that you’ll need to build one platform independent clipboard:

This totals 563 DKK per device or alternatively 70 euro (~521 DKK) when producing 100 or more in a batch.

Implementing user story #1 – Being able to use the keyboard as usual

So – a user want to connect a device to her computer and her keyboard to the device, after which she expects the computer to respond to key presses made on the keyboard, as if nothing was in the middle.

Hardware compatibility issues encountered early on in the project

Initially I tried implementing user story #1 using the Arduino Due – I figured it had two USB ports, one that could act as a native USB device towards the computer and one that act as USB host towards the keyboard.

Unfortunately it turned out that the programming USB port on the Due, can only easily be used for programming the device. The native USB port can be used for either acting as a client (keyboard, mice, etc) towards the computer or as a host for peripherals, but of course not both at the same time.

The article about the Due on Arduino.cc, talks about the native USB port:

Using the Native port enables you to use the Due as a client USB peripheral (acting as a mouse or a keyboard connected to the computer) or as a USB host device so that devices can be connected to the Due (like a mouse, keyboard, or an Android phone).

Bummer – I tried extending the Due with a USB host shield, but it seemed like the Due got confused and thought I was asking the native port to act as a USB host device, even when using the libraries provided with the host shield.

I then tried implementing user story #1 using the Arduino Uno and the USB host shield – this should in principle be possible, but the Uno board’s firmware has to be flashed between each reprogramming of the board, as it needs different firmware for the board to act as a keyboard towards the computer than the firmware needed to act as an Arduino to the computer. Read more about flashing the Uno to act as a peripheral to the computer on this hackaday.com article.

What to do? Go search the internet for more information.

It turns out that the Arduino Leonardo board has an interesting feature very suitable for this application. Its single USB port can act as both a programming port, enabling serial communication towards the computer while at the same time acting as several peripherals towards the PC.

Combined with a USB host shield the Arduino Leonardo gives the unique possibility of being able to listen for ingoing keystrokes from a keyboard connected to the USB host shield while being able to send these keystrokes onto the PC through the USB port on the Leonardo.

Registering key strokes from the USB host shield

This is done using the USB host shield, through the library provided by Oleg Mazurov and Alexei Glushchenko from [email protected], Kristian Lauszus and Andrew Kroll on GitHub (download).

Once the library has been installed, the projects source code line #143 and #146 initiates global objects USBHost and HidKeyboard that will enable communication with a keyboard connected to the USB Host shield.

USB UsbHost;
HIDBoot HidKeyboard(&UsbHost);

Once initialized, the HidKeyboard object has a HIDReportParser named keyboard_in (initialized on line #157) registered on line #339 and the USB host shield is given program control on every loop-call that the Arduino does – see line #362

UsbHost.Task();

The ProxyKeyboardParser class, which is implemented for this particular project and extended from the KeyboardReportParser parser (which is again a specialization of the HIDReportParser), provides callback methods for the HidKeyboard object to call when the user interacts with the keyboard.

Sending key strokes onward to the connected computer

This step was the most trouble I had in the project. The Arduino keyboard library functionality kindly translates ASCII characters to a KeyReport object with key strokes that the computer understands. This way it is easy to send upper and lower-case letters using the Keyboard.write method. But if you have the raw key strokes and the modifier byte (communicating the left/right ctrl, shrift, alt and GUI key states), this turns out to be an anti-feature.

I looked into the Arduino cores implementation of the press and release methods and found that I could extend the Keyboard_ class and overwrite the implementation (lines 88-123 in the projects source code) to not do a transformation from ASCII to key stroke bytes and change of modifiers.

// Sending out a key press to the computer
size_t KeyboardOut::press(uint8_t k) {
  uint8_t i;
  // Add k to the key report only if it's not already present
  // and if there is an empty slot.
  if (_keyReport.keys[0] != k && _keyReport.keys[1] != k && 
    _keyReport.keys[2] != k && _keyReport.keys[3] != k &&
    _keyReport.keys[4] != k && _keyReport.keys[5] != k) {
 
    for (i=0; i<6; i++) {
      if (_keyReport.keys[i] == 0x00) {
        _keyReport.keys[i] = k;
        break;
      }
    }
    if (i == 6) {
      setWriteError();
      return 0;
    }	
  }
  send_report();
  return 1;
}
 
// Sending out a key release to the computer
size_t KeyboardOut::release(uint8_t k) {
  uint8_t i;
  // Test the key report to see if k is present.  Clear it if it exists.
  // Check all positions in case the key is present more than once (which it shouldn't be)
  for (i=0; i<6; i++) {
    if (0 != k && _keyReport.keys[i] == k) {
      _keyReport.keys[i] = 0x00;
    }
  }
  send_report();
  return 1;
}

This functionality is called from the ProxyKeyboardParser class (lines 159-180). A simplified version of the source code below, shows how key presses from the USB host shield is communicated to the computer using the keyboard_out object.

void ProxyKeyboardParser::OnControlKeysChanged(uint8_t before, uint8_t after) {
  keyboard_out.set_modifiers(after);
  keyboard_out.send_report();
  working_blink();
}
 
void ProxyKeyboardParser::OnKeyDown(uint8_t mod, uint8_t key)
{
  keyboard_out.press(key);
  working_blink();
}
 
void ProxyKeyboardParser::OnKeyUp(uint8_t mod, uint8_t key)
{
  keyboard_out.release(key);
  working_blink();
}

The working LED blinks whenever a key press or release is intercepted – to show the user that something is happening, in the hopes of making the device more user friendly.

Implementing user story #2 – Being able to record keystrokes

In order for the user to be able to record their keystrokes, she must first activate the recording. This is done by pressing a special combination of keys. I thought of a sequence which I anticipated was rarely used when entering data into the systems at the police force. This has remains to be finally verified, but the code can easily be modified to support an alternative activation key sequence.

The ProxyKeyboardParser’s OnKeyDown method was extended with a call to an axillary function which evaluates if the user is trying to start or stop a recording.

void ProxyKeyboardParser::OnKeyDown(uint8_t mod, uint8_t key)
{
  bool controlled = intercept_recording_command(mod, key);
  if(!controlled) {
    intercept_recording_key(mod, key);
    keyboard_out.press(key);
  }
  working_blink();
}

The implementation of the intercept_recording_command function checks for three scenarios:

  1. Start the recording of keystrokes. (line 234)
  2. Replay a recording of keystrokes. (line 238)
  3. Stop the recording of keystrokes. (line 242)

The replaying of keystrokes are for the next user story to implement, whereas this focusses on starting and stopping a recording.

When the user holds down the left or right control key, while holding down the left or right shift key – and presses one of the numbers one though nine on the numeric pad, the recording of a sequence of keystrokes begins. See lines 234-237.

When recording is active and the user presses either the tabular key, the return/enter on either the main keyboard or the numeric pad, the recording is stopped.

When starting  are recording the an integer variable active_channel_index is set to zero and a call to the clear_channel function clears out the recording buffer (an array called channels), by writing zeros all in its entire length.

As seen above, the OnKeyDown callback on the ProxyKeyboardParser also calls the intercept_recording_key function, if the key press was one of the aforementioned command key scenarios.

This intercept_recording_key function saves the modifiers and key to the channels buffer, incrementing the active_channel_index by two, for every key pressed by the user. That way both the state of the eight modifier buttons are saved along with the key presses.

Implementing user story #3 – Being able to replay keystrokes

Replaying keystrokes can only happen when the user is not recording. If this is the case, the user replays by pressing either the left or right control key, together with one of the numbers one through nine on the numeric pad.

When this happens, the replay_channel function (lines 289-311) is called. A loop is going through each of the two-tuples of modifier and key:

  • First determining if the key is larger than zero. If this is not the case, the replay is stopped immediately – as a zero key indicates the end of a sequence of keystrokes.
  • Then the modifier byte of the report is set.
  • The key is pressed and
  • finally the key is released.
keyboard_out.set_modifiers(mods);
keyboard_out.press(key);
keyboard_out.release(key);

The state of the modifiers are saved (line 307) before and restored (line 291) after the channel is replayed to the computer.

Implementing user story #4 – Record and replay multiple sequences

This was actually implemented together with the user stories #2 and #3, as I found it easier to have this functionality implemented from the beginning, rather than implementing it simple and advancing from there.

The buffer, called channels, in which the keystrokes are recorded, is actually a two dimensional array, where the first index dictates which of nine channels the recording is to be stored in or replayed from. Every function manipulating keystroke data, takes an unsigned integer argument referencing which channel the operation is to be performed in.

This is again why the user presses control + shift + a number on the numeric pad, when starting a recording. The latter tells the device which channel the recording is going into, the same goes for the replay.

Implementing user story #5 – Storing sequences on a physical medium

The RFDI/NFC shield is connected and it is verified that the Arduino Leonardo can communicate with it (it uses an I²C bus). It had to be modified acording to this guide from Adafruit.com, removing the connection to pin 2 and re-routing it to pin 6.

As I have had an agile approach to this, estimated what I could get implemented in the project period and done my implementation one user story at a time, I didn’t get to implement the storing of keystroke data on an RFID MiFare tag. This was mainly due to trouble in the initial phase of finding compatible hardware and implementing a keyboard proxy, bypassing the Arduino’s anti-feature of translating ASCII characters to key strokes.

Implementing user story #6 – Securing the physical medium

This could be implemented using hashes generated from a users password as keys when storing data on the MiFare card. The user could enter this password as the first key sequence when inserting a new RFID tag in the device.

The only problem is that the MiFare’s encryption scheme is no longer considered save as hackers as early as 2008 discovered ways to reveal secrets off the tags in minutes using a regular desktop computer, see http://www.computerworld.com/article/2537817/security0/how-they-hacked-it–the-mifare-rfid-crack-explained.html.

One could employ a stronger encryption scheme, such as an algorithm in the AES family.

Further work

Below are suggestions for further work, for me or anyone reading this:

  • Implementing the two remaining user stories #5 and #6 as mentioned above,
  • Verifying the control sequence (ctrl + shift + # on numeric pad) is not used by other programs at the police,
  • verify that the relevant computers at the police force supports USB, alternatively implement the sending part of the device using the PS/2 keyboard protocol.

Concluding

I have build a device, using the Arduino platform that sits between a USB keyboard and a USB enabled PC, forwarding any key press, except the ones that are used to control the recording, stopping and replaying of key strokes on nine channels.

The device fulfils the need of a platform independent clipboard across multiple applications on a single computer, but the transfer of key strokes between machines using a physical medium remains.

I hope you enjoyed reading this article – please feel free to ping me on e-mail or Twitter (@kraenhansen), in case you have comments or questions.

How I get notifications on rentable rooms on findroommate.dk

dialog-screenshot
This is the second tool in my toolchain developed on my mission of programming myself out of my childhood room at my parents. Findroommate.dk has a search alert feature, where they claim that “A new room was just announced!”, but when you lookup the date that the room was announced, its always three days old. By new tool basically performs a search on the website for rooms in my desired area of interest with my desired bounds on the rent. A log is kept on the rooms already seen and the search result is then processed to check if any new room has appeared. When a new room appears I get a message box notification and a sound file starts playing until the message dialog is dismissed.
When I hit the “Yes” button of the dialog, I get the room shown in my Google Chrome webbrowser. The script performs a search every 60 seconds. Dependencies are the imported libraries as well as the Google Chrome browser and the mplayer CLI multi media player.

import requests
import json
import pickle
import time
import subprocess
import pygtk
pygtk.require('2.0')
import gtk
 
BROWSER_PATH = "google-chrome"
SOUNDPLAYER_PATH = "mplayer"
ALARM_SOUND = "Pleasurekraft - Carny.mp3"
SLEEP_DELAY = 60
 
def alarm(identifier, username):
	print "Found a new room! %s #%u" % (username, identifier)
	sound_process = subprocess.Popen([SOUNDPLAYER_PATH, ALARM_SOUND])
	dialog = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format=None)
	dialog.set_markup("Found a new room on findroommat.dknDo you wish to show it now?")
	answer = dialog.run()
	sound_process.terminate()
	if answer == gtk.RESPONSE_YES:
		browser_process = subprocess.Popen([BROWSER_PATH, "http://www.findroommate.dk/Profiler/%s" % username])
 
def fetch_profiles():
	headers = {
		'Content-Type': 'application/json',
	}
	data = {
		'UserType':0,
		'MapCenterBoundsLat':'55.67246738804332',
		'MapCenterBoundsLng':'12.583579399999962',
		'ViewPortLat':'55.77648968046662',
		'ViewPortLng':'12.782019951757775',
		'RentLow':'2000',
		'RentHigh':'4000',
		'RentalPeriodsArray':'1,2,3',
		'SmokingMustBeAllowed':False,
		'AnimalsMustBeAllowed':False,
		'WifiMustBePresent':False,
		'CableTvMustBePresent':False,
		'WasherMustBePresent':False,
		'DishWasherMustBePresent':False,
		'IncludeWomen':True,
		'IncludeMen':True,
		'PersonIsSmoker':False,
		'ExcludePersonsWithChildren':False,
		'MustHavePicture':False,
		'SortingTypeId':'adPlaced',
		'SortingValue':'desc'
	}
	r = requests.post('http://www.findroommate.dk/API/Search/Search.asmx/GetSearchResults', data=json.dumps(data), headers=headers)
	response = json.loads(r.content)
	return_value = response["d"]["ReturnValue"]
	return json.loads(return_value)
 
if __name__ == '__main__':
	print 'New room alarm on findroommate.dk'
	file_name = "ids.pickle"
	first_run = False
	try:
		with open(file_name) as f:
			visited_profile_ids = pickle.load(f)
	except IOError as e:
		visited_profile_ids = []
		first_run = True
 
	while True:
		profiles = fetch_profiles()
 
		print "Found %u profiles when searching." % profiles['Count']
		for profile in profiles['Profiles']:
			identifier = profile['UserIdInt']
			username = profile['Username']
			if identifier not in visited_profile_ids:
				visited_profile_ids.append(identifier)
				if not first_run or True:
					alarm(identifier, username)
 
		with open(file_name, 'w') as f:
			pickle.dump(visited_profile_ids, f)
 
		time.sleep(SLEEP_DELAY)

How I monitor my position on the waiting lists of findbolig.nu

Instacoded
I am in the process of getting myself a new apartment. This is why I signed myself up for the website findbolig.nu on which I can sign myself up for a rented apartment at a selection of dormitories and alike.
It is not possible to get an estimate on the approximate time I have to wait on waiting lists, but I can see my number in the waiting list of all the buildings I have signed myself up to.
This is why I build myself a small python script which logs me in, fetches the list of buildings I have signed myself up to, fetches my position in the queue and appends this to a CSV datafile.
In case you would like to feel inspired by this, feel free to fetch this for personal non-commercial use.
This tool can be ran by downloading it and naming it something like original like “findbolig-venteliste-extractor.py”.
Afterwards run it with the following parameters:

  • -u or –username : Your username on findbolig.nu
  • -p or –password : Your password on findbolig.nu
  • -o or –output : The .csv datafile that you would like to append your data to.
  • -l or –log : The level of logging you would like: Either, error, warning, info or debug.

You could for example run:

python findbolig-venteliste-extractor.py -u name -p pass -o placements.csv -l debug

This will crawl and produce a CSV datafile, appending to it if ran multiple times, with a date stamp as the first column and every other column begin the position in the queue of a particular building, represented with the buildings unique identifier.
BTW: The script understands if the buildings are removed or added from the wishlist on the website.
I also did a small spreadsheet to visualize the data, this links to an external document, which could be placements.csv outputted above.

Click here to download the ODS visualizing the placements.

The script can also be downloaded directly from github.

import argparse
import logging
import sys
import re
import time
import json
import csv
import datetime
import requests
 
log = logging.getLogger(__name__)
logging.getLogger('pyactiveresource').setLevel(logging.WARNING)
 
class FindBoligNuClient:
	URL_base = "http://www.findbolig.nu/"
	URL_secure_base = "https://www.findbolig.nu/"
	URL_login = URL_base+"logind.aspx"
	URL_venteliste = URL_base+"Findbolig-nu/Min-side/ventelisteboliger/opskrivninger.aspx?"
	URL_placement = URL_secure_base+"Services/WaitlistService.asmx/GetWaitlistRank"
	session = requests.Session()
	def __init__(self):
		log.info("Initializing the findbolig.nu client")
		response = self.session.get(self.URL_base)
		if "Findbolig.nu" not in response.text:
			log.error("It seems like the findbolig.nu website is down or has changed a lot.")
			sys.exit(-1);
 
	def login(self, username, password):
		log.info("Logging into %s using username '%s'", FindBoligNuClient.URL_login, username)
		# Fetch the regular login page.
		response = self.session.get(self.URL_login)
		# Extract input names and values
		data = dict()
		content = response.text
		input_fields = re.findall("<input(.*)>", content, flags=re.IGNORECASE)
		for field in input_fields:
			name = re.findall('.*name="([^"]*)".*', field)
			value = re.findall('.*value="([^"]*)".*', field)
			if name:
				if value:
					data[name[0]] = value[0]
				else:
					data[name[0]] = ""
		data["ctl00$placeholdercontent_1$txt_UserName"] = username
		data["ctl00$placeholdercontent_1$txt_Password"] = password
		data["__EVENTTARGET"] = "ctl00$placeholdercontent_1$but_Login"
		data["__EVENTARGUMENT"] = ""
 
		response = self.session.post(self.URL_login, data=data)
		if "Log af" in response.text:
			# Extract users full name.
			name = re.search('<span id="fm1_lbl_userName">(.*)&nbsp;</span>', response.text)
			log.info("Logged in as %s", name.group(1))
			return True
		else:
			return False
 
	def extract_waitinglist_references(self):
		result = []
		response = self.session.get(self.URL_venteliste)
		table_content = re.search('<table[^>]*id="GridView_Results"[^>]*>(.*?)</table>', response.text, flags=re.IGNORECASE|re.DOTALL)
		if table_content:
			table_content = table_content.group(1)
			rows = re.findall('<tr class="rowstyle"[^>]*>(.*?)</tr>', table_content, flags=re.IGNORECASE|re.DOTALL)
			for row in rows:
				#collumn = re.findall('<td[^>]*>(.*?)</td>', row, flags=re.IGNORECASE|re.DOTALL)
				bid = re.search('href="/Ejendomspraesentation.aspx?bid=([^"]*)"', row, flags=re.IGNORECASE|re.DOTALL)
				if bid:
					bid = int(bid.group(1))
					result.append(bid)
		return result
 
	def extract_waitinglist_placements(self, bids, sleep=1):
		result = {}
		for bid in bids:
			log.debug("Requesting placement on building #%u.", bid)
			data = {
				'buildingId': bid
			}
			headers = {
				'Content-Type': 'application/json; charset=UTF-8'
			}
			response = self.session.post(self.URL_placement, data=json.dumps(data), headers=headers)
			if response:
				response = response.json()
				if response["d"] and response["d"]["WaitPlacement"]:
					result[str(bid)] = int(response["d"]["WaitPlacement"])
					log.debug("It was %u.", result[str(bid)])
				else:
					raise RuntimeError("Error reading a placement: Error in JSON structure.")
			else:
				raise RuntimeError("Error reading a placement.")
			time.sleep(sleep)
		return result
 
def write_data(data):
	fieldnames_temp = set()
	try:
		output_file = open(args.output,'r')
		reader = csv.DictReader(output_file, delimiter=',')
		all_data = list(reader)
 
		# Extract the fieldnames
		if reader.fieldnames:
			for name in reader.fieldnames:
				if name != "date":
					fieldnames_temp.add(str(name))
 
		output_file.close()
	except IOError:
		log.info("There was no existing data in the datafile.")
		all_data = list()
 
	# Do a union over all elements of the list.
	fieldnames = ["date"]
	fieldnames_temp |= set(data.keys())
	fieldnames.extend(list(fieldnames_temp))
 
	# Insert the date as the first field.
	data["date"] = datetime.datetime.now().date().isoformat()
	# Add this datapoint as new data.
	all_data.append(data)
 
	output_file = open(args.output,'wb')
	writer = csv.DictWriter(output_file, delimiter=',', fieldnames=fieldnames)
	writer.writeheader()
	writer.writerows(all_data)
	output_file.close()
 
if __name__ == "__main__":
	parser = argparse.ArgumentParser()
	parser.add_argument('-u', '--username', help='Your username on findbolig.nu.', required=True)
	parser.add_argument('-p', '--password', help='Your password on findbolig.nu.', required=True)
	parser.add_argument('-o', '--output', help='The output file.', required=True)
	parser.add_argument('-l', '--log', help='Set the log level to debug.', default='WARNING')
	args = parser.parse_args()
 
	numeric_level = getattr(logging, args.log.upper(), None)
	if not isinstance(numeric_level, int):
		raise ValueError('Invalid log level: %s' % args.log)
	logging.basicConfig(format='%(levelname)st%(message)s', level=numeric_level)
 
	print "findbolig.nu venteliste extractor v.0.1n"
 
	client = FindBoligNuClient()
	success = client.login(args.username, args.password)
	if not success:
		log.error("Couldn't login using the credentials provided.")
		sys.exit(-2)
	# Fetch bids for all buildings on the whishlist.
	venteliste_bids = client.extract_waitinglist_references()
	# Iterate the list of bids and return a dict of the placements.
	venteliste_placements = client.extract_waitinglist_placements(venteliste_bids, 0)
	# Append to the datafile.
	write_data(venteliste_placements)

Collabedit Java compiler and runner

I have an interview tonight, where I am told that I am supposed to do Java coding on a shared document via the collabedit.com platform.
As I am checked on both speed and correctness I figured that it would be nice to know when I did obvious syntax errors, but as far as I can see, collabedit supports syntax highlighting but does not support any error detecting or running of the actual program.
But it is possible to download your shared document, with the click of a mouse.
This is why I have just hacked together a small python script that listens for a file to be downloaded, and then runs the javac and java tools on it.
I call the script collabedit-java-compiler.py, and it is created to be used on a linux environment, you might be able to use it on other platforms by fx changing the os.system("clear") to os.system("cls") or likewise.

If you have any suggestions, feel free to comment.

#!/usr/bin/python
# Author: Kraen Hansen (www.creen.dk)
# Date: November 2011
import sys
import os.path
from time import sleep
 
def compile_java(filename, classname, autorun):
	print "Compiling "+filename
	result = os.system("javac "+filename)
	if result == 0:
		print "Compiled successfully!"
		should_run = autorun
		if not should_run:
			run = raw_input("Would you like to run it? [Y/n]")
			if(run != "n" and run != "N"):
				should_run = True
		if should_run:
			os.system("clear")
			folder = os.path.split(filename)[0]
			runcommand = "java -cp ""+folder+"" "+classname
			print "Running the program! ("+runcommand+")"
			print "------------------------------"
			print ""
			result = os.system(runcommand)
			print ""
			print "------------------------------"
			print "Program terminated with the signal:", result
			raw_input("Press any key to continue.")
	else:
		raw_input("Press any key to continue.")
 
def collabedit_java_compiler(filename, classname, autorun):
	running = True
	while(running):
		os.system("clear")
		if(os.path.isfile(filename)):
			print "Found the source file!"
			compile_java(filename, classname, autorun)
			print "Removing the file ..."
			os.remove(filename)
		else:
			print "Didn't find a file ... waiting 1 sec"
		sleep(1)
 
if __name__ == "__main__":
	print "Started the collabedit java compiler."
	if len(sys.argv) == 3:
		filename = sys.argv[1]
		classname = sys.argv[2]
		collabedit_java_compiler(filename, classname, True)
	else:
		print """Usage: "path to downloaded file" "Main java class":"""
		print """Example: ./collabedit-java-compiler.py /home/creen/Downloads/n6qp8.java Test"""

How I made Gmail my default email client (on linux)

So there I was. I just signed up for yet another membership in an organisation an I got myself yet another email to check. So I decided to add the 5th email inbox on my Thunderbird (2.0.0.24) desktop email client. But I quickly realized that 5 inboxes and lots of separate calendar feeds was causing Thunderbird a dramatic loadtime and with the GUI and data fetching running in the same thread, this caused the application to hang more than 30+ seconds every time I just wanted to open my email client to check my mail.

So I decided to do something that I should have done a long time ago: Use Google products as primary tools for managing my communications.

In this process I used a fair amount of time on adjusting the products to fit my needs, which included:

  • Adding all my different inboxes on different mailservers. (Using the “check mail using POP3” feature of Gmail).
  • Sending mail through my webhost’s SMTP server, which removed the “send on behalf of …@gmail.com” field on the e-mail header of the messages I sent out.
  • Creating a lot of filters for automatic categorization of my incoming e-mails.
  • Enabled IMAP support, so I could fetch my mail from a (Android) smartphone.
  • Added some lab stuff:
    • Custom date formats: 24-hour clock (14:57) or show dates with the day first (31/12/07).
    • Refresh POP accounts: Fetch messages from your POP accounts on demand by using the refresh link on top of the inbox.

But using a web application as an email client adds some problems. One which I made a solution for.

Clicking a mailto: link on a webpage or pressing your e-mail hotkey on your keyboard doesn’t bring you to your Gmail client. But this I fixed with a small python script (I called it gmail.py).

#!/usr/bin/python
# Author: Kraen Hansen (www.creen.dk)
# Date: February 2011
import sys
import os
import urlparse
 
if __name__ == '__main__':
	command = ""
	if len(sys.argv) == 2 and sys.argv[1] != "":
		argument = urlparse.urlparse(sys.argv[1])
		if(argument.scheme == 'mailto'):
			mailoptionsSplitted = argument.path.replace("?","&").split("&")
			options = {}
			if(len(mailoptionsSplitted) >= 1):
				options['to'] = mailoptionsSplitted[0]
			for option in mailoptionsSplitted:
				temp = option.split("=")
				if(len(temp) == 2):
					options[temp[0]] = temp[1]
			#print options
			command += "google-chrome --app='https://mail.google.com/mail?view=cm&tf=0"
			if 'to' in options.keys():
				command += "&to="+options['to']
			if 'cc' in options.keys():
				command += "&cc="+options['cc']
			if 'bcc' in options.keys():
				command += "&bcc="+options['bcc']
			if 'subject' in options.keys():
				command += "&su="+options['subject']
			if 'body' in options.keys():
				command += "&body="+options['body']
			command += "'";
		else:
			print "Unknown scheme:",argument.scheme
	else:
		command = "google-chrome 'https://mail.google.com/mail/#inbox'"
	os.system(command);

I saved this in my home folder of my linux machine (Linux Mint 8 / Helena with Gnome desktop environment and the Google Chrome webbrowser) and made sure that I had it had permissions for execution. Then I opened my “Preferred Applications” settings from the applications menu, where I specified that I would like to use my new script as my “Mail Reader”. (See the update below, if you cannot find the “Custom” option in the list for Mail Readers)
Now my keyboard pops up Gmail in a chrome browser (you can change this for any preferred browser) if I hit the hotkey on the keyboard and it opens the gmail compose dialog if I click a mailto: link on a webpage. It is worth noticing that the script supports the “to”, “cc”, “bcc”, “subject” and “body” fields of the mailto: querystring.

I hope this gives some inspiration to how you could set up your gmail and please let me know if you have done anything smart on your setup.

Update 22/9:

So I just installed Linux Mint 11 (derived from Ubuntu 11.04) and I wished to reproduce my solution, but sadly the Preferred Applications have been given redesigned, so I didn’t have the Customs option in the dropdown menu.

This is simply because it is removed. So I looked into how to get it there.
I realised that this was controlled by the MimeType value of .desktop elements in the
~/.local/share/applications folder.

So here is how to fix it:

Create a new file called gmail.desktop in the ~/.local/share/applications folder (~ being your /home/[username]/ folder of course).
Past into this file, something like this:

#!/usr/bin/env xdg-open

[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Icon=/home/[username]/gmail_logo.png
Name=Gmail
Exec=/home/[username]/gmail.py %u
Comment=Launch the gmail web application
Categories=Application;Network;Email;
MimeType=x-scheme-handler/mailto;

You have to change two things:

  1. Change the Icon variable to point to an image of the gmail logo. I have created one, with transparent background. You will have to download this and change the Icon variable to point to this.
  2. Change the Exec to option to match the location of your gmail.py script (pasted above). Remember to leave in the %u.

Take notice of the last line, stating the MimeType, as being x-scheme-handler/mailto, this makes it show up as a valid email reader in the preferred applications dialog.