#!/usr/bin/env python

from Xlib import X, display
import os, sys, getopt


################
#Global Vars
#
#keycode 86 is the keypad +
#keycode 9 is esc
#To find the codes for your keys run xev and place the mouse over the window and hit a key.  You will see a press and release event.
swap_key = 86
kill_key = 9
#If you want to add keys, add them in here or we won't listen for them
keys = [swap_key,kill_key]
##################

def usage():
	"""Prints out the valid parameters."""
	print """Valid commands are:
-n              switch now
-s arg          switch to screen arg now
-h              this help message"""


def swap_screen(current_root, other_root, dis, x_pos, y_pos):
	"""Warps the pointer and returns the old x,y position and the now current root."""
	old_x = current_root.query_pointer()._data["root_x"]
	old_y = current_root.query_pointer()._data["root_y"]
	other_root.warp_pointer(x_pos, y_pos)
	dis.sync()
	return old_x, old_y, other_root

def handle_event(event, current_root, other_root, dis, x_pos, y_pos):
	"""Checks to see if we care about the event and returns the results of swap_screen if we did."""
	#If it isn't a KeyPress event I don't care
	if event.type == X.KeyPress:
		keycode = event.detail
		if keycode == swap_key:
			return swap_screen(current_root, other_root, dis, x_pos, y_pos)
		if keycode == kill_key:
			sys.exit()


#This is set up for two screens, but could be expanded for more screens.
#Extra screen and root vars would need to be created and the next_root dict would have to be expanded
#(You could skip the screen var as it isn't directly used, but that would mean changing the default screen and next screen detection.)
def start(once=False, screen_num=False):
	"""Sets up the screen info and then begins to process

If once is true we call swap_screen with the correct variables and ignore the returned value since we then quit.
If once is false we enter an infinite loop and wait to handle events.  The loop will be broken by hitting the kill key (this is done in the handle_event method)."""
	x_pos, y_pos = 100,100
	next_screen = {0:1,1:0}
	#Getting the screen info
	dis = display.Display()
	screen_a = dis.screen()
	root_a = screen_a.root
	#screen_b is screen after screen_a or screen_num if given
	screen_b = dis.screen(next_screen[dis.get_default_screen()]) if not screen_num else dis.screen(int(screen_num))
	root_b = screen_b.root
	current_root = root_a
	root_a.change_attributes(event_mask = X.KeyPressMask)
	root_b.change_attributes(event_mask = X.KeyPressMask)
	#This is where we say what is the next root for a given root
	next_root = {root_a:root_b,root_b:root_a}
	if once:
		swap_screen(current_root, next_root[current_root], dis, x_pos, y_pos)
		sys.exit()
	#Hey X!  I wanna know when these keys are pressed!
	for keycode in keys:
		root_a.grab_key(keycode, X.ControlMask, 1, X.GrabModeAsync, X.GrabModeAsync)
		root_b.grab_key(keycode, X.ControlMask, 1, X.GrabModeAsync, X.GrabModeAsync)
	while 1:
		event = current_root.display.next_event()
		
		try:
			x_pos, y_pos, current_root = handle_event(event, current_root, next_root[current_root], dis, x_pos, y_pos)
		#We except a TypeError here because handle_event can return None
		#It was mostly doing this when it handled KeyRelease events.  I'm not sure why X was sending them to us, but it was.
		except TypeError as inst:
			pass

if __name__ == '__main__':
	try:
		optlist, args = getopt.getopt(sys.argv[1:], 'ns:h')
		#Using list comprehensions here so we can ensure that the commands have a priority without using lots of nested ifs.  This does make -s a bit trickier though.
		if '-h' in [opt for opt, value in optlist]:
			usage()
			sys.exit(1)
		elif '-s' in [opt for opt, value in optlist]:
			#We can't use the values of opt, value here because they will be set to the last of optlist.
			#So, here we find the value that matches the first -s in the optlist or False if we don't find it (which we should)
			value = next((val for opt, val in optlist if opt == '-s'), False)
			#Note that we also set once to true
			start(screen_num=value, once=True)
		elif '-n' in [opt for opt, value in optlist]:
			start(once=True)
		#We don't have any args, if you add some you'll want to change this.
		if args:
			print "Unknown argument " + args[0] + ".  There are no valid arguments!"
			usage()
			sys.exit(1)
		if not optlist:
			start()
	#Oh noes!  Invalid options!
	except getopt.GetoptError as inst:
		print "Oops!"
		print inst
		usage()


