#!/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

from winswitch.util.simple_logger import Logger, msig
from winswitch.consts import MODIFIER_KEY_SHIFT, MODIFIER_KEY_CONTROL, MODIFIER_KEY_ALT

from Xlib import X, display, error
from Xlib.protocol.event import ClientMessage

def get_display_size(disp):
	d = display.Display(disp)
	try:
		w = 0
		h = 0
		i = 0
		while i<d.screen_count():
			screen = d.screen(i)
			w += screen.width_in_pixels
			h += screen.height_in_pixels
			i += 1
	finally:
		d.close()
	return	(w, h)

def get_root_window_string_property(disp, prop):
	d = display.Display(disp)
	try:
		prop_atom = d.intern_atom(prop)
		str_prop = d.intern_atom("STRING")
		assert prop_atom is not None and prop_atom is not X.NONE
		i = 0
		value = None
		while i<d.screen_count():
			screen = d.screen(i)
			root_win = screen.root
			prop = root_win.get_property(prop_atom, str_prop, 0, 8, delete = 0)
			if prop:
				value = prop.value
				break
			i += 1
	finally:
		d.close()
	return	value


class xlib_wm:
	def __init__(self):
		Logger(self, log_colour=Logger.HIGHLIGHTED_CRIMSON)
		self.net_wm_pid=0
		self.display=display.Display()
		self.net_wm_pid = self.display.intern_atom("_NET_WM_PID")
		self.wm_delete_window = self.display.intern_atom("WM_DELETE_WINDOW")
		self.net_wm_minimize = self.display.intern_atom("_NET_WM_ACTION_MINIMIZE")
		self.wm_protocols = self.display.intern_atom("WM_PROTOCOLS")
		self.screensaver_status = self.display.intern_atom("_SCREENSAVER_STATUS")
		self.is_screensaver_running = None
		self.slog("display=%s" % self.display.get_display_name())
		self.sdebug("display=%s" % self.display)
		self.sdebug("display extensions=%s" % self.display.list_extensions())
		self.sdebug("_NET_WM_PID=%d, WM_DELETE_WINDOW=%d, WM_PROTOCOLS=%d, _NET_WM_ACTION_MINIMIZE=%d, _SCREENSAVER_STATUS=%d" % (self.net_wm_pid, self.wm_delete_window, self.wm_protocols, self.net_wm_minimize, self.screensaver_status))


	def for_matching_pids(self, pids, callback):
		sig = msig(pids, callback)
		found_pids = []
		missing_pids = sorted(pids)

		screen_index = 0
		while screen_index<self.display.screen_count():
			screen = self.display.screen(screen_index)
			root = screen.root
			for window in root.query_tree().children:
				try:
					wm_class = window.get_wm_class()
					wm_name = window.get_wm_name()
					if not wm_class and not wm_name and not window.get_wm_state():
						continue

					if wm_name and wm_name=="xpra":
						continue
					if wm_class and "xpra" in wm_class:
						continue
					pid = -1
					pid_reply = window.get_property(self.net_wm_pid, X.AnyPropertyType, 0, 10)
					if pid_reply:
						pid = int(pid_reply.value.tolist()[0])

						if pid>0 and pid in pids:
							callback(screen_index, window, pid)
							if pid not in found_pids:
								found_pids.append(pid)
							if pid in missing_pids:
								missing_pids.remove(pid)
				except Exception, e:
					if e.__class__ == error.BadWindow:
						self.debug(sig+" exception: " + str(e))
					else:
						self.error(sig, e)
			screen_index = screen_index + 1
		if len(missing_pids)>0:
			self.debug(sig+" did not find the pids: %s" % str(missing_pids))

	def window_info(self, window):
		return "class=%s, wm_name=%s, get_wm_state=%s, properties=%s, attributes=%s, wm_icon_size=%s, wm_client_machine=%s" % (window.get_wm_class(), window.get_wm_name(), window.get_wm_state(), window.list_properties(), window.get_attributes(), window.get_wm_icon_size(), window.get_wm_client_machine())

	def handle_x_event(self, *args):
		self.log("(%s)" % str(args))

	def close_windows(self, pids):
		self.error("(%s)" % pids)
		self.for_matching_pids(pids, self.close_callback)

	def close_callback(self, screen_index, window, pid):
		self.error("(%s,%s,%s) window_info=%s" % (screen_index, window, pid, self.window_info(window)))
		self.window_info(window)
		self.send_atom(window, self.wm_delete_window)

	def send_atom(self, window, atom):
		self.log("(%s,%s)" % (window, atom))
		time = X.CurrentTime
		args = [0] * 3
		data = (32, ([atom, time] + args))
		ev = ClientMessage(window = window, client_type = self.wm_protocols, data = data)
		window.send_event(event=ev, event_mask=0, propagate=0, onerror=self.xlib_error)
		self.display.sync()

	def minimize_windows(self, pids):
		self.for_matching_pids(pids, self.minimize_callback)

	def minimize_callback(self, screen_index, window, pid):
		sig = "(%s,%s,%s) window_info=%s" % (screen_index, window, pid, self.window_info(window))
		self.error(sig)
		self.window_info(window)
		self.send_atom(window, self.net_wm_minimize)

	def xlib_error(self, *err):
		self.error("(%s)" % str(err))

	def is_screensaver_running_xlib(self):
		"""
		Not used, does not work and I dont know why!
		"""
		import inspect
		for extension in ["MIT-SCREEN-SAVER", "Xss"]:
			ext = self.display.query_extension (extension)
			self.sdebug("%s=%s" % (extension, ext))
			if ext:
				self.sdebug("inspect(%s)=%s" % (extension, str(inspect.getmembers(ext))))
		self.sdebug("display=%s" % str(inspect.getmembers(self.display)))
		if not ext:
			return
		ss = self.display.get_screen_saver()
		self.sdebug("screensaver=%s" % ss)
		if ss:
			self.sdebug("inspect(%s)=%s" % (ss, str(inspect.getmembers(ss))))
		i = 0
		while i<self.display.screen_count():
			screen = self.display.screen(i)
			self.sdebug("screen[%d]=%s" % (i,screen))
			self.sdebug("inspect(%s)=%s" % (screen, str(inspect.getmembers(screen))))
			root = screen.root
			self.sdebug("root(%s)=%s" % (screen, root))
			self.sdebug("inspect(%s)=%s" % (root, str(inspect.getmembers(root))))
			continue

			prop = root.property_get(self.screensaver_status)
			self.sdebug("screen[%d]: _SCREENSAVER_STATUS by atom=%s" % (i,prop))
			if prop:
				return	True
			prop = root.property_get("_SCREENSAVER_STATUS")
			self.sdebug("screen[%d]: _SCREENSAVER_STATUS by name=%s" % (i,prop))
			if prop:
				return	True
		return	False

	def get_modifiers_set(self):
		keys = self.display.query_keymap()
		mapping = self.display.get_modifier_mapping()

		modifiers = []
		i = 0
		for mod in [MODIFIER_KEY_SHIFT, "Lock", MODIFIER_KEY_CONTROL, MODIFIER_KEY_ALT, "Mod2", "Mod3", "Mod4", "Mod5"]:
			m = mapping[i]
			for keycode in m:
				if keycode==0:
					break
				key_entry = keys[keycode>>3]
				enabled = key_entry >> (keycode & 0x07)
				#print "mod=%s, entry=%s, value=%s, enabled=%s" % (mod, entry, key_entry, enabled)
				if enabled:
					modifiers.append(mod)
			i += 1
		return	modifiers
