# -*- coding: utf-8 -*-
#
# This file is part of EventGhost.
# Copyright (C) 2005-2009 Lars-Peter Voss <bitmonster@eventghost.org>
#
# EventGhost is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 2 as published by the
# Free Software Foundation;
#
# EventGhost 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 <http://www.gnu.org/licenses/>.

VK_CODES = (
    ('AltGr', 10),
    ('Shift', 16),
    ('LShift', 160),
    ('RShift', 161),
    ('Ctrl', 17),
    ('LCtrl', 162),
    ('RCtrl', 163),
    ('Alt', 18),
    ('LAlt', 164),
    ('RAlt', 165),
    ('LWin', 91),
    ('RWin', 92),
    ('Apps', 93),
    ('LButton', 1),
    ('RButton', 2),
    ('MButton', 4),
    ('XButton1', 5),
    ('XButton2', 6),

    ('CapsLock', 20),
    ('NumLock', 144),
    ('ScrollLock', 145),

    ('Cancel', 3),
    ('Backspace', 8),
    ('Tabulator', 9),
    ('Clear', 12),
    ('Pause', 19),
    ('Kana', 21),
    ('Junja', 23),
    ('Final', 24),
    ('Hanja', 25),
    ('Escape', 27),
    ('Convert', 28),
    ('NonConvert', 29),
    ('Accept', 30),
    ('ModeChange', 31),
    ('Space', 32),
    ('PageUp', 33),
    ('PageDown', 34),
    ('End', 35),
    ('Home', 36),
    ('Left', 37),
    ('Up', 38),
    ('Right', 39),
    ('Down', 40),
    ('Select', 41),
    ('Print', 42),
    ('Execute', 43),
    ('PrintScreen', 44),
    ('Insert', 45),
    ('Delete', 46),
    ('Help', 47),
    ('A', 65),
    ('B', 66),
    ('C', 67),
    ('D', 68),
    ('E', 69),
    ('F', 70),
    ('G', 71),
    ('H', 72),
    ('I', 73),
    ('J', 74),
    ('K', 75),
    ('L', 76),
    ('M', 77),
    ('N', 78),
    ('O', 79),
    ('P', 80),
    ('Q', 81),
    ('R', 82),
    ('S', 83),
    ('T', 84),
    ('U', 85),
    ('V', 86),
    ('W', 87),
    ('X', 88),
    ('Y', 89),
    ('Z', 90),
    ('Sleep', 95),
    ('0', 48),
    ('1', 49),
    ('2', 50),
    ('3', 51),
    ('4', 52),
    ('5', 53),
    ('6', 54),
    ('7', 55),
    ('8', 56),
    ('9', 57),
    ('Numpad0', 96),
    ('Numpad1', 97),
    ('Numpad2', 98),
    ('Numpad3', 99),
    ('Numpad4', 100),
    ('Numpad5', 101),
    ('Numpad6', 102),
    ('Numpad7', 103),
    ('Numpad8', 104),
    ('Numpad9', 105),
    ('Multiply', 106),
    ('Add', 107),
    ('Separator', 108),
    ('Subtract', 109),
    ('Decimal', 110),
    ('Divide', 111),
    ('F1', 112),
    ('F2', 113),
    ('F3', 114),
    ('F4', 115),
    ('F5', 116),
    ('F6', 117),
    ('F7', 118),
    ('F8', 119),
    ('F9', 120),
    ('F10', 121),
    ('F11', 122),
    ('F12', 123),
    ('F13', 124),
    ('F14', 125),
    ('F15', 126),
    ('F16', 127),
    ('F17', 128),
    ('F18', 129),
    ('F19', 130),
    ('F20', 131),
    ('F21', 132),
    ('F22', 133),
    ('F23', 134),
    ('F24', 135),

    ('BrowserBack', 166),
    ('BrowserForward', 167),
    ('BrowserRefresh', 168),
    ('BrowserStop', 169),
    ('BrowserSearch', 170),
    ('BrowserFavorites', 171),
    ('BrowserHome', 172),
    ('VolumeMute', 173),
    ('VolumeDown', 174),
    ('VolumeUp', 175),
    ('MediaNextTrack', 176),
    ('MediaPrevTrack', 177),
    ('MediaStop', 178),
    ('MediaPlayPause', 179),
    ('LaunchMail', 180),
    ('LaunchMediaSelect', 181),
    ('LaunchApp1', 182),
    ('LaunchApp2', 183),

    ('OemPlus', 187),
    ('OemComma', 188),
    ('OemMinus', 189),
    ('OemPeriod', 190),
    ('Oem1', 186),
    ('Oem2', 191),
    ('Oem3', 192),
    ('Oem4', 219),
    ('Oem5', 220),
    ('Oem6', 221),
    ('Oem7', 222),
    ('Oem8', 223),
    ('Oem92', 146),
    ('Oem93', 147),
    ('Oem94', 148),
    ('Oem95', 149),
    ('Oem96', 150),
    ('OemE1', 225),
    ('Oem102', 226),
    ('OemE3', 227),
    ('OemE4', 228),
    ('ProcessKey', 229),
    ('OemE6', 230),
    ('Packet', 231),
    ('OemE9', 233),
    ('OemEA', 234),
    ('OemEB', 235),
    ('OemEC', 236),
    ('OemED', 237),
    ('OemEE', 238),
    ('OemEF', 239),
    ('OemF0', 240),
    ('OemF1', 241),
    ('OemF2', 242),
    ('OemF3', 243),
    ('OemF4', 244),
    ('OemF5', 245),
    ('Attn', 246),
    ('CrSel', 247),
    ('ExSel', 248),
    ('EraseEof', 249),
    ('Play', 250),
    ('Zoom', 251),
    ('Noname', 252),
    ('PA1', 253),
    ('OemClear', 254),

    ('U00', 0),
    ('U07', 7),
    #('Reserved_0A', 10), we use this code as AltGr
    ('U0B', 11),
    ('U0E', 14),
    ('U0F', 15),
    ('U16', 22),
    ('U1A', 26),
    ('U3A', 58),
    ('U3B', 59),
    ('U3C', 60),
    ('U3D', 61),
    ('U3E', 62),
    ('U3F', 63),
    ('U40', 64),
    ('U5E', 94),
    ('U88', 136),
    ('U89', 137),
    ('U8A', 138),
    ('U8B', 139),
    ('U8C', 140),
    ('U8D', 141),
    ('U8E', 142),
    ('U8F', 143),
    ('U97', 151),
    ('U98', 152),
    ('U99', 153),
    ('U9A', 154),
    ('U9B', 155),
    ('U9C', 156),
    ('U9D', 157),
    ('U9E', 158),
    ('U9F', 159),
    ('UB8', 184),
    ('UB9', 185),
    ('UC1', 193),
    ('UC2', 194),
    ('UC3', 195),
    ('UC4', 196),
    ('UC5', 197),
    ('UC6', 198),
    ('UC7', 199),
    ('UC8', 200),
    ('UC9', 201),
    ('UCA', 202),
    ('UCB', 203),
    ('UCC', 204),
    ('UCD', 205),
    ('UCE', 206),
    ('UCF', 207),
    ('UD0', 208),
    ('UD1', 209),
    ('UD2', 210),
    ('UD3', 211),
    ('UD4', 212),
    ('UD5', 213),
    ('UD6', 214),
    ('UD7', 215),
    ('UD8', 216),
    ('UD9', 217),
    ('UDA', 218),
    ('UE0', 224),
    ('UE8', 232),
    ('UFF', 255),

    ('Return', 13),
)

import eg
from eg.WinApi.Dynamic import (
    SendInput, OpenProcess, CloseHandle, WaitForInputIdle, GetKeyboardState,
    SetTimer, GetCurrentThreadId, GetWindowThreadProcessId, SetKeyboardState,
    INPUT, INPUT_KEYBOARD, KEYEVENTF_KEYUP,
    GetGUIThreadInfo, GUITHREADINFO, PROCESS_QUERY_INFORMATION, VK_SHIFT,
    VK_LSHIFT, VK_CONTROL, VK_LCONTROL, VK_MENU,
    WM_TIMER, WM_SYSKEYDOWN, WM_KEYDOWN, WM_SYSKEYUP, WM_KEYUP, DWORD,
    AttachThreadInput, VkKeyScanW, MapVirtualKey, GetMessage,
    PostMessage, MSG, byref, sizeof, pointer, c_ubyte,
    GetFocus, GetForegroundWindow,
)
import wx


del GetKeyboardState.argtypes
del SetKeyboardState.argtypes
del SetTimer.argtypes

PBYTE256 = c_ubyte * 256

VK_KEYS = {
    'BACK': 0x08,
    'TAB': 0x09,
    'ENTER': 0x0D,
    'CONTROL': 0x11,
    'CAPITAL': 0x14,
    'ESC': 0x1B,
    'SPC': 0x20,
    'PGUP': 0x21,
    'PGDOWN': 0x22,
    'INS': 0x012D,
    'DEL': 0x012E,
    'WIN': 0x015B,
}

for keyword, code in VK_CODES:
    VK_KEYS[keyword.upper()] = code


def ParseSingleChar(char):
    """
    Translates a single character to the needed key sequence.
    """
    vkCode = VkKeyScanW(char) & 0xFFFF
    if vkCode == 0xFFFF:
        eg.PrintError(
            "SendKeys: Can't translate character '%s' to key sequence!" % char
        )
        return
    data = []
    if vkCode & 0x200:
        data.append(VK_CONTROL)
    if vkCode & 0x400:
        data.append(VK_MENU)
    if vkCode & 0x100:
        data.append(VK_LSHIFT)
    data.append(vkCode)
    return data


def ParseText(text):
    """
    Translates a string to a key sequence.
    """
    data = []
    i = 0
    strLen = len(text)
    while i < strLen:
        char = text[i]
        if char == "{":
            if i+1 < strLen and text[i+1] == "{":
                i += 2
                data.append(ParseSingleChar(char))
            else:
                end = text.find("}", i+1)
                if end == -1:
                    raise SyntaxError("Matching closing brace not found")
                key = text[i+1:end]
                i = end + 1
                key2 = key.replace("_", "+").replace("-", "+").upper()
                words = key2.split("+")
                for word in words:
                    if word not in VK_KEYS:
                        res = unicode(eval(key, {}, eg.globals.__dict__))
                        data.extend(ParseText(res))
                        break
                else:
                    data.append([VK_KEYS[word] for word in words])
        else:
            i += 1
            data.append(ParseSingleChar(char))
    return data



class SendKeysParser:

    @eg.LogIt
    def __init__(self):
        self.dummyWindow = wx.Frame(None, -1, "Dummy Window")
        self.dummyHwnd = self.dummyWindow.GetHandle()
        self.msg = MSG()
        self.isSysKey = False
        self.sendInputStruct = INPUT()
        self.sendInputStruct.type = INPUT_KEYBOARD
        self.keyboardStateBuffer = PBYTE256()
        self.procHandle = None
        self.guiTreadInfo = GUITHREADINFO()
        self.guiTreadInfo.cbSize = sizeof(GUITHREADINFO)


    def __call__(self, hwnd, keystrokeString, useAlternateMethod=False):
        needGetFocus = False
        sendToFront = False
        if hwnd is None:
            sendToFront = True
            hwnd = GetForegroundWindow()
            needGetFocus = True

        dwProcessId = DWORD()
        threadID = GetWindowThreadProcessId(hwnd, byref(dwProcessId))
        processID = dwProcessId.value
        ourThreadID = GetCurrentThreadId()

        # If not, attach our thread's 'input' to the foreground thread's
        if threadID != ourThreadID:
            AttachThreadInput(threadID, ourThreadID, True)

        if needGetFocus:
            hwnd = GetFocus()
        if not sendToFront:
            if GetGUIThreadInfo(0, byref(self.guiTreadInfo)):
                sendToFront = (self.guiTreadInfo.hwndFocus == hwnd)
            else:
                sendToFront = False
        if not hwnd:
            hwnd = None
        self.procHandle = OpenProcess(
                PROCESS_QUERY_INFORMATION,
                0,
                processID
            )
        #self.WaitForInputProcessed()
        oldKeyboardState = PBYTE256()
        GetKeyboardState(byref(oldKeyboardState))

        keyData = ParseText(keystrokeString)
        if sendToFront and not useAlternateMethod:
            self.SendRawCodes1(keyData)
        else:
            self.SendRawCodes2(keyData, hwnd)

        SetKeyboardState(byref(oldKeyboardState))
        self.WaitForInputProcessed()
        if threadID != ourThreadID:
            AttachThreadInput(threadID, ourThreadID, False)
        if self.procHandle:
            CloseHandle(self.procHandle)


    def WaitForInputProcessed(self):
        if self.procHandle:
            WaitForInputIdle(self.procHandle, 100)
        def DoIt():
            SetTimer(self.dummyHwnd, 1, 0, None)
            self.msg.message = 0
            while self.msg.message != WM_TIMER:
                GetMessage(byref(self.msg), self.dummyHwnd, 0, 0)
        eg.CallWait(DoIt)


    def SendRawCodes1(self, keyData):
        """
        Uses the SendInput-API function to send the virtual keycode.
        Can only send to the frontmost window.
        """
        sendInputStruct = self.sendInputStruct
        sendInputStructPointer = pointer(sendInputStruct)
        sendInputStructSize = sizeof(sendInputStruct)
        keyboardStruct = sendInputStruct.ki
        for block in keyData:
            keyboardStruct.dwFlags = 0
            for virtualKey in block:
                keyboardStruct.wVk = virtualKey & 0xFF
                SendInput(1, sendInputStructPointer, sendInputStructSize)
                self.WaitForInputProcessed()
            keyboardStruct.dwFlags = KEYEVENTF_KEYUP
            for virtualKey in reversed(block):
                keyboardStruct.wVk = virtualKey & 0xFF
                SendInput(1, sendInputStructPointer, sendInputStructSize)
                self.WaitForInputProcessed()


    def SendRawCodes2(self, keyData, hwnd):
        """
        Uses PostMessage and SetKeyboardState to emulate the the virtual
        keycode. Can send to a specified window handle.
        """
        keyboardStateBuffer = self.keyboardStateBuffer
        for block in keyData:
            for virtualKey in block:
                keyCode = virtualKey & 0xFF
                highBits = virtualKey & 0xFF00
                lparam = ((MapVirtualKey(keyCode, 0) | highBits) << 16) | 1

                keyboardStateBuffer[keyCode] |= 128

                if keyCode == VK_LSHIFT:
                    keyboardStateBuffer[VK_SHIFT] |= 128
                #elif keyCode == VK_MENU:
                #    self.isSysKey = True
                elif keyCode == VK_CONTROL:
                    keyboardStateBuffer[VK_LCONTROL] |= 128

                if self.isSysKey:
                    mesg = WM_SYSKEYDOWN
                    lparam |= 0x20000000
                else:
                    mesg = WM_KEYDOWN

                SetKeyboardState(byref(keyboardStateBuffer))
                PostMessage(hwnd, mesg, keyCode, lparam)
                self.WaitForInputProcessed()

            for virtualKey in reversed(block):
                keyCode = virtualKey & 0xFF
                highBits = virtualKey & 0xFF00
                lparam = (
                    ((MapVirtualKey(keyCode, 0) | highBits) << 16)
                    | 0xC0000001
                )
                keyboardStateBuffer[keyCode] &= ~128

                if keyCode == VK_LSHIFT:
                    keyboardStateBuffer[VK_SHIFT] &= ~128
                #elif keyCode == VK_MENU:
                #    self.isSysKey = False
                elif keyCode == VK_CONTROL:
                    keyboardStateBuffer[VK_LCONTROL] &= ~128

                if self.isSysKey:
                    mesg = WM_SYSKEYUP
                    lparam |= 0x20000000
                else:
                    mesg = WM_KEYUP

                SetKeyboardState(byref(keyboardStateBuffer))
                PostMessage(hwnd, mesg, keyCode, lparam)
                self.WaitForInputProcessed()

