Source code for autokey.scripting.highlevel

"""
Highlevel scripting API, requires xautomation to be installed
"""

import time
import os
import subprocess
import tempfile
import imghdr
import struct


[docs] class PatternNotFound(Exception): """Exception raised by functions""" pass
# numeric representation of the mouse buttons. For use in visgrep. LEFT = 1 """Left mouse button""" MIDDLE = 2 """Middle mouse button""" RIGHT = 3 """Right mouse button"""
[docs] def visgrep(scr: str, pat: str, tolerance: int = 0) -> int: """ Usage: C{visgrep(scr: str, pat: str, tolerance: int = 0) -> int} Visual grep of scr for pattern pat. Requires xautomation (http://hoopajoo.net/projects/xautomation.html). Usage: C{visgrep("screen.png", "pat.png")} @param scr: path of PNG image to be grepped. @param pat: path of pattern image (PNG) to look for in scr. @param tolerance: An integer ≥ 0 to specify the level of tolerance for 'fuzzy' matches. @raise ValueError: Raised if tolerance is negative or not convertable to int @raise PatternNotFound: Raised if C{pat} not found. @raise FileNotFoundError: Raised if either file is not found @returns: Coordinates of the topleft point of the match, if any. Raises L{PatternNotFound} exception otherwise. """ tol = int(tolerance) if tol < 0: raise ValueError("tolerance must be ≥ 0.") with open(scr), open(pat): pass with tempfile.NamedTemporaryFile() as f: subprocess.call(['png2pat', pat], stdout=f) # don't use check_call, some versions (1.05) have a missing return statement in png2pat.c so the exit status ≠ 0 f.flush() os.fsync(f.fileno()) vg = subprocess.Popen(['visgrep', '-t' + str(tol), scr, f.name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = vg.communicate() coord_str = out[0].decode().split(' ')[0].split(',') try: coord = [int(coord_str[0]), int(coord_str[1])] except (ValueError, IndexError) as e: raise PatternNotFound(str([x.decode() for x in out]) + '\n\t' + repr(e)) return coord
[docs] def get_png_dim(filepath: str) -> int: """ Usage: C{get_png_dim(filepath:str) -> (int)} Finds the dimension of a PNG. @param filepath: file path of the PNG. @returns: (width, height). @raise Exception: Raised if the file is not a png """ if not imghdr.what(filepath) == 'png': raise Exception("not PNG") head = open(filepath, 'rb').read(24) return struct.unpack('!II', head[16:24])
[docs] def mouse_move(x: int, y: int, display: str=''): """ Moves the mouse using xte C{mousemove} from xautomation @param x: x location to move the mouse to @param y: y location to move the mouse to @param display: X display to pass to C{xte} """ subprocess.call(['xte', '-x', display, "mousemove {} {}".format(int(x), int(y))])
[docs] def mouse_rmove(x: int, y: int, display: str=''): """ Moves the mouse using xte C{mousermove} command from xautomation @param x: x location to move the mouse to @param y: y location to move the mouse to @param display: X display to pass to C{xte} """ subprocess.call(['xte', '-x', display, "mousermove {} {}".format(int(x), int(y))])
[docs] def mouse_click(button: int, display: str=''): """ Clicks the mouse in the current location using xte C{mouseclick} from xautomation @param button: Which button signal to send from the mouse @param display: X display to pass to C{xte} """ subprocess.call(['xte', '-x', display, "mouseclick {}".format(int(button))])
[docs] def mouse_pos(): """ Returns the current location of the mouse. @returns: Returns the mouse location in a C{list} """ tmp = subprocess.check_output("xmousepos").decode().split() return list(map(int, tmp))[:2]
[docs] def click_on_pat(pat: str, mousebutton: int=1, offset: (float, float)=None, tolerance: int=0, restore_pos: bool=False) -> None: """ Requires C{imagemagick}, C{xautomation}, C{xwd}. Click on a pattern at a specified offset (x,y) in percent of the pattern dimension. x is the horizontal distance from the top left corner, y is the vertical distance from the top left corner. By default, the offset is (50,50), which means that the center of the pattern will be clicked at. @param pat: path of pattern image (PNG) to click on. @param mousebutton: mouse button number used for the click @param offset: offset from the top left point of the match. (float,float) @param tolerance: An integer ≥ 0 to specify the level of tolerance for 'fuzzy' matches. If negative or not convertible to int, raises ValueError. @param restore_pos: return to the initial mouse position after the click. @raises: L{PatternNotFound}: Raised when the pattern is not found on the screen """ x0, y0 = mouse_pos() move_to_pat(pat, offset, tolerance) mouse_click(mousebutton) if restore_pos: mouse_move(x0, y0)
[docs] def move_to_pat(pat: str, offset: (float, float)=None, tolerance: int=0) -> None: """See L{click_on_pat}""" with tempfile.NamedTemporaryFile() as f: subprocess.call(''' xwd -root -silent -display :0 | convert xwd:- png:''' + f.name, shell=True) loc = visgrep(f.name, pat, tolerance) pat_size = get_png_dim(pat) if offset is None: x, y = [l + ps//2 for l, ps in zip(loc, pat_size)] else: x, y = [l + ps*(off/100) for off, l, ps in zip(offset, loc, pat_size)] mouse_move(x, y)
[docs] def acknowledge_gnome_notification(): """ Moves mouse pointer to the bottom center of the screen and clicks on it. """ x0, y0 = mouse_pos() mouse_move(10000, 10000) # TODO: What if the screen is larger? Loop until mouse position does not change anymore? x, y = mouse_pos() mouse_rmove(-x/2, 0) mouse_click(LEFT) time.sleep(.2) mouse_move(x0, y0)