# -*- coding: utf-8 -*-
# Copyright (C) 2011 Chris Dekter
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
__all__ = ["XRecordInterface", "AtSpiInterface"]
import os, threading, re, time, socket, select, logging, Queue, subprocess
try:
import pyatspi
HAS_ATSPI = True
except ImportError:
HAS_ATSPI = False
from Xlib import X, XK, display, error
try:
from Xlib.ext import record, xtest
HAS_RECORD = True
except ImportError:
HAS_RECORD = False
from Xlib.protocol import rq, event
import common
if common.USING_QT:
from PyQt4.QtGui import QClipboard, QApplication
else:
from gi.repository import Gtk, Gdk
logger = logging.getLogger("interface")
MASK_INDEXES = [
(X.ShiftMapIndex, X.ShiftMask),
(X.ControlMapIndex, X.ControlMask),
(X.LockMapIndex, X.LockMask),
(X.Mod1MapIndex, X.Mod1Mask),
(X.Mod2MapIndex, X.Mod2Mask),
(X.Mod3MapIndex, X.Mod3Mask),
(X.Mod4MapIndex, X.Mod4Mask),
(X.Mod5MapIndex, X.Mod5Mask),
]
CAPSLOCK_LEDMASK = 1<<0
NUMLOCK_LEDMASK = 1<<1
class XInterfaceBase(threading.Thread):
"""
Encapsulates the common functionality for the two X interface classes.
"""
def __init__(self, mediator, app):
threading.Thread.__init__(self)
self.setDaemon(True)
self.setName("XInterface-thread")
self.mediator = mediator
self.app = app
self.lastChars = [] # QT4 Workaround
self.__enableQT4Workaround = False # QT4 Workaround
self.shutdown = False
# Event loop
self.eventThread = threading.Thread(target=self.__eventLoop)
self.queue = Queue.Queue()
# Event listener
self.listenerThread = threading.Thread(target=self.__flushEvents)
if common.USING_QT:
self.clipBoard = QApplication.clipboard()
else:
self.clipBoard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.selection = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY)
self.__initMappings()
# Set initial lock state
ledMask = self.localDisplay.get_keyboard_control().led_mask
mediator.set_modifier_state(Key.CAPSLOCK, (ledMask & CAPSLOCK_LEDMASK) != 0)
mediator.set_modifier_state(Key.NUMLOCK, (ledMask & NUMLOCK_LEDMASK) != 0)
# Window name atoms
self.__NameAtom = self.localDisplay.intern_atom("_NET_WM_NAME", True)
self.__VisibleNameAtom = self.localDisplay.intern_atom("_NET_WM_VISIBLE_NAME", True)
if not common.USING_QT:
self.keyMap = Gdk.Keymap.get_default()
self.keyMap.connect("keys-changed", self.on_keys_changed)
self.__ignoreRemap = False
self.eventThread.start()
self.listenerThread.start()
def __eventLoop(self):
while True:
method, args = self.queue.get()
if method is None and args is None:
break
try:
method(*args)
except Exception, e:
logger.exception("Error in X event loop thread")
self.queue.task_done()
def __enqueue(self, method, *args):
self.queue.put_nowait((method, args))
def on_keys_changed(self, data=None):
if not self.__ignoreRemap:
logger.debug("Recorded keymap change event")
self.__ignoreRemap = True
time.sleep(0.2)
self.__enqueue(self.__ungrabAllHotkeys)
self.__enqueue(self.__delayedInitMappings)
else:
logger.debug("Ignored keymap change event")
def __delayedInitMappings(self):
self.__initMappings()
self.__ignoreRemap = False
def __initMappings(self):
self.localDisplay = display.Display()
self.rootWindow = self.localDisplay.screen().root
self.rootWindow.change_attributes(event_mask=X.SubstructureNotifyMask|X.StructureNotifyMask)
altList = self.localDisplay.keysym_to_keycodes(XK.XK_ISO_Level3_Shift)
self.__usableOffsets = (0, 1)
for code, offset in altList:
if code == 108 and offset == 0:
self.__usableOffsets += (4, 5)
logger.debug("Enabling sending using Alt-Grid")
break
# Build modifier mask mapping
self.modMasks = {}
mapping = self.localDisplay.get_modifier_mapping()
for keySym, ak in XK_TO_AK_MAP.iteritems():
if ak in MODIFIERS:
keyCodeList = self.localDisplay.keysym_to_keycodes(keySym)
found = False
for keyCode, lvl in keyCodeList:
for index, mask in MASK_INDEXES:
if keyCode in mapping[index]:
self.modMasks[ak] = mask
found = True
break
if found: break
logger.debug("Modifier masks: %r", self.modMasks)
self.__grabHotkeys()
self.localDisplay.flush()
# --- get list of keycodes that are unused in the current keyboard mapping
keyCode = 8
avail = []
for keyCodeMapping in self.localDisplay.get_keyboard_mapping(keyCode, 200):
codeAvail = True
for offset in keyCodeMapping:
if offset != 0:
codeAvail = False
break
if codeAvail:
avail.append(keyCode)
keyCode += 1
self.__availableKeycodes = avail
self.remappedChars = {}
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
self.keymap_test()
def keymap_test(self):
code = self.localDisplay.keycode_to_keysym(108, 0)
for attr in XK.__dict__.iteritems():
if attr[0].startswith("XK"):
if attr[1] == code:
logger.debug("Alt-Grid: %s, %s", attr[0], attr[1])
logger.debug(repr(self.localDisplay.keysym_to_keycodes(XK.XK_ISO_Level3_Shift)))
logger.debug("X Server Keymap")
for char in "\\|`1234567890-=~!@#$%^&*()qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>?":
keyCodeList = self.localDisplay.keysym_to_keycodes(ord(char))
if len(keyCodeList) > 0:
logger.debug("[%s] : %s", char, keyCodeList)
else:
logger.debug("No mapping for [%s]", char)
def __needsMutterWorkaround(self, item):
if Key.SUPER not in item.modifiers:
return False
try:
output = subprocess.check_output(["ps", "-eo", "command"])
lines = output.splitlines()
for line in lines:
if "gnome-shell" in line or "cinnamon" in line or "unity" in line:
return True
except:
pass # since this is just a nasty workaround, if anything goes wrong just disable it
return False
def __grabHotkeys(self):
"""
Run during startup to grab global and specific hotkeys in all open windows
"""
c = self.app.configManager
hotkeys = c.hotKeys + c.hotKeyFolders
# Grab global hotkeys in root window
for item in c.globalHotkeys:
if item.enabled:
self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, self.rootWindow)
if self.__needsMutterWorkaround(item):
self.__enqueue(self.__grabRecurse, item, self.rootWindow, False)
# Grab hotkeys without a filter in root window
for item in hotkeys:
if item.get_applicable_regex() is None:
self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, self.rootWindow)
if self.__needsMutterWorkaround(item):
self.__enqueue(self.__grabRecurse, item, self.rootWindow, False)
self.__enqueue(self.__recurseTree, self.rootWindow, hotkeys)
def __recurseTree(self, parent, hotkeys):
# Grab matching hotkeys in all open child windows
try:
children = parent.query_tree().children
except:
return # window has been destroyed
for window in children:
try:
title = self.get_window_title(window, False)
klass = self.get_window_class(window, False)
if title or klass:
for item in hotkeys:
if item.get_applicable_regex() is not None and item._should_trigger_window_title((title, klass)):
self.__grabHotkey(item.hotKey, item.modifiers, window)
self.__grabRecurse(item, window, False)
self.__enqueue(self.__recurseTree, window, hotkeys)
except:
logger.exception("grab on window failed")
def __ungrabAllHotkeys(self):
"""
Ungrab all hotkeys in preparation for keymap change
"""
c = self.app.configManager
hotkeys = c.hotKeys + c.hotKeyFolders
# Ungrab global hotkeys in root window, recursively
for item in c.globalHotkeys:
if item.enabled:
self.__ungrabHotkey(item.hotKey, item.modifiers, self.rootWindow)
if self.__needsMutterWorkaround(item):
self.__ungrabRecurse(item, self.rootWindow, False)
# Ungrab hotkeys without a filter in root window, recursively
for item in hotkeys:
if item.get_applicable_regex() is None:
self.__ungrabHotkey(item.hotKey, item.modifiers, self.rootWindow)
if self.__needsMutterWorkaround(item):
self.__ungrabRecurse(item, self.rootWindow, False)
self.__recurseTreeUngrab(self.rootWindow, hotkeys)
def __recurseTreeUngrab(self, parent, hotkeys):
# Ungrab matching hotkeys in all open child windows
try:
children = parent.query_tree().children
except:
return # window has been destroyed
for window in children:
try:
title = self.get_window_title(window, False)
klass = self.get_window_class(window, False)
if title or klass:
for item in hotkeys:
if item.get_applicable_regex() is not None and item._should_trigger_window_title((title, klass)):
self.__ungrabHotkey(item.hotKey, item.modifiers, window)
self.__ungrabRecurse(item, window, False)
self.__enqueue(self.__recurseTreeUngrab, window, hotkeys)
except:
logger.exception("ungrab on window failed")
def __grabHotkeysForWindow(self, window):
"""
Grab all hotkeys relevant to the window
Used when a new window is created
"""
c = self.app.configManager
hotkeys = c.hotKeys + c.hotKeyFolders
title = self.get_window_title(window)
klass = self.get_window_class(window)
for item in hotkeys:
if item.get_applicable_regex() is not None and item._should_trigger_window_title((title, klass)):
self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, window)
elif self.__needsMutterWorkaround(item):
self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, window)
def __grabHotkey(self, key, modifiers, window):
"""
Grab a specific hotkey in the given window
"""
logger.debug("Grabbing hotkey: %r %r", modifiers, key)
try:
keycode = self.__lookupKeyCode(key)
mask = 0
for mod in modifiers:
mask |= self.modMasks[mod]
window.grab_key(keycode, mask, True, X.GrabModeAsync, X.GrabModeAsync)
if Key.NUMLOCK in self.modMasks:
window.grab_key(keycode, mask|self.modMasks[Key.NUMLOCK], True, X.GrabModeAsync, X.GrabModeAsync)
if Key.CAPSLOCK in self.modMasks:
window.grab_key(keycode, mask|self.modMasks[Key.CAPSLOCK], True, X.GrabModeAsync, X.GrabModeAsync)
if Key.CAPSLOCK in self.modMasks and Key.NUMLOCK in self.modMasks:
window.grab_key(keycode, mask|self.modMasks[Key.CAPSLOCK]|self.modMasks[Key.NUMLOCK], True, X.GrabModeAsync, X.GrabModeAsync)
except Exception, e:
logger.warn("Failed to grab hotkey %r %r: %s", modifiers, key, str(e))
def grab_hotkey(self, item):
"""
Grab a hotkey.
If the hotkey has no filter regex, it is global and is grabbed recursively from the root window
If it has a filter regex, iterate over all children of the root and grab from matching windows
"""
if item.get_applicable_regex() is None:
self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, self.rootWindow)
if self.__needsMutterWorkaround(item):
self.__enqueue(self.__grabRecurse, item, self.rootWindow, False)
else:
self.__enqueue(self.__grabRecurse, item, self.rootWindow)
def __grabRecurse(self, item, parent, checkWinInfo=True):
try:
children = parent.query_tree().children
except:
return # window has been destroyed
for window in children:
shouldTrigger = False
if checkWinInfo:
title = self.get_window_title(window, False)
klass = self.get_window_class(window, False)
shouldTrigger = item._should_trigger_window_title((title, klass))
if shouldTrigger or not checkWinInfo:
self.__grabHotkey(item.hotKey, item.modifiers, window)
self.__grabRecurse(item, window, False)
else:
self.__grabRecurse(item, window)
def ungrab_hotkey(self, item):
"""
Ungrab a hotkey.
If the hotkey has no filter regex, it is global and is grabbed recursively from the root window
If it has a filter regex, iterate over all children of the root and ungrab from matching windows
"""
import copy
newItem = copy.copy(item)
if item.get_applicable_regex() is None:
self.__enqueue(self.__ungrabHotkey, newItem.hotKey, newItem.modifiers, self.rootWindow)
if self.__needsMutterWorkaround(item):
self.__enqueue(self.__ungrabRecurse, newItem, self.rootWindow, False)
else:
self.__enqueue(self.__ungrabRecurse, newItem, self.rootWindow)
def __ungrabRecurse(self, item, parent, checkWinInfo=True):
try:
children = parent.query_tree().children
except:
return # window has been destroyed
for window in children:
shouldTrigger = False
if checkWinInfo:
title = self.get_window_title(window, False)
klass = self.get_window_class(window, False)
shouldTrigger = item._should_trigger_window_title((title, klass))
if shouldTrigger or not checkWinInfo:
self.__ungrabHotkey(item.hotKey, item.modifiers, window)
self.__ungrabRecurse(item, window, False)
else:
self.__ungrabRecurse(item, window)
def __ungrabHotkey(self, key, modifiers, window):
"""
Ungrab a specific hotkey in the given window
"""
logger.debug("Ungrabbing hotkey: %r %r", modifiers, key)
try:
keycode = self.__lookupKeyCode(key)
mask = 0
for mod in modifiers:
mask |= self.modMasks[mod]
window.ungrab_key(keycode, mask)
if Key.NUMLOCK in self.modMasks:
window.ungrab_key(keycode, mask|self.modMasks[Key.NUMLOCK])
if Key.CAPSLOCK in self.modMasks:
window.ungrab_key(keycode, mask|self.modMasks[Key.CAPSLOCK])
if Key.CAPSLOCK in self.modMasks and Key.NUMLOCK in self.modMasks:
window.ungrab_key(keycode, mask|self.modMasks[Key.CAPSLOCK]|self.modMasks[Key.NUMLOCK])
except Exception, e:
logger.warn("Failed to ungrab hotkey %r %r: %s", modifiers, key, str(e))
def lookup_string(self, keyCode, shifted, numlock, altGrid):
if keyCode == 0:
return ""
keySym = self.localDisplay.keycode_to_keysym(keyCode, 0)
if keySym in XK_TO_AK_NUMLOCKED and numlock and not (numlock and shifted):
return XK_TO_AK_NUMLOCKED[keySym]
elif keySym in XK_TO_AK_MAP:
return XK_TO_AK_MAP[keySym]
else:
try:
index = 0
if shifted: index += 1
if altGrid: index += 4
return unichr(self.localDisplay.keycode_to_keysym(keyCode, index))
except ValueError:
return "" % keyCode
def send_string_clipboard(self, string, pasteCommand):
self.__enqueue(self.__sendStringClipboard, string, pasteCommand)
def __sendStringClipboard(self, string, pasteCommand):
logger.debug("Sending string: %r", string)
if pasteCommand is None:
if common.USING_QT:
self.sem = threading.Semaphore(0)
self.app.exec_in_main(self.__fillSelection, string)
self.sem.acquire()
else:
self.__fillSelection(string)
focus = self.localDisplay.get_input_focus().focus
xtest.fake_input(focus, X.ButtonPress, X.Button2)
xtest.fake_input(focus, X.ButtonRelease, X.Button2)
else:
if common.USING_QT:
self.sem = threading.Semaphore(0)
self.app.exec_in_main(self.__fillClipboard, string)
self.sem.acquire()
else:
self.__fillClipboard(string)
self.mediator.send_string(pasteCommand)
if common.USING_QT:
self.app.exec_in_main(self.__restoreClipboard)
logger.debug("Send via clipboard done")
def __restoreClipboard(self):
if self.__savedClipboard != "":
if common.USING_QT:
self.clipBoard.setText(self.__savedClipboard, QClipboard.Clipboard)
else:
Gdk.threads_enter()
self.clipBoard.set_text(self.__savedClipboard)
Gdk.threads_leave()
def __fillSelection(self, string):
if common.USING_QT:
self.clipBoard.setText(string, QClipboard.Selection)
self.sem.release()
else:
Gdk.threads_enter()
self.selection.set_text(string.encode("utf-8"))
Gdk.threads_leave()
def __fillClipboard(self, string):
if common.USING_QT:
self.__savedClipboard = self.clipBoard.text()
self.clipBoard.setText(string, QClipboard.Clipboard)
self.sem.release()
else:
Gdk.threads_enter()
text = self.clipBoard.wait_for_text()
self.__savedClipboard = ''
if text is not None: self.__savedClipboard = text
self.clipBoard.set_text(string.encode("utf-8"))
Gdk.threads_leave()
def begin_send(self):
self.__enqueue(self.__grab_keyboard)
def finish_send(self):
self.__enqueue(self.__ungrabKeyboard)
def grab_keyboard(self):
self.__enqueue(self.__grab_keyboard)
def __grab_keyboard(self):
focus = self.localDisplay.get_input_focus().focus
focus.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime)
self.localDisplay.flush()
def ungrab_keyboard(self):
self.__enqueue(self.__ungrabKeyboard)
def __ungrabKeyboard(self):
self.localDisplay.ungrab_keyboard(X.CurrentTime)
self.localDisplay.flush()
def __findUsableKeycode(self, codeList):
for code, offset in codeList:
if offset in self.__usableOffsets:
return code, offset
return None, None
def send_string(self, string):
self.__enqueue(self.__sendString, string)
def __sendString(self, string):
"""
Send a string of printable characters.
"""
logger.debug("Sending string: %r", string)
# Determine if workaround is needed
if not ConfigManager.SETTINGS[ENABLE_QT4_WORKAROUND]:
self.__checkWorkaroundNeeded()
# First find out if any chars need remapping
remapNeeded = False
for char in string:
keyCodeList = self.localDisplay.keysym_to_keycodes(ord(char))
usableCode, offset = self.__findUsableKeycode(keyCodeList)
if usableCode is None and char not in self.remappedChars:
remapNeeded = True
break
# Now we know chars need remapping, do it
if remapNeeded:
self.__ignoreRemap = True
self.remappedChars = {}
remapChars = []
for char in string:
keyCodeList = self.localDisplay.keysym_to_keycodes(ord(char))
usableCode, offset = self.__findUsableKeycode(keyCodeList)
if usableCode is None:
remapChars.append(char)
logger.debug("Characters requiring remapping: %r", remapChars)
availCodes = self.__availableKeycodes
logger.debug("Remapping with keycodes in the range: %r", availCodes)
mapping = self.localDisplay.get_keyboard_mapping(8, 200)
firstCode = 8
for i in xrange(len(availCodes) - 1):
code = availCodes[i]
sym1 = 0
sym2 = 0
if len(remapChars) > 0:
char = remapChars.pop(0)
self.remappedChars[char] = (code, 0)
sym1 = ord(char)
if len(remapChars) > 0:
char = remapChars.pop(0)
self.remappedChars[char] = (code, 1)
sym2 = ord(char)
if sym1 != 0:
mapping[code - firstCode][0] = sym1
mapping[code - firstCode][1] = sym2
mapping = [tuple(l) for l in mapping]
self.localDisplay.change_keyboard_mapping(firstCode, mapping)
self.localDisplay.flush()
focus = self.localDisplay.get_input_focus().focus
for char in string:
try:
keyCodeList = self.localDisplay.keysym_to_keycodes(ord(char))
keyCode, offset = self.__findUsableKeycode(keyCodeList)
if keyCode is not None:
if offset == 0:
self.__sendKeyCode(keyCode, theWindow=focus)
if offset == 1:
self.__pressKey(Key.SHIFT)
self.__sendKeyCode(keyCode, self.modMasks[Key.SHIFT], focus)
self.__releaseKey(Key.SHIFT)
if offset == 4:
self.__pressKey(Key.ALT_GR)
self.__sendKeyCode(keyCode, self.modMasks[Key.ALT_GR], focus)
self.__releaseKey(Key.ALT_GR)
if offset == 5:
self.__pressKey(Key.ALT_GR)
self.__pressKey(Key.SHIFT)
self.__sendKeyCode(keyCode, self.modMasks[Key.ALT_GR]|self.modMasks[Key.SHIFT], focus)
self.__releaseKey(Key.SHIFT)
self.__releaseKey(Key.ALT_GR)
elif char in self.remappedChars:
keyCode, offset = self.remappedChars[char]
if offset == 0:
self.__sendKeyCode(keyCode, theWindow=focus)
if offset == 1:
self.__pressKey(Key.SHIFT)
self.__sendKeyCode(keyCode, self.modMasks[Key.SHIFT], focus)
self.__releaseKey(Key.SHIFT)
else:
logger.warn("Unable to send character %r", char)
except Exception, e:
logger.exception("Error sending char %r: %s", char, str(e))
self.__ignoreRemap = False
def send_key(self, keyName):
"""
Send a specific non-printing key, eg Up, Left, etc
"""
self.__enqueue(self.__sendKey, keyName)
def __sendKey(self, keyName):
logger.debug("Send special key: [%r]", keyName)
self.__sendKeyCode(self.__lookupKeyCode(keyName))
def fake_keypress(self, keyName):
self.__enqueue(self.__fakeKeypress, keyName)
def __fakeKeypress(self, keyName):
keyCode = self.__lookupKeyCode(keyName)
xtest.fake_input(self.rootWindow, X.KeyPress, keyCode)
xtest.fake_input(self.rootWindow, X.KeyRelease, keyCode)
def fake_keydown(self, keyName):
self.__enqueue(self.__fakeKeydown, keyName)
def __fakeKeydown(self, keyName):
keyCode = self.__lookupKeyCode(keyName)
xtest.fake_input(self.rootWindow, X.KeyPress, keyCode)
def fake_keyup(self, keyName):
self.__enqueue(self.__fakeKeyup, keyName)
def __fakeKeyup(self, keyName):
keyCode = self.__lookupKeyCode(keyName)
xtest.fake_input(self.rootWindow, X.KeyRelease, keyCode)
def send_modified_key(self, keyName, modifiers):
"""
Send a modified key (e.g. when emulating a hotkey)
"""
self.__enqueue(self.__sendModifiedKey, keyName, modifiers)
def __sendModifiedKey(self, keyName, modifiers):
logger.debug("Send modified key: modifiers: %s key: %s", modifiers, keyName)
try:
mask = 0
for mod in modifiers:
mask |= self.modMasks[mod]
keyCode = self.__lookupKeyCode(keyName)
for mod in modifiers: self.__pressKey(mod)
self.__sendKeyCode(keyCode, mask)
for mod in modifiers: self.__releaseKey(mod)
except Exception, e:
logger.warn("Error sending modified key %r %r: %s", modifiers, keyName, str(e))
def send_mouse_click(self, xCoord, yCoord, button, relative):
self.__enqueue(self.__sendMouseClick, xCoord, yCoord, button, relative)
def __sendMouseClick(self, xCoord, yCoord, button, relative):
# Get current pointer position so we can return it there
pos = self.rootWindow.query_pointer()
if relative:
focus = self.localDisplay.get_input_focus().focus
focus.warp_pointer(xCoord, yCoord)
xtest.fake_input(focus, X.ButtonPress, button, x=xCoord, y=yCoord)
xtest.fake_input(focus, X.ButtonRelease, button, x=xCoord, y=yCoord)
else:
self.rootWindow.warp_pointer(xCoord, yCoord)
xtest.fake_input(self.rootWindow, X.ButtonPress, button, x=xCoord, y=yCoord)
xtest.fake_input(self.rootWindow, X.ButtonRelease, button, x=xCoord, y=yCoord)
self.rootWindow.warp_pointer(pos.root_x, pos.root_y)
self.__flush()
def send_mouse_click_relative(self, xoff, yoff, button):
self.__enqueue(self.__sendMouseClickRelative, xoff, yoff, button)
def __sendMouseClickRelative(self, xoff, yoff, button):
# Get current pointer position
pos = self.rootWindow.query_pointer()
xCoord = pos.root_x + xoff
yCoord = pos.root_y + yoff
self.rootWindow.warp_pointer(xCoord, yCoord)
xtest.fake_input(self.rootWindow, X.ButtonPress, button, x=xCoord, y=yCoord)
xtest.fake_input(self.rootWindow, X.ButtonRelease, button, x=xCoord, y=yCoord)
self.rootWindow.warp_pointer(pos.root_x, pos.root_y)
self.__flush()
def flush(self):
self.__enqueue(self.__flush)
def __flush(self):
self.localDisplay.flush()
self.lastChars = []
def press_key(self, keyName):
self.__enqueue(self.__pressKey, keyName)
def __pressKey(self, keyName):
self.__sendKeyPressEvent(self.__lookupKeyCode(keyName), 0)
def release_key(self, keyName):
self.__enqueue(self.__releaseKey, keyName)
def __releaseKey(self, keyName):
self.__sendKeyReleaseEvent(self.__lookupKeyCode(keyName), 0)
def __flushEvents(self):
while True:
try:
readable, w, e = select.select([self.localDisplay], [], [], 1)
time.sleep(1)
if self.localDisplay in readable:
createdWindows = []
destroyedWindows = []
for x in xrange(self.localDisplay.pending_events()):
event = self.localDisplay.next_event()
if event.type == X.CreateNotify:
createdWindows.append(event.window)
if event.type == X.DestroyNotify:
destroyedWindows.append(event.window)
for window in createdWindows:
if window not in destroyedWindows:
self.__enqueue(self.__grabHotkeysForWindow, window)
if self.shutdown:
break
except:
pass
def handle_keypress(self, keyCode):
self.__enqueue(self.__handleKeyPress, keyCode)
def __handleKeyPress(self, keyCode):
focus = self.localDisplay.get_input_focus().focus
modifier = self.__decodeModifier(keyCode)
if modifier is not None:
self.mediator.handle_modifier_down(modifier)
else:
self.mediator.handle_keypress(keyCode, self.get_window_title(focus), self.get_window_class(focus))
def handle_keyrelease(self, keyCode):
self.__enqueue(self.__handleKeyrelease, keyCode)
def __handleKeyrelease(self, keyCode):
modifier = self.__decodeModifier(keyCode)
if modifier is not None:
self.mediator.handle_modifier_up(modifier)
def handle_mouseclick(self, button, x, y):
self.__enqueue(self.__handleMouseclick, button, x, y)
def __handleMouseclick(self, button, x, y):
title = self.get_window_title()
klass = self.get_window_class()
info = (title, klass)
if x is None and y is None:
ret = self.localDisplay.get_input_focus().focus.query_pointer()
self.mediator.handle_mouse_click(ret.root_x, ret.root_y, ret.win_x, ret.win_y, button, info)
else:
focus = self.localDisplay.get_input_focus().focus
try:
rel = focus.translate_coords(self.rootWindow, x, y)
self.mediator.handle_mouse_click(x, y, rel.x, rel.y, button, info)
except:
self.mediator.handle_mouse_click(x, y, 0, 0, button, info)
def __decodeModifier(self, keyCode):
"""
Checks if the given keyCode is a modifier key. If it is, returns the modifier name
constant as defined in the iomediator module. If not, returns C{None}
"""
keyName = self.lookup_string(keyCode, False, False, False)
if keyName in MODIFIERS:
return keyName
return None
def __sendKeyCode(self, keyCode, modifiers=0, theWindow=None):
if ConfigManager.SETTINGS[ENABLE_QT4_WORKAROUND] or self.__enableQT4Workaround:
self.__doQT4Workaround(keyCode)
self.__sendKeyPressEvent(keyCode, modifiers, theWindow)
self.__sendKeyReleaseEvent(keyCode, modifiers, theWindow)
def __checkWorkaroundNeeded(self):
focus = self.localDisplay.get_input_focus().focus
windowName = self.get_window_title(focus)
windowClass = self.get_window_class(focus)
w = self.app.configManager.workAroundApps
if w.match(windowName) or w.match(windowClass):
self.__enableQT4Workaround = True
else:
self.__enableQT4Workaround = False
def __doQT4Workaround(self, keyCode):
if len(self.lastChars) > 0:
if keyCode in self.lastChars:
self.localDisplay.flush()
time.sleep(0.0125)
self.lastChars.append(keyCode)
if len(self.lastChars) > 10:
self.lastChars.pop(0)
def __sendKeyPressEvent(self, keyCode, modifiers, theWindow=None):
if theWindow is None:
focus = self.localDisplay.get_input_focus().focus
else:
focus = theWindow
keyEvent = event.KeyPress(
detail=keyCode,
time=X.CurrentTime,
root=self.rootWindow,
window=focus,
child=X.NONE,
root_x=1,
root_y=1,
event_x=1,
event_y=1,
state=modifiers,
same_screen=1
)
focus.send_event(keyEvent)
def __sendKeyReleaseEvent(self, keyCode, modifiers, theWindow=None):
if theWindow is None:
focus = self.localDisplay.get_input_focus().focus
else:
focus = theWindow
keyEvent = event.KeyRelease(
detail=keyCode,
time=X.CurrentTime,
root=self.rootWindow,
window=focus,
child=X.NONE,
root_x=1,
root_y=1,
event_x=1,
event_y=1,
state=modifiers,
same_screen=1
)
focus.send_event(keyEvent)
def __lookupKeyCode(self, char):
if char in AK_TO_XK_MAP:
return self.localDisplay.keysym_to_keycode(AK_TO_XK_MAP[char])
elif char.startswith("