""" Platform-specific code for Windows is encapsulated in this module. """

import os
import re
import time
import numpy
import ctypes
import threading
try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk
from ctypes import wintypes
from PIL import Image, ImageTk, ImageOps

from .SettingsDebug import Debug

# Python 3 compatibility
try:
    basestring
except NameError:
    basestring = str
try:
    unicode
except:
    unicode = str

class PlatformManagerWindows(object):
    """ Abstracts Windows-specific OS-level features """
    def __init__(self):
        #self._root = tk.Tk()
        #self._root.overrideredirect(1)
        #self._root.withdraw()
        user32 = ctypes.WinDLL('user32', use_last_error=True)
        gdi32 = ctypes.WinDLL('gdi32', use_last_error=True)
        kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
        psapi = ctypes.WinDLL('psapi', use_last_error=True)
        self._user32 = user32
        self._gdi32 = gdi32
        self._kernel32 = kernel32
        self._psapi = psapi

        # Pay attention to different screen DPI settings
        self._user32.SetProcessDPIAware()

        # Mapping to `keyboard` names
        self._SPECIAL_KEYCODES = {
            "BACKSPACE": 	"backspace",
            "TAB": 			"tab",
            "CLEAR": 		"clear",
            "ENTER": 		"enter",
            "SHIFT": 		"shift",
            "CTRL": 		"ctrl",
            "ALT": 			"alt",
            "PAUSE": 		"pause",
            "CAPS_LOCK": 	"caps lock",
            "ESC": 			"esc",
            "SPACE":		"spacebar",
            "PAGE_UP":      "page up",
            "PAGE_DOWN":    "page down",
            "END":			"end",
            "HOME":			"home",
            "LEFT":			"left arrow",
            "UP":			"up arrow",
            "RIGHT":		"right arrow",
            "DOWN":			"down arrow",
            "SELECT":		"select",
            "PRINT":		"print",
            "PRINTSCREEN":	"print screen",
            "INSERT":		"ins",
            "DELETE":		"del",
            "WIN":			"win",
            "CMD":			"win",
            "META":			"win",
            "NUM0":		    "keypad 0",
            "NUM1":		    "keypad 1",
            "NUM2":		    "keypad 2",
            "NUM3":		    "keypad 3",
            "NUM4":		    "keypad 4",
            "NUM5":		    "keypad 5",
            "NUM6":		    "keypad 6",
            "NUM7":		    "keypad 7",
            "NUM8":		    "keypad 8",
            "NUM9":		    "keypad 9",
            "NUM9":		    "keypad 9",
            "SEPARATOR":    83,
            "ADD":	        78,
            "MINUS":        74,
            "MULTIPLY":     55,
            "DIVIDE":       53,
            "F1":			"f1",
            "F2":			"f2",
            "F3":			"f3",
            "F4":			"f4",
            "F5":			"f5",
            "F6":			"f6",
            "F7":			"f7",
            "F8":			"f8",
            "F9":			"f9",
            "F10":			"f10",
            "F11":			"f11",
            "F12":			"f12",
            "F13":			"f13",
            "F14":			"f14",
            "F15":			"f15",
            "F16":			"f16",
            "NUM_LOCK":		"num lock",
            "SCROLL_LOCK":	"scroll lock",
        }
        self._REGULAR_KEYCODES = {
            "0":			"0",
            "1":			"1",
            "2":			"2",
            "3":			"3",
            "4":			"4",
            "5":			"5",
            "6":			"6",
            "7":			"7",
            "8":			"8",
            "9":			"9",
            "a":			"a",
            "b":			"b",
            "c":			"c",
            "d":			"d",
            "e":			"e",
            "f":			"f",
            "g":			"g",
            "h":			"h",
            "i":			"i",
            "j":			"j",
            "k":			"k",
            "l":			"l",
            "m":			"m",
            "n":			"n",
            "o":			"o",
            "p":			"p",
            "q":			"q",
            "r":			"r",
            "s":			"s",
            "t":			"t",
            "u":			"u",
            "v":			"v",
            "w":			"w",
            "x":			"x",
            "y":			"y",
            "z":			"z",
            ";":			";",
            "=":			"=",
            ",":			",",
            "-":			"-",
            ".":			".",
            "/":			"/",
            "`":			"`",
            "[":			"[",
            "\\":			"\\",
            "]":			"]",
            "'":			"'",
            " ":			" ",
        }
        self._UPPERCASE_KEYCODES = {
            "~":			"`",
            "+":			"=",
            ")":			"0",
            "!":			"1",
            "@":			"2",
            "#":			"3",
            "$":			"4",
            "%":			"5",
            "^":			"6",
            "&":			"7",
            "*":			"8",
            "(":			"9",
            "A":			"a",
            "B":			"b",
            "C":			"c",
            "D":			"d",
            "E":			"e",
            "F":			"f",
            "G":			"g",
            "H":			"h",
            "I":			"i",
            "J":			"j",
            "K":			"k",
            "L":			"l",
            "M":			"m",
            "N":			"n",
            "O":			"o",
            "P":			"p",
            "Q":			"q",
            "R":			"r",
            "S":			"s",
            "T":			"t",
            "U":			"u",
            "V":			"v",
            "W":			"w",
            "X":			"x",
            "Y":			"y",
            "Z":			"z",
            ":":			";",
            "<":			",",
            "_":			"-",
            ">":			".",
            "?":			"/",
            "|":			"\\",
            "\"":			"'",
            "{":            "[",
            "}":            "]",
        }

    def _check_count(self, result, func, args):
        #pylint: disable=unused-argument
        """ Private function to return ctypes errors cleanly """
        if result == 0:
            raise ctypes.WinError(ctypes.get_last_error())
        return args

    ## Screen functions
    def getBitmapFromRect(self, x, y, w, h):
        """ Capture the specified area of the (virtual) screen. """
        min_x, min_y, screen_width, screen_height = self._getVirtualScreenRect()
        img = self._getVirtualScreenBitmap()
        # Limit the coordinates to the virtual screen
        # Then offset so 0,0 is the top left corner of the image
        # (Top left of virtual screen could be negative)
        x1 = min(max(min_x, x), min_x+screen_width) - min_x
        y1 = min(max(min_y, y), min_y+screen_height) - min_y
        x2 = min(max(min_x, x+w), min_x+screen_width) - min_x
        y2 = min(max(min_y, y+h), min_y+screen_height) - min_y
        return numpy.array(img.crop((x1, y1, x2, y2)))
    def getScreenBounds(self, screenId):
        """ Returns the screen size of the specified monitor (0 being the main monitor). """
        screen_details = self.getScreenDetails()
        if not isinstance(screenId, int) or screenId < -1 or screenId >= len(screen_details):
            raise ValueError("Invalid screen ID")
        if screenId == -1:
            # -1 represents the entire virtual screen
            return self._getVirtualScreenRect()
        return screen_details[screenId]["rect"]
    def getScreenDetails(self):
        """ Return list of attached monitors

        For each monitor (as dict), ``monitor["rect"]`` represents the screen as positioned
        in virtual screen. List is returned in device order, with the first element (0)
        representing the primary monitor.
        """
        monitors = self._getMonitorInfo()
        primary_screen = None
        screens = []
        for monitor in monitors:
            # Convert screen rect to Lackey-style rect (x,y,w,h) as position in virtual screen
            screen = {
                "rect": (
                    monitor["rect"][0],
                    monitor["rect"][1],
                    monitor["rect"][2] - monitor["rect"][0],
                    monitor["rect"][3] - monitor["rect"][1]
                )
            }
            screens.append(screen)
        return screens
    def isPointVisible(self, x, y):
        """ Checks if a point is visible on any monitor. """
        class POINT(ctypes.Structure):
            _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]
        pt = POINT()
        pt.x = x
        pt.y = y
        MONITOR_DEFAULTTONULL = 0
        hmon = self._user32.MonitorFromPoint(pt, MONITOR_DEFAULTTONULL)
        if hmon == 0:
            return False
        return True
    def _captureScreen(self, device_name):
        """ Captures a bitmap from the given monitor device name

        Returns as a PIL Image (BGR rather than RGB, for compatibility with OpenCV)
        """

        ## Define constants/structs
        class HBITMAP(ctypes.Structure):
            _fields_ = [("bmType", ctypes.c_long),
                        ("bmWidth", ctypes.c_long),
                        ("bmHeight", ctypes.c_long),
                        ("bmWidthBytes", ctypes.c_long),
                        ("bmPlanes", ctypes.wintypes.WORD),
                        ("bmBitsPixel", ctypes.wintypes.WORD),
                        ("bmBits", ctypes.wintypes.LPVOID)]
        class BITMAPINFOHEADER(ctypes.Structure):
            _fields_ = [("biSize", ctypes.wintypes.DWORD),
                        ("biWidth", ctypes.c_long),
                        ("biHeight", ctypes.c_long),
                        ("biPlanes", ctypes.wintypes.WORD),
                        ("biBitCount", ctypes.wintypes.WORD),
                        ("biCompression", ctypes.wintypes.DWORD),
                        ("biSizeImage", ctypes.wintypes.DWORD),
                        ("biXPelsPerMeter", ctypes.c_long),
                        ("biYPelsPerMeter", ctypes.c_long),
                        ("biClrUsed", ctypes.wintypes.DWORD),
                        ("biClrImportant", ctypes.wintypes.DWORD)]
        class BITMAPINFO(ctypes.Structure):
            _fields_ = [("bmiHeader", BITMAPINFOHEADER),
                        ("bmiColors", ctypes.wintypes.DWORD*3)]
        HORZRES = ctypes.c_int(8)
        VERTRES = ctypes.c_int(10)
        SRCCOPY =    0x00CC0020
        CAPTUREBLT = 0x40000000
        DIB_RGB_COLORS = 0

        ## Begin logic
        self._gdi32.CreateDCW.restype = ctypes.c_void_p
        hdc = self._gdi32.CreateDCW(ctypes.c_wchar_p(str(device_name)), 0, 0, 0) # Convert to bytestring for c_wchar_p type
        if hdc == 0:
            raise ValueError("Empty hdc provided")

        # Get monitor specs
        self._gdi32.GetDeviceCaps.argtypes = [ctypes.c_void_p, ctypes.c_int]
        screen_width = self._gdi32.GetDeviceCaps(hdc, HORZRES)
        screen_height = self._gdi32.GetDeviceCaps(hdc, VERTRES)

        # Create memory device context for monitor
        self._gdi32.CreateCompatibleDC.restype = ctypes.c_void_p
        self._gdi32.CreateCompatibleDC.argtypes = [ctypes.c_void_p]
        hCaptureDC = self._gdi32.CreateCompatibleDC(hdc)
        if hCaptureDC == 0:
            raise WindowsError("gdi:CreateCompatibleDC failed")

        # Create bitmap compatible with monitor
        self._gdi32.CreateCompatibleBitmap.restype = ctypes.c_void_p
        self._gdi32.CreateCompatibleBitmap.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
        hCaptureBmp = self._gdi32.CreateCompatibleBitmap(hdc, screen_width, screen_height)
        if hCaptureBmp == 0:
            raise WindowsError("gdi:CreateCompatibleBitmap failed")

        # Select hCaptureBmp into hCaptureDC device context
        self._gdi32.SelectObject.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
        self._gdi32.SelectObject(hCaptureDC, hCaptureBmp)

        # Perform bit-block transfer from screen to device context (and thereby hCaptureBmp)
        self._gdi32.BitBlt.argtypes = [
            ctypes.c_void_p,
            ctypes.c_int,
            ctypes.c_int,
            ctypes.c_int,
            ctypes.c_int,
            ctypes.c_void_p,
            ctypes.c_int,
            ctypes.c_int,
            ctypes.c_ulong
        ]
        self._gdi32.BitBlt(hCaptureDC, 0, 0, screen_width, screen_height, hdc, 0, 0, SRCCOPY | CAPTUREBLT)

        # Capture image bits from bitmap
        img_info = BITMAPINFO()
        img_info.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
        img_info.bmiHeader.biWidth = screen_width
        img_info.bmiHeader.biHeight = screen_height
        img_info.bmiHeader.biPlanes = 1
        img_info.bmiHeader.biBitCount = 32
        img_info.bmiHeader.biCompression = 0
        img_info.bmiHeader.biClrUsed = 0
        img_info.bmiHeader.biClrImportant = 0

        buffer_length = screen_width * 4 * screen_height
        image_data = ctypes.create_string_buffer(buffer_length)

        self._gdi32.GetDIBits.restype = ctypes.c_int
        self._gdi32.GetDIBits.argtypes = [
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_uint,
            ctypes.c_uint,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_uint
        ]
        scanlines = self._gdi32.GetDIBits(
            hCaptureDC,
            hCaptureBmp,
            0,
            screen_height,
            ctypes.byref(image_data),
            ctypes.byref(img_info),
            DIB_RGB_COLORS)
        if scanlines != screen_height:
            raise WindowsError("gdi:GetDIBits failed")
        final_image = ImageOps.flip(
            Image.frombuffer(
                "RGBX",
                (screen_width, screen_height),
                image_data,
                "raw",
                "RGBX",
                0,
                1))
        # Destroy created device context & GDI bitmap
        self._gdi32.DeleteObject.argtypes = [ctypes.c_void_p]
        self._gdi32.DeleteObject(hdc)
        self._gdi32.DeleteObject(hCaptureDC)
        self._gdi32.DeleteObject(hCaptureBmp)
        return final_image
    def _getMonitorInfo(self):
        """ Returns info about the attached monitors, in device order

        [0] is always the primary monitor
        """
        monitors = []
        CCHDEVICENAME = 32
        def _MonitorEnumProcCallback(hMonitor, hdcMonitor, lprcMonitor, dwData):
            class MONITORINFOEX(ctypes.Structure):
                _fields_ = [("cbSize", ctypes.wintypes.DWORD),
                            ("rcMonitor", ctypes.wintypes.RECT),
                            ("rcWork", ctypes.wintypes.RECT),
                            ("dwFlags", ctypes.wintypes.DWORD),
                            ("szDevice", ctypes.wintypes.WCHAR*CCHDEVICENAME)]
            lpmi = MONITORINFOEX()
            lpmi.cbSize = ctypes.sizeof(MONITORINFOEX)
            self._user32.GetMonitorInfoW(hMonitor, ctypes.byref(lpmi))
            #hdc = self._gdi32.CreateDCA(ctypes.c_char_p(lpmi.szDevice), 0, 0, 0)
            monitors.append({
                "hmon": hMonitor,
                #"hdc":  hdc,
                "rect": (lprcMonitor.contents.left,
                         lprcMonitor.contents.top,
                         lprcMonitor.contents.right,
                         lprcMonitor.contents.bottom),
                "name": lpmi.szDevice
                })
            return True
        MonitorEnumProc = ctypes.WINFUNCTYPE(
            ctypes.c_bool,
            ctypes.c_ulong,
            ctypes.c_ulong,
            ctypes.POINTER(ctypes.wintypes.RECT),
            ctypes.c_int)
        callback = MonitorEnumProc(_MonitorEnumProcCallback)
        if self._user32.EnumDisplayMonitors(0, 0, callback, 0) == 0:
            raise WindowsError("Unable to enumerate monitors")
        # Clever magic to make the screen with origin of (0,0) [the primary monitor]
        # the first in the list
        # Sort by device ID - 0 is primary, 1 is next, etc.
        monitors.sort(key=lambda x: (not (x["rect"][0] == 0 and x["rect"][1] == 0), x["name"]))

        return monitors
    def _getVirtualScreenRect(self):
        """ The virtual screen is the bounding box containing all monitors.

        Not all regions in the virtual screen are actually visible. The (0,0) coordinate
        is the top left corner of the primary screen rather than the whole bounding box, so
        some regions of the virtual screen may have negative coordinates if another screen
        is positioned in Windows as further to the left or above the primary screen.

        Returns the rect as (x, y, w, h)
        """
        SM_XVIRTUALSCREEN = 76  # Left of virtual screen
        SM_YVIRTUALSCREEN = 77  # Top of virtual screen
        SM_CXVIRTUALSCREEN = 78 # Width of virtual screen
        SM_CYVIRTUALSCREEN = 79 # Height of virtual screen

        return (self._user32.GetSystemMetrics(SM_XVIRTUALSCREEN), \
                self._user32.GetSystemMetrics(SM_YVIRTUALSCREEN), \
                self._user32.GetSystemMetrics(SM_CXVIRTUALSCREEN), \
                self._user32.GetSystemMetrics(SM_CYVIRTUALSCREEN))
    def _getVirtualScreenBitmap(self):
        """ Returns a PIL bitmap (BGR channel order) of all monitors

        Arranged like the Virtual Screen
        """

        # Collect information about the virtual screen & monitors
        min_x, min_y, screen_width, screen_height = self._getVirtualScreenRect()
        monitors = self._getMonitorInfo()

        # Initialize new black image the size of the virtual screen
        virt_screen = Image.new("RGB", (screen_width, screen_height))

        # Capture images of each of the monitors and overlay on the virtual screen
        for monitor_id in range(0, len(monitors)):
            img = self._captureScreen(monitors[monitor_id]["name"])
            # Capture virtscreen coordinates of monitor
            x1, y1, x2, y2 = monitors[monitor_id]["rect"]
            # Convert to image-local coordinates
            x = x1 - min_x
            y = y1 - min_y
            # Paste on the virtual screen
            virt_screen.paste(img, (x, y))
        return virt_screen


    ## Clipboard functions
    def osCopy(self):
        """ Triggers the OS "copy" keyboard shortcut """
        from .InputEmulation import Keyboard
        k = Keyboard()
        k.keyDown("{CTRL}")
        k.type("c")
        k.keyUp("{CTRL}")
    def osPaste(self):
        """ Triggers the OS "paste" keyboard shortcut """
        from .InputEmulation import Keyboard
        k = Keyboard()
        k.keyDown("{CTRL}")
        k.type("v")
        k.keyUp("{CTRL}")

    ## Window functions
    def getWindowByTitle(self, wildcard, order=0):
        """ Returns a handle for the first window that matches the provided "wildcard" regex """
        EnumWindowsProc = ctypes.WINFUNCTYPE(
            ctypes.c_bool,
            ctypes.POINTER(ctypes.c_int),
            ctypes.py_object)
        def callback(hwnd, context):
            if ctypes.windll.user32.IsWindowVisible(hwnd):
                length = ctypes.windll.user32.GetWindowTextLengthW(hwnd)
                buff = ctypes.create_unicode_buffer(length + 1)
                ctypes.windll.user32.GetWindowTextW(hwnd, buff, length + 1)
                if re.search(context["wildcard"], buff.value, flags=re.I) != None and not context["handle"]:
                    if context["order"] > 0:
                        context["order"] -= 1
                    else:
                        context["handle"] = hwnd
            return True
        data = {"wildcard": wildcard, "handle": None, "order": order}
        ctypes.windll.user32.EnumWindows(EnumWindowsProc(callback), ctypes.py_object(data))
        return data["handle"]
    def getWindowByPID(self, pid, order=0):
        """ Returns a handle for the first window that matches the provided PID """
        if pid <= 0:
            return None
        EnumWindowsProc = ctypes.WINFUNCTYPE(
            ctypes.c_bool,
            ctypes.POINTER(ctypes.c_int),
            ctypes.py_object)
        def callback(hwnd, context):
            if ctypes.windll.user32.IsWindowVisible(hwnd):
                pid = ctypes.c_ulong()
                ctypes.windll.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
                if context["pid"] == int(pid.value) and not context["handle"]:
                    if context["order"] > 0:
                        context["order"] -= 1
                    else:
                        context["handle"] = hwnd
            return True
        data = {"pid": pid, "handle": None, "order": order}
        ctypes.windll.user32.EnumWindows(EnumWindowsProc(callback), ctypes.py_object(data))
        return data["handle"]
    def getWindowRect(self, hwnd):
        """ Returns a rect (x,y,w,h) for the specified window's area """
        rect = ctypes.wintypes.RECT()
        if ctypes.windll.user32.GetWindowRect(hwnd, ctypes.byref(rect)):
            x1 = rect.left
            y1 = rect.top
            x2 = rect.right
            y2 = rect.bottom
            return (x1, y1, x2-x1, y2-y1)
        return None
    def focusWindow(self, hwnd):
        """ Brings specified window to the front """
        Debug.log(3, "Focusing window: " + str(hwnd))
        SW_RESTORE = 9
        if ctypes.windll.user32.IsIconic(hwnd):
            ctypes.windll.user32.ShowWindow(hwnd, SW_RESTORE)
        ctypes.windll.user32.SetForegroundWindow(hwnd)
    def getWindowTitle(self, hwnd):
        """ Gets the title for the specified window """
        length = ctypes.windll.user32.GetWindowTextLengthW(hwnd)
        buff = ctypes.create_unicode_buffer(length + 1)
        ctypes.windll.user32.GetWindowTextW(hwnd, buff, length + 1)
        return buff.value
    def getWindowPID(self, hwnd):
        """ Gets the process ID that the specified window belongs to """
        pid = ctypes.c_ulong()
        ctypes.windll.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
        return int(pid.value)
    def getForegroundWindow(self):
        """ Returns a handle to the window in the foreground """
        return self._user32.GetForegroundWindow()

    ## Highlighting functions
    def highlight(self, rect, color="red", seconds=None):
        """ Simulates a transparent rectangle over the specified ``rect`` on the screen.

        Actually takes a screenshot of the region and displays with a
        rectangle border in a borderless window (due to Tkinter limitations)

        If a Tkinter root window has already been created somewhere else,
        uses that instead of creating a new one.
        """
        if tk._default_root is None:
            Debug.log(3, "Creating new temporary Tkinter root")
            temporary_root = True
            root = tk.Tk()
            root.withdraw()
        else:
            Debug.log(3, "Borrowing existing Tkinter root")
            temporary_root = False
            root = tk._default_root
        image_to_show = self.getBitmapFromRect(*rect)
        app = highlightWindow(root, rect, color, image_to_show)
        if seconds == 0:
            t = threading.Thread(target=app.do_until_timeout)
            t.start()
            return app
        app.do_until_timeout(seconds)
        

    ## Process functions
    def isPIDValid(self, pid):
        """ Checks if a PID is associated with a running process """
        ## Slightly copied wholesale from http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid
        ## Thanks to http://stackoverflow.com/users/1777162/ntrrgc and http://stackoverflow.com/users/234270/speedplane
        class ExitCodeProcess(ctypes.Structure):
            _fields_ = [('hProcess', ctypes.c_void_p),
                        ('lpExitCode', ctypes.POINTER(ctypes.c_ulong))]
        SYNCHRONIZE = 0x100000
        PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
        process = self._kernel32.OpenProcess(SYNCHRONIZE|PROCESS_QUERY_LIMITED_INFORMATION, 0, pid)
        if not process:
            return False
        ec = ExitCodeProcess()
        out = self._kernel32.GetExitCodeProcess(process, ctypes.byref(ec))
        if not out:
            err = self._kernel32.GetLastError()
            if self._kernel32.GetLastError() == 5:
                # Access is denied.
                logging.warning("Access is denied to get pid info.")
            self._kernel32.CloseHandle(process)
            return False
        elif bool(ec.lpExitCode):
            # There is an exit code, it quit
            self._kernel32.CloseHandle(process)
            return False
        # No exit code, it's running.
        self._kernel32.CloseHandle(process)
        return True
    def killProcess(self, pid):
        """ Kills the process with the specified PID (if possible) """
        SYNCHRONIZE = 0x00100000
        PROCESS_TERMINATE = 0x0001
        hProcess = self._kernel32.OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, True, pid)
        result = self._kernel32.TerminateProcess(hProcess, 0)
        self._kernel32.CloseHandle(hProcess)
    def getProcessName(self, pid):
        if pid <= 0:
            return ""
        MAX_PATH_LEN = 2048
        proc_name = ctypes.create_string_buffer(MAX_PATH_LEN)
        PROCESS_VM_READ = 0x0010
        PROCESS_QUERY_INFORMATION = 0x0400
        hProcess = self._kernel32.OpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION, 0, pid)
        #self._psapi.GetProcessImageFileName.restype = ctypes.wintypes.DWORD
        self._psapi.GetModuleFileNameExA(hProcess, 0, ctypes.byref(proc_name), MAX_PATH_LEN)
        return os.path.basename(proc_name.value.decode("utf-8"))

## Helper class for highlighting

class highlightWindow(tk.Toplevel):
    def __init__(self, root, rect, frame_color, screen_cap):
        """ Accepts rect as (x,y,w,h) """
        self.root = root
        tk.Toplevel.__init__(self, self.root, bg="red", bd=0)

        ## Set toplevel geometry, remove borders, and push to the front
        self.geometry("{2}x{3}+{0}+{1}".format(*rect))
        self.overrideredirect(1)
        self.attributes("-topmost", True)

        ## Create canvas and fill it with the provided image. Then draw rectangle outline
        self.canvas = tk.Canvas(
            self,
            width=rect[2],
            height=rect[3],
            bd=0,
            bg="blue",
            highlightthickness=0)
        self.tk_image = ImageTk.PhotoImage(Image.fromarray(screen_cap[..., [2, 1, 0]]))
        self.canvas.create_image(0, 0, image=self.tk_image, anchor=tk.NW)
        self.canvas.create_rectangle(
            2,
            2,
            rect[2]-2,
            rect[3]-2,
            outline=frame_color,
            width=4)
        self.canvas.pack(fill=tk.BOTH, expand=tk.YES)

        ## Lift to front if necessary and refresh.
        self.lift()
        self.update()
    def do_until_timeout(self, seconds=None):
        if seconds is not None:
            self.root.after(seconds*1000, self.root.destroy)
        self.root.mainloop()

    def close(self):
        self.root.destroy()