#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

import thread
import os, sys

from winswitch.util.commands_util import XMODMAP_COMMAND
from winswitch.util.file_io import get_session_xmodmap_filename, get_xmodmap_filename, is_valid_file
from winswitch.util.common import visible_command, save_binary_file, load_binary_file
from winswitch.util.simple_logger import Logger

logger=Logger("xmodmap_util", log_colour=Logger.CYAN)

DISABLE_XMODMAP = "--disable-xmodmap" in sys.argv

def cook_xmodmap_modifiers(mod_input):
	""" ensures that the data can be fed back to xmodmap """
	if not mod_input:
		return	"";
	newdata = []
	for line in mod_input.splitlines():
		logger.sdebug("line=%s" % line)
		#skip header lines:
		if not line or line.find("xmodmap:")>=0:
			continue
		#"mod4        Super_L (0x73),  Super_R (0x74),  Super_L (0x7f),  Hyper_L (0x80)"
		#['mod4', 'Super_L', '(0x73),', 'Super_R', '(0x74),', 'Super_L', '(0x7f),', 'Hyper_L', '(0x80)']
		parts = line.split()
		modifier = parts[0]		#ie: mod4
		newdata.insert(0, "clear %s" % modifier)
		keys = []
		for k in parts[1:]:
			if k.startswith("("):
				continue
			keys.append(k)
		if len(keys)>0:
			newdata.append("add %s = %s" % (modifier, " ".join(set(keys))))
	return "\n".join(newdata)

def get_xmodmap(display=None, env=None):
	"""
		Captures the xmodmap currently set on the display
	"""
	if DISABLE_XMODMAP:
		return	None
	if env is None:
		env = {}
		#only pass what we really need:
		for x in ["LOGNAME", "USER", "PATH", "XAUTHORITY", "HOME", "USERNAME",
				"LANG", "DISPLAY", "HOSTNAME", "PWD"]:
			if x in os.environ:
				env[x] = os.environ.get(x)
		if display:
			env["DISPLAY"] = display
	return exec_xmodmap(env, "-pke"), cook_xmodmap_modifiers(exec_xmodmap(env, "-pm"))

def exec_xmodmap(env, param):
	cmd = [XMODMAP_COMMAND, param]
	try:
		from winswitch.util.process_util import get_output
		code, out, err = get_output(cmd, env=env)
	except Exception, e:
		logger.serr("failed to run %s: %s" % (cmd, e), env, param)
		return	None
	logger.sdebug("exec(%s)" % str(cmd), env, param)
	if code!=0:
		logger.serror("process terminated with error code %s, stdout=%s, stderr=%s" % (code, visible_command(out), visible_command(err)), env, param)
		return	None
	logger.slog("exec(%s)=(%s,%s)" % (cmd, visible_command(out), visible_command(err)), env, param)
	return	out

def set_xmodmap_from_file(session, xmodmap_filename):
	if DISABLE_XMODMAP:
		return	False
	if not is_valid_file(xmodmap_filename):
		logger.serror("invalid xmodmap file", session, xmodmap_filename)
		return	False
	env = session.env.copy()
	env["DISPLAY"] = session.display
	logger.slog(None, session, xmodmap_filename)
	return exec_xmodmap(env, xmodmap_filename)

def set_xmodmap(session, user, use_default=False):
	""" sets the user's xmodmap
		Note: this is done in a thread as this can take some time
	"""
	thread.start_new_thread(threaded_set_xmodmap, (session, user, use_default))

def threaded_set_xmodmap(session, user, use_default=False):
	"""
	Runs xmodmap with the keyboard information supplied by the user.
	Or with the default. Takes care of saving the default if not saved yet.
	"""
	logger.slog(None, session, user, use_default)
	server_xmodmap_data = None

	for is_modifiers, attr_name in [
					(False, "default_xmodmap_keys"),
					(True, "default_xmodmap_modifiers")]:
		filename = get_session_xmodmap_filename(session.display, is_modifiers=is_modifiers)
		user_filename = get_xmodmap_filename(user.uuid, is_modifiers=is_modifiers)

		is_using_default = False
		""" ensure we always have a default value recorded in the server_session object: """
		default_value = getattr(session, attr_name)
		if not default_value:
			""" Load or capture default xmodmap """
			if is_valid_file(filename):
				""" reload from disk """
				default_value = load_binary_file(filename)
				setattr(session, attr_name, default_value)
				is_using_default = True
			if not default_value:
				""" get from X11 server """
				if not server_xmodmap_data:
					logger.sdebug("default xmodmap data not found, querying it", session, user, use_default)
					server_xmodmap_data = get_xmodmap(session.display, env=session.env)
				if server_xmodmap_data:
					""" xmodmap_data is a pair: extract the correct item """
					if is_modifiers:
						default_value = server_xmodmap_data[1]
					else:
						default_value = server_xmodmap_data[0]
				if default_value:
					logger.sdebug("saving new defaults to %s" % filename, session, user, use_default)
					save_binary_file(filename, default_value)
					is_using_default = True
			elif not is_valid_file(filename):
				""" xmodmap data reloaded from session file but missing from disk?? """
				save_binary_file(filename, default_value)

		""" now apply the user's custom xmodmap """
		if not use_default and is_valid_file(user_filename):
			logger.sdebug("setting user provided map", session, user)
			set_xmodmap_from_file(session, user_filename)
		else:
			if is_using_default:
				logger.sdebug("already using default map", session, user)
			else:
				logger.sdebug("setting default map", session, user)
				set_xmodmap_from_file(session, filename)

def main():
	from winswitch.util.common import csv_list
	logger.slog(None)
	logger.slog("get_xmodmap=%s" % csv_list(get_xmodmap()))

if __name__ == "__main__":
	main()
