/*
 * COPYRIGHT:        See COPYING in the top level directory
 * PROJECT:          ReactOS Win32k subsystem
 * PURPOSE:          HotKey support
 * FILE:             win32ss/user/ntuser/hotkey.c
 * PROGRAMER:        Eric Kohl
 */

/*
 * FIXME: Hotkey notifications are triggered by keyboard input (physical or programatically)
 * and since only desktops on WinSta0 can receive input in seems very wrong to allow
 * windows/threads on destops not belonging to WinSta0 to set hotkeys (receive notifications).
 *     -- Gunnar
 */

#include <win32k.h>
DBG_DEFAULT_CHANNEL(UserHotkey);

/* GLOBALS *******************************************************************/

/*
 * Hardcoded hotkeys. See http://ivanlef0u.fr/repo/windoz/VI20051005.html
 * or http://repo.meh.or.id/Windows/VI20051005.html .
 *
 * NOTE: The (Shift-)F12 keys are used only for the "UserDebuggerHotKey" setting
 * which enables setting a key shortcut which, when pressed, establishes a
 * breakpoint in the code being debugged:
 * see http://technet.microsoft.com/en-us/library/cc786263(v=ws.10).aspx
 * and http://flylib.com/books/en/4.441.1.33/1/ for more details.
 * By default the key is VK-F12 on a 101-key keyboard, and is VK_SUBTRACT
 * (hyphen / substract sign) on a 82-key keyboard.
 */
/*                       pti   pwnd  modifiers  vk      id  next */
// HOT_KEY hkF12 =      {NULL, 1,    0,         VK_F12, IDHK_F12,      NULL};
// HOT_KEY hkShiftF12 = {NULL, 1,    MOD_SHIFT, VK_F12, IDHK_SHIFTF12, &hkF12};
// HOT_KEY hkWinKey =   {NULL, 1,    MOD_WIN,   0,      IDHK_WINKEY,   &hkShiftF12};

PHOT_KEY gphkFirst = NULL;
BOOL bWinHotkeyActive = FALSE;

/* FUNCTIONS *****************************************************************/

VOID FASTCALL
StartDebugHotKeys(VOID)
{
    UINT vk = VK_F12;
    UserUnregisterHotKey(PWND_BOTTOM, IDHK_F12);
    UserUnregisterHotKey(PWND_BOTTOM, IDHK_SHIFTF12);
    if (!ENHANCED_KEYBOARD(gKeyboardInfo.KeyboardIdentifier))
    {
        vk = VK_SUBTRACT;
    }
    UserRegisterHotKey(PWND_BOTTOM, IDHK_SHIFTF12, MOD_SHIFT, vk);
    UserRegisterHotKey(PWND_BOTTOM, IDHK_F12, 0, vk);
    ERR("Start up the debugger hotkeys!! Should see this once!\n");
}

/*
 * IntGetModifiers
 *
 * Returns a value that indicates if the key is a modifier key, and
 * which one.
 */
static
UINT FASTCALL
IntGetModifiers(PBYTE pKeyState)
{
    UINT fModifiers = 0;

    if (IS_KEY_DOWN(pKeyState, VK_SHIFT))
        fModifiers |= MOD_SHIFT;

    if (IS_KEY_DOWN(pKeyState, VK_CONTROL))
        fModifiers |= MOD_CONTROL;

    if (IS_KEY_DOWN(pKeyState, VK_MENU))
        fModifiers |= MOD_ALT;

    if (IS_KEY_DOWN(pKeyState, VK_LWIN) || IS_KEY_DOWN(pKeyState, VK_RWIN))
        fModifiers |= MOD_WIN;

    return fModifiers;
}

/*
 * UnregisterWindowHotKeys
 *
 * Removes hotkeys registered by specified window on its cleanup
 */
VOID FASTCALL
UnregisterWindowHotKeys(PWND pWnd)
{
    PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst;

    while (pHotKey)
    {
        /* Save next ptr for later use */
        phkNext = pHotKey->pNext;

        /* Should we delete this hotkey? */
        if (pHotKey->pWnd == pWnd)
        {
            /* Update next ptr for previous hotkey and free memory */
            *pLink = phkNext;
            ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY);
        }
        else /* This hotkey will stay, use its next ptr */
            pLink = &pHotKey->pNext;

        /* Move to the next entry */
        pHotKey = phkNext;
    }
}

/*
 * UnregisterThreadHotKeys
 *
 * Removes hotkeys registered by specified thread on its cleanup
 */
VOID FASTCALL
UnregisterThreadHotKeys(PTHREADINFO pti)
{
    PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst;

    while (pHotKey)
    {
        /* Save next ptr for later use */
        phkNext = pHotKey->pNext;

        /* Should we delete this hotkey? */
        if (pHotKey->pti == pti)
        {
            /* Update next ptr for previous hotkey and free memory */
            *pLink = phkNext;
            ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY);
        }
        else /* This hotkey will stay, use its next ptr */
            pLink = &pHotKey->pNext;

        /* Move to the next entry */
        pHotKey = phkNext;
    }
}

/*
 * IsHotKey
 *
 * Checks if given key and modificators have corresponding hotkey
 */
static PHOT_KEY FASTCALL
IsHotKey(UINT fsModifiers, WORD wVk)
{
    PHOT_KEY pHotKey = gphkFirst;

    while (pHotKey)
    {
        if (pHotKey->fsModifiers == fsModifiers &&
            pHotKey->vk == wVk)
        {
            /* We have found it */
            return pHotKey;
        }

        /* Move to the next entry */
        pHotKey = pHotKey->pNext;
    }

    return NULL;
}

/*
 * co_UserProcessHotKeys
 *
 * Sends WM_HOTKEY message if given keys are hotkey
 */
BOOL NTAPI
co_UserProcessHotKeys(WORD wVk, BOOL bIsDown)
{
    UINT fModifiers;
    PHOT_KEY pHotKey;
    PWND pWnd;
    BOOL DoNotPostMsg = FALSE;

    if (wVk == VK_SHIFT || wVk == VK_CONTROL || wVk == VK_MENU ||
        wVk == VK_LWIN || wVk == VK_RWIN)
    {
        /* Those keys are specified by modifiers */
        wVk = 0;
    }

    fModifiers = IntGetModifiers(gafAsyncKeyState);

    /* Check if it is a hotkey */
    pHotKey = IsHotKey(fModifiers, wVk);

    if (pHotKey)
    {
        TRACE("Hot key pressed (pWnd %p, id %d)\n", pHotKey->pWnd, pHotKey->id);

        /* FIXME: See comment about "UserDebuggerHotKey" on top of this file. */
        if (pHotKey->id == IDHK_SHIFTF12 || pHotKey->id == IDHK_F12)
        {
            if (bIsDown)
            {
                ERR("Hot key pressed for Debug Activation! ShiftF12 = %d or F12 = %d\n",pHotKey->id == IDHK_SHIFTF12 , pHotKey->id == IDHK_F12);
                //DoNotPostMsg = co_ActivateDebugger(); // FIXME
            }
            return DoNotPostMsg;
        }

        /* Process hotkey if it is key up event */
        if (!bIsDown)
        {
            /* WIN and F12 keys are not hardcoded here. See comments on top of this file. */
            if (pHotKey->id == IDHK_WINKEY && bWinHotkeyActive == TRUE)
            {
                pWnd = ValidateHwndNoErr(InputWindowStation->ShellWindow);
                if (pWnd)
                {
                   TRACE("System Hot key Id %d Key %d\n",pHotKey->id, wVk );
                   UserPostMessage(UserHMGetHandle(pWnd), WM_SYSCOMMAND, SC_TASKLIST, 0);
                   //ptiLastInput = pWnd->head.pti;
                   bWinHotkeyActive = FALSE;
                   return FALSE;
                }
            }
        }
        else
        {    /* The user pressed the win key */
            if (pHotKey->id == IDHK_WINKEY)
            {
               bWinHotkeyActive = TRUE;
               return FALSE;
            }
        }

        if (bIsDown)
        {
            if (!pHotKey->pWnd)
            {
                TRACE("UPTM Hot key Id %d Key %d\n",pHotKey->id, wVk );
                UserPostThreadMessage(pHotKey->pti, WM_HOTKEY, pHotKey->id, MAKELONG(fModifiers, wVk));
                //ptiLastInput = pHotKey->pti;
                return TRUE; /* Don't send any message */
            }
            else
            {
               if (pHotKey->pWnd == PWND_BOTTOM)
               {
                   if (gpqForeground != NULL)
                   { 
                      pWnd = gpqForeground->spwndFocus; 
                   }
                   else
                      return FALSE;
                }
                else
                {
                   pWnd = pHotKey->pWnd;
                }
                if (pWnd)
                {          //  pWnd->head.rpdesk->pDeskInfo->spwndShell needs testing.
                   if (pWnd == ValidateHwndNoErr(InputWindowStation->ShellWindow) && pHotKey->id == SC_TASKLIST)
                   {
                      ERR("Sending to shell window w/o IDHK_WINKEY..\n");
                      UserPostMessage(UserHMGetHandle(pWnd), WM_SYSCOMMAND, SC_TASKLIST, 0);
                   }
                   else
                   {
                      TRACE("UPM Hot key Id %d Key %d\n",pHotKey->id, wVk );
                      UserPostMessage(UserHMGetHandle(pWnd), WM_HOTKEY, pHotKey->id, MAKELONG(fModifiers, wVk));
                   }
                   //ptiLastInput = pWnd->head.pti;
                   return TRUE; /* Don't send any message */
                }
            }
        }
    }
    return FALSE;
}


/*
 * DefWndGetHotKey
 *
 * GetHotKey message support
 */
UINT FASTCALL
DefWndGetHotKey(PWND pWnd)
{
    PHOT_KEY pHotKey = gphkFirst;

    WARN("DefWndGetHotKey\n");

    while (pHotKey)
    {
        if (pHotKey->pWnd == pWnd && pHotKey->id == IDHK_REACTOS)
        {
            /* We have found it */
            return MAKELONG(pHotKey->vk, pHotKey->fsModifiers);
        }

        /* Move to the next entry */
        pHotKey = pHotKey->pNext;
    }

    return 0;
}

/*
 * DefWndSetHotKey
 *
 * SetHotKey message support
 */
INT FASTCALL
DefWndSetHotKey(PWND pWnd, WPARAM wParam)
{
    UINT fsModifiers, vk;
    PHOT_KEY pHotKey, *pLink;
    INT iRet = 1;

    WARN("DefWndSetHotKey wParam 0x%x\n", wParam);

    // A hot key cannot be associated with a child window.
    if (pWnd->style & WS_CHILD)
        return 0;

    // VK_ESCAPE, VK_SPACE, and VK_TAB are invalid hot keys.
    if (LOWORD(wParam) == VK_ESCAPE ||
        LOWORD(wParam) == VK_SPACE ||
        LOWORD(wParam) == VK_TAB)
    {
        return -1;
    }

    vk = LOWORD(wParam);
    fsModifiers = HIWORD(wParam);

    if (wParam)
    {
        pHotKey = gphkFirst;
        while (pHotKey)
        {
            if (pHotKey->fsModifiers == fsModifiers &&
                pHotKey->vk == vk &&
                pHotKey->id == IDHK_REACTOS)
            {
                if (pHotKey->pWnd != pWnd)
                    iRet = 2; // Another window already has the same hot key.
                break;
            }

            /* Move to the next entry */
            pHotKey = pHotKey->pNext;
        }
    }
    
    pHotKey = gphkFirst;
    pLink = &gphkFirst;
    while (pHotKey)
    {
        if (pHotKey->pWnd == pWnd &&
            pHotKey->id == IDHK_REACTOS)
        {
            /* This window has already hotkey registered */
            break;
        }

        /* Move to the next entry */
        pLink = &pHotKey->pNext;
        pHotKey = pHotKey->pNext;
    }

    if (wParam)
    {
        if (!pHotKey)
        {
            /* Create new hotkey */
            pHotKey = ExAllocatePoolWithTag(PagedPool, sizeof(HOT_KEY), USERTAG_HOTKEY);
            if (pHotKey == NULL)
                return 0;

            pHotKey->pWnd = pWnd;
            pHotKey->id = IDHK_REACTOS; // Don't care, these hot keys are unrelated to the hot keys set by RegisterHotKey
            pHotKey->pNext = gphkFirst;
            gphkFirst = pHotKey;
        }

        /* A window can only have one hot key. If the window already has a
           hot key associated with it, the new hot key replaces the old one. */
        pHotKey->pti = NULL;
        pHotKey->fsModifiers = fsModifiers;
        pHotKey->vk = vk;
    }
    else if (pHotKey)
    {
        /* Remove hotkey */
        *pLink = pHotKey->pNext;
        ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY);
    }

    return iRet;
}


BOOL FASTCALL
UserRegisterHotKey(PWND pWnd,
                   int id,
                   UINT fsModifiers,
                   UINT vk)
{
    PHOT_KEY pHotKey;
    PTHREADINFO pHotKeyThread;

    /* Find hotkey thread */
    if (pWnd == NULL || pWnd == PWND_BOTTOM)
    {
        pHotKeyThread = PsGetCurrentThreadWin32Thread();
    }
    else
    {
        pHotKeyThread = pWnd->head.pti;
    }

    /* Check for existing hotkey */
    if (IsHotKey(fsModifiers, vk))
    {
        EngSetLastError(ERROR_HOTKEY_ALREADY_REGISTERED);
        WARN("Hotkey already exists\n");
        return FALSE;
    }

    /* Create new hotkey */
    pHotKey = ExAllocatePoolWithTag(PagedPool, sizeof(HOT_KEY), USERTAG_HOTKEY);
    if (pHotKey == NULL)
    {
        EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return FALSE;
    }

    pHotKey->pti = pHotKeyThread;
    pHotKey->pWnd = pWnd;
    pHotKey->fsModifiers = fsModifiers;
    pHotKey->vk = vk;
    pHotKey->id = id;

    /* Insert hotkey to the global list */
    pHotKey->pNext = gphkFirst;
    gphkFirst = pHotKey;

    return TRUE;
}

BOOL FASTCALL
UserUnregisterHotKey(PWND pWnd, int id)
{
    PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst;
    BOOL bRet = FALSE;

    while (pHotKey)
    {
        /* Save next ptr for later use */
        phkNext = pHotKey->pNext;

        /* Should we delete this hotkey? */
        if (pHotKey->pWnd == pWnd && pHotKey->id == id)
        {
            /* Update next ptr for previous hotkey and free memory */
            *pLink = phkNext;
            ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY);

            bRet = TRUE;
        }
        else /* This hotkey will stay, use its next ptr */
            pLink = &pHotKey->pNext;

        /* Move to the next entry */
        pHotKey = phkNext;
    }
    return bRet;
}


/* SYSCALLS *****************************************************************/


BOOL APIENTRY
NtUserRegisterHotKey(HWND hWnd,
                     int id,
                     UINT fsModifiers,
                     UINT vk)
{
    PHOT_KEY pHotKey;
    PWND pWnd = NULL;
    PTHREADINFO pHotKeyThread;
    BOOL bRet = FALSE;

    TRACE("Enter NtUserRegisterHotKey\n");

    if (fsModifiers & ~(MOD_ALT|MOD_CONTROL|MOD_SHIFT|MOD_WIN)) // FIXME: Does Win2k3 support MOD_NOREPEAT?
    {
        WARN("Invalid modifiers: %x\n", fsModifiers);
        EngSetLastError(ERROR_INVALID_FLAGS);
        return 0;
    }

    UserEnterExclusive();

    /* Find hotkey thread */
    if (hWnd == NULL)
    {
        pHotKeyThread = gptiCurrent;
    }
    else
    {
        pWnd = UserGetWindowObject(hWnd);
        if (!pWnd)
            goto cleanup;

        pHotKeyThread = pWnd->head.pti;

        /* Fix wine msg "Window on another thread" test_hotkey */
        if (pWnd->head.pti != gptiCurrent)
        {
           EngSetLastError(ERROR_WINDOW_OF_OTHER_THREAD);
           WARN("Must be from the same Thread.\n");
           goto cleanup;
        }
    }

    /* Check for existing hotkey */
    if (IsHotKey(fsModifiers, vk))
    {
        EngSetLastError(ERROR_HOTKEY_ALREADY_REGISTERED);
        WARN("Hotkey already exists\n");
        goto cleanup;
    }

    /* Create new hotkey */
    pHotKey = ExAllocatePoolWithTag(PagedPool, sizeof(HOT_KEY), USERTAG_HOTKEY);
    if (pHotKey == NULL)
    {
        EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
        goto cleanup;
    }

    pHotKey->pti = pHotKeyThread;
    pHotKey->pWnd = pWnd;
    pHotKey->fsModifiers = fsModifiers;
    pHotKey->vk = vk;
    pHotKey->id = id;

    /* Insert hotkey to the global list */
    pHotKey->pNext = gphkFirst;
    gphkFirst = pHotKey;

    bRet = TRUE;

cleanup:
    TRACE("Leave NtUserRegisterHotKey, ret=%i\n", bRet);
    UserLeave();
    return bRet;
}


BOOL APIENTRY
NtUserUnregisterHotKey(HWND hWnd, int id)
{
    PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst;
    BOOL bRet = FALSE;
    PWND pWnd = NULL;

    TRACE("Enter NtUserUnregisterHotKey\n");
    UserEnterExclusive();

    /* Fail if given window is invalid */
    if (hWnd && !(pWnd = UserGetWindowObject(hWnd)))
        goto cleanup;

    while (pHotKey)
    {
        /* Save next ptr for later use */
        phkNext = pHotKey->pNext;

        /* Should we delete this hotkey? */
        if (pHotKey->pWnd == pWnd && pHotKey->id == id)
        {
            /* Update next ptr for previous hotkey and free memory */
            *pLink = phkNext;
            ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY);

            bRet = TRUE;
        }
        else /* This hotkey will stay, use its next ptr */
            pLink = &pHotKey->pNext;

        /* Move to the next entry */
        pHotKey = phkNext;
    }

cleanup:
    TRACE("Leave NtUserUnregisterHotKey, ret=%i\n", bRet);
    UserLeave();
    return bRet;
}

/* EOF */
