/*
 * Copyright 2004-2020 Sandboxie Holdings, LLC 
 * Copyright 2020 David Xanatos, xanasoft.com
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

//---------------------------------------------------------------------------
// GUI Services
//---------------------------------------------------------------------------

#include "dll.h"

#include "gui_p.h"
#include "core/svc/GuiWire.h"


//---------------------------------------------------------------------------
// Functions
//---------------------------------------------------------------------------


static HWND Gui_GetWindow(HWND hWnd, UINT uCmd);

static HWND Gui_GetParent(HWND hWnd);

static HWND Gui_SetParent(HWND hWndChild, HWND hWndNewParent);

static BOOL Gui_SwapMouseButton(BOOL fSwap);

static BOOL Gui_SetDoubleClickTime(UINT uInterval);

static BOOL Gui_ClipCursor(const RECT *lpRect);

static BOOLEAN Gui_GrantHandle(ULONG_PTR handle);

static HCURSOR Gui_SetCursor(HCURSOR hCursor);

static BOOL Gui_GetIconInfo(HICON hIcon, PICONINFO piconinfo);

static BOOL Gui_SetCursorPos(int x, int y);

static BOOL Gui_SetForegroundWindow(HWND hWnd);

static HMONITOR Gui_MonitorFromWindow(HWND hWnd, DWORD dwFlags);

static BOOL Gui_BlockInput(BOOL fBlockIt);

static UINT Gui_SendInput(ULONG nInputs, LPINPUT pInputs, ULONG cbInput);

static HDESK Gui_OpenInputDesktop(
    DWORD dwFlags, BOOL fInherit, ACCESS_MASK dwDesiredAccess);

static BOOL Gui_GetUserObjectInformationW(
    HANDLE hObj, int nIndex,
    void *pvInfo, DWORD nLength, DWORD *lpnLengthNeeded);

static BOOL Gui_OpenClipboard(HWND hwnd);

static BOOL Gui_CloseClipboard(void);

static HANDLE Gui_GetClipboardData(UINT uFormat);

static void Gui_GetClipboardData_BMP(void *buf, ULONG fmt);

static void Gui_GetClipboardData_MF(void *buf, ULONG sz, ULONG fmt);

static void Gui_GetClipboardData_EnhMF(void *buf, ULONG sz, ULONG fmt);

static LONG Gui_ChangeDisplaySettingsExA(
    void *lpszDeviceName, void *lpDevMode, HWND hwnd,
    DWORD dwflags, void *lParam);

static LONG Gui_ChangeDisplaySettingsExW(
    void *lpszDeviceName, void *lpDevMode, HWND hwnd,
    DWORD dwflags, void *lParam);


static LONG Gui_GetRawInputDeviceInfoA(
    _In_opt_ HANDLE hDevice, _In_ UINT uiCommand,
    _Inout_ LPVOID pData, _Inout_ PUINT pcbSize);

static LONG Gui_GetRawInputDeviceInfoW(
    _In_opt_ HANDLE hDevice, _In_ UINT uiCommand,
    _Inout_ LPVOID pData, _Inout_ PUINT pcbSize);

//---------------------------------------------------------------------------


typedef BOOL (*P_GetUserObjectInformationW)(
    HANDLE hObj, int nIndex,
    void *pvInfo, DWORD nLength, DWORD *lpnLengthNeeded);

typedef HMETAFILE (*P_SetMetaFileBitsEx)(
    UINT nSize, const BYTE *lpData);

typedef HENHMETAFILE (*P_SetEnhMetaFileBits)(
    UINT nSize, const BYTE *lpData);

typedef HBITMAP (*P_CreateCompatibleBitmap)( 
    HDC hdc, int cx, int cy);

typedef int (*P_SetDIBits)
    (HDC hdc, HBITMAP hbm, UINT start, UINT cLines, CONST VOID *lpBits, CONST BITMAPINFO * lpbmi, UINT ColorUse);

typedef HDC (*P_GetDC)(
    HWND hWnd);

typedef int (*P_ReleaseDC)(
    HWND hWnd,
    HDC hDC);

static P_GetUserObjectInformationW __sys_GetUserObjectInformationW = NULL;

#ifndef _DPI_AWARENESS_CONTEXTS_
struct DPI_AWARENESS_CONTEXT__ { int unused; };
typedef DPI_AWARENESS_CONTEXT__ *DPI_AWARENESS_CONTEXT;
#endif

typedef DPI_AWARENESS_CONTEXT (WINAPI *P_GetThreadDpiAwarenessContext)(
    VOID);

static P_GetThreadDpiAwarenessContext __sys_GetThreadDpiAwarenessContext = NULL;


//---------------------------------------------------------------------------
// Variables
//---------------------------------------------------------------------------


static BOOLEAN Gui_ClipCursorActive = FALSE;

static HWND  Gui_OpenClipboard_hwnd = NULL;
static ULONG Gui_OpenClipboard_tid  = 0;
static ULONG Gui_OpenClipboard_seq  = -1;

static HANDLE Gui_DummyInputDesktopHandle = NULL;


//---------------------------------------------------------------------------
// Gui_InitMisc
//---------------------------------------------------------------------------


_FX BOOLEAN Gui_InitMisc(HMODULE module)
{
    if (! Gui_OpenAllWinClasses) {

        
        SBIEDLL_HOOK_GUI(SetParent);
        if (Gui_UseProxyService) {
            SBIEDLL_HOOK_GUI(GetWindow);
            SBIEDLL_HOOK_GUI(GetParent);
            SBIEDLL_HOOK_GUI(SetForegroundWindow);
            SBIEDLL_HOOK_GUI(MonitorFromWindow);
        
            SBIEDLL_HOOK_GUI(SetCursor);
            SBIEDLL_HOOK_GUI(GetIconInfo);
            SBIEDLL_HOOK_GUI(SetCursorPos);
            SBIEDLL_HOOK_GUI(ClipCursor);
        }
        SBIEDLL_HOOK_GUI(SwapMouseButton);
        SBIEDLL_HOOK_GUI(SetDoubleClickTime);

        if (Dll_OsBuild >= 6000) {

            //
            // hook BlockInput and SendInput on Windows Vista because UIPI
            // is disabled if UAC is disabled.  if UAC is enabled, kernel
            // side UIPI will also protect these two APIs.  see also Gui_Init
            // in core/drv/gui.c and PostThreadMessage in core/dll/guimsg.c
            //

            SBIEDLL_HOOK_GUI(BlockInput);
            SBIEDLL_HOOK_GUI(SendInput);
        }
    }

	if (!Gui_UseProxyService)
		return TRUE;

    SBIEDLL_HOOK_GUI(OpenClipboard);
    SBIEDLL_HOOK_GUI(CloseClipboard);
    SBIEDLL_HOOK_GUI(GetClipboardData);

    //
    // Chinese instant messenger QQ.exe (aka TM.exe) uses OpenInputDesktop,
    // GetThreadDesktop and GetUserObjectInformation to determine if the
    // desktop is locked, and if OpenInputDesktop fails, it assumes lock.
    // Fortunately it is enough to hook just OpenInputDesktop to fix this
    //
    // Google Chrome also uses OpenInputDesktop and GetUserObjectInformation
    // to check if the desktop is locked, and other programs might as well
    //

    if (1) {

        typedef HDESK (*P_OpenInputDesktop)(
            DWORD dwFlags, BOOL fInherit, ACCESS_MASK dwDesiredAccess);

        P_OpenInputDesktop __sys_OpenInputDesktop = (P_OpenInputDesktop)
            Ldr_GetProcAddrNew(DllName_user32, L"OpenInputDesktop","OpenInputDesktop");

        __sys_GetUserObjectInformationW = (P_GetUserObjectInformationW)
            Ldr_GetProcAddrNew(DllName_user32, L"GetUserObjectInformationW","GetUserObjectInformationW");

        if (__sys_OpenInputDesktop) {
            SBIEDLL_HOOK_GUI(OpenInputDesktop);
        }

        if (__sys_GetUserObjectInformationW) {
            SBIEDLL_HOOK_GUI(GetUserObjectInformationW);
        }
    }

    //
    // ChangeDisplaySettingsEx on Windows 8
    //

    if (Dll_OsBuild >= 8400) {
        P_ChangeDisplaySettingsEx __sys_ChangeDisplaySettingsExA =
            Ldr_GetProcAddrNew(DllName_user32, L"ChangeDisplaySettingsExA","ChangeDisplaySettingsExA");
        P_ChangeDisplaySettingsEx __sys_ChangeDisplaySettingsExW =
            Ldr_GetProcAddrNew(DllName_user32, L"ChangeDisplaySettingsExW","ChangeDisplaySettingsExW");
        SBIEDLL_HOOK_GUI(ChangeDisplaySettingsExA);
        SBIEDLL_HOOK_GUI(ChangeDisplaySettingsExW);
    }

    SBIEDLL_HOOK_GUI(GetRawInputDeviceInfoA);
    SBIEDLL_HOOK_GUI(GetRawInputDeviceInfoW);

	__sys_GetThreadDpiAwarenessContext = (P_GetThreadDpiAwarenessContext)
		Ldr_GetProcAddrNew(DllName_user32, L"GetThreadDpiAwarenessContext","GetThreadDpiAwarenessContext");

    return TRUE;
}


//---------------------------------------------------------------------------
// Gui_GetWindow
//---------------------------------------------------------------------------


_FX HWND Gui_GetWindow(HWND hWnd, UINT uCmd)
{
    if (uCmd < 0x10)
        return Gui_GetWindowFromProxy(uCmd, hWnd);
    else
        return NULL;
}


//---------------------------------------------------------------------------
// Gui_GetParent
//---------------------------------------------------------------------------


_FX HWND Gui_GetParent(HWND hWnd)
{
    if (__sys_IsWindow(hWnd) || (! hWnd))
        return __sys_GetParent(hWnd);
    else
        return Gui_GetWindowFromProxy('prnt', hWnd);
}


//---------------------------------------------------------------------------
// Gui_SetParent
//---------------------------------------------------------------------------


_FX HWND Gui_SetParent(HWND hWndChild, HWND hWndNewParent)
{
    //
    // fail the request (with a warning) if either the child or the
    // parent windows are not accessible to this process.  but note that
    // we make a special exception of converting the desktop hwnd to NULL
    //

    if (! Gui_IsWindowAccessible(hWndChild)) {
        SetLastError(ERROR_INVALID_WINDOW_HANDLE);
        return NULL;
    }

    if (hWndNewParent && (! Gui_IsWindowAccessible(hWndNewParent))) {
        if (hWndNewParent == __sys_GetDesktopWindow())
            hWndNewParent = NULL;
        else {
            //SbieApi_Log(2205, L"SetParent");
            SetLastError(ERROR_INVALID_WINDOW_HANDLE);
            return NULL;
        }
    }

    return __sys_SetParent(hWndChild, hWndNewParent);
}


//---------------------------------------------------------------------------
// Gui_ClipCursor
//---------------------------------------------------------------------------


_FX BOOL Gui_ClipCursor(const RECT *lpRect)
{
    GUI_CLIP_CURSOR_REQ req;
    void *rpl;

    req.msgid = GUI_CLIP_CURSOR;
    if (lpRect) {
        req.have_rect = TRUE;
        memcpy(&req.rect, lpRect, sizeof(req.rect));
        Gui_ClipCursorActive = TRUE;
    } else {
        req.have_rect = FALSE;
        memzero(&req.rect, sizeof(req.rect));
        Gui_ClipCursorActive = FALSE;
    }
    req.dpi_awareness_ctx = __sys_GetThreadDpiAwarenessContext ? (LONG64)(LONG_PTR)__sys_GetThreadDpiAwarenessContext() : 0;

    rpl = Gui_CallProxy(&req, sizeof(req), sizeof(ULONG));
    if (rpl) {
        Dll_Free(rpl);
        return TRUE;
    } else {
        SetLastError(ERROR_ACCESS_DENIED);
        return FALSE;
    }
}


//---------------------------------------------------------------------------
// Gui_ResetClipCursor
//---------------------------------------------------------------------------


_FX void Gui_ResetClipCursor(void)
{
    //
    // programs that set a clip cursor (e.g. full screen games) tend to
    // remove the clip on alt-tab switch, but do not remove the clip when
    // terminating.  and the clip remains in effect probably because the
    // sandboxed process does not have WINSTA_WRITEATTRIBUTES access.
    // to work around this, we have DllMain call this function on exit.
    //

    if (Gui_ClipCursorActive)
        Gui_ClipCursor(NULL);
}


//---------------------------------------------------------------------------
// Gui_SwapMouseButton
//---------------------------------------------------------------------------


_FX BOOL Gui_SwapMouseButton(BOOL fSwap)
{
    SetLastError(ERROR_ACCESS_DENIED);
    return FALSE;
}


//---------------------------------------------------------------------------
// Gui_SetDoubleClickTime
//---------------------------------------------------------------------------


_FX BOOL Gui_SetDoubleClickTime(UINT uInterval)
{
    SetLastError(ERROR_ACCESS_DENIED);
    return FALSE;
}


//---------------------------------------------------------------------------
// Gui_GrantHandle
//---------------------------------------------------------------------------


_FX BOOLEAN Gui_GrantHandle(ULONG_PTR handle)
{
    GUI_GRANT_HANDLE_REQ req;
    void *rpl;

    if (! handle)
        return FALSE;

    req.msgid = GUI_GRANT_HANDLE;
    req.handle_type = 1;        // bitmap/cursor/icon
    req.handle_value = (ULONG)handle;

    rpl = Gui_CallProxy(&req, sizeof(req), sizeof(ULONG));
    if (rpl) {
        Dll_Free(rpl);
        return TRUE;

    } else {
        SetLastError(ERROR_INVALID_CURSOR_HANDLE);
        return FALSE;
    }
}


//---------------------------------------------------------------------------
// Gui_SetCursor
//---------------------------------------------------------------------------


_FX HCURSOR Gui_SetCursor(HCURSOR hCursor)
{
    HCURSOR hCursorRet = __sys_SetCursor(hCursor);
    if ((! hCursorRet) && (GetLastError() == ERROR_INVALID_CURSOR_HANDLE)) {

        //
        // the cursor handle is probably not yet accessible to our
        // job object so ask SbieSvc GUI Proxy server to grant us access
        //

        if (Gui_GrantHandle((ULONG_PTR)hCursor)) {

            hCursorRet = __sys_SetCursor(hCursor);
        }
    }

    return hCursorRet;
}


//---------------------------------------------------------------------------
// Gui_GetIconInfo
//---------------------------------------------------------------------------


_FX BOOL Gui_GetIconInfo(HICON hIcon, PICONINFO piconinfo)
{
    BOOL ok = __sys_GetIconInfo(hIcon, piconinfo);
    if ((! ok) && (GetLastError() == ERROR_INVALID_CURSOR_HANDLE)) {

        //
        // the icon handle is probably not yet accessible to our
        // job object so ask SbieSvc GUI Proxy server to grant us access
        //

        if (Gui_GrantHandle((ULONG_PTR)hIcon)) {

            ok = __sys_GetIconInfo(hIcon, piconinfo);
        }
    }

    return ok;
}


//---------------------------------------------------------------------------
// Gui_SetCursorPos
//---------------------------------------------------------------------------


_FX BOOL Gui_SetCursorPos(int x, int y)
{
    GUI_SET_CURSOR_POS_REQ req;
    GUI_SET_CURSOR_POS_RPL *rpl;
    ULONG error;
    BOOL retval;

    req.msgid = GUI_SET_CURSOR_POS;
    req.error = GetLastError();
    req.x = x;
    req.y = y;
    req.dpi_awareness_ctx = __sys_GetThreadDpiAwarenessContext ? (LONG64)(LONG_PTR)__sys_GetThreadDpiAwarenessContext() : 0;
    rpl = Gui_CallProxyEx(&req, sizeof(req), sizeof(ULONG), TRUE);
    if (rpl) {
        retval = rpl->retval;
        error = rpl->error;
        Dll_Free(rpl);
    } else {
        error = ERROR_ACCESS_DENIED;
        retval = FALSE;
    }
    SetLastError(error);
    return retval;
}


//---------------------------------------------------------------------------
// Gui_SetForegroundWindow
//---------------------------------------------------------------------------


_FX BOOL Gui_SetForegroundWindow(HWND hWnd)
{
    GUI_SET_FOREGROUND_WINDOW_REQ req;
    void *rpl;

    if (__sys_IsWindow(hWnd) || (! hWnd)) {
        // window is in the same sandbox (or is NULL), no need for GUI Proxy
        return __sys_SetForegroundWindow(hWnd);
    }

    if (__sys_SetForegroundWindow(hWnd))
        return TRUE;

    Gui_AllowSetForegroundWindow();

    req.msgid = GUI_SET_FOREGROUND_WINDOW;
    req.hwnd = (ULONG)(ULONG_PTR)hWnd;
    rpl = Gui_CallProxyEx(&req, sizeof(req), sizeof(ULONG), TRUE);
    if (rpl) {
        Dll_Free(rpl);
        return TRUE;
    } else {
        SetLastError(ERROR_INVALID_WINDOW_HANDLE);
        return FALSE;
    }
}


//---------------------------------------------------------------------------
// Gui_MonitorFromWindow
//---------------------------------------------------------------------------


_FX HMONITOR Gui_MonitorFromWindow(HWND hWnd, DWORD dwFlags)
{
    ULONG err = GetLastError();
    HMONITOR result = __sys_MonitorFromWindow(hWnd, dwFlags);
    if (! result) {

        GUI_MONITOR_FROM_WINDOW_REQ req;
        GUI_MONITOR_FROM_WINDOW_RPL *rpl;

        req.msgid = GUI_MONITOR_FROM_WINDOW;
        req.error = err;
        req.hwnd = (ULONG)(ULONG_PTR)hWnd;
        req.flags = dwFlags;

        rpl = Gui_CallProxy(&req, sizeof(req), sizeof(*rpl));
        if (! rpl)
            return FALSE;

        err = rpl->error;
        result = (HMONITOR)(LONG_PTR)(LONG)rpl->retval;
        Dll_Free(rpl);

        SetLastError(err);
    }

    return result;
}


//---------------------------------------------------------------------------
// Gui_BlockInput
//---------------------------------------------------------------------------


_FX BOOL Gui_BlockInput(BOOL fBlockIt)
{
    if (fBlockIt)
        return 0;
    else
        return __sys_BlockInput(fBlockIt);
}


//---------------------------------------------------------------------------
// Gui_SendInput
//---------------------------------------------------------------------------


_FX UINT Gui_SendInput(ULONG nInputs, LPINPUT pInputs, ULONG cbInput)
{
    //
    // we are going to pass the input to the system one INPUT structure
    // at a time, so we can check for change in the foreground window
    //

    ULONG retval = 0;
    while (nInputs && nInputs < 16) {

        //
        // if the foreground window is not sandboxed, stop
        //

        HWND hwnd = __sys_GetForegroundWindow();
        if (! hwnd)
            break;
        if (! Gui_IsSameBox(hwnd, NULL, NULL))
            break;

        //
        // otherwise pass one INPUT structure to the system, then advance
        // to the next INPUT structure.  note that we must get a return
        // of exactly 1
        //

        if (__sys_SendInput(1, pInputs, cbInput) != 1)
            break;

        ++retval;
        --nInputs;
        pInputs = (LPINPUT)((UCHAR *)pInputs + cbInput);
    }

    return retval;
}


//---------------------------------------------------------------------------
// Gui_OpenInputDesktop
//---------------------------------------------------------------------------


_FX HDESK Gui_OpenInputDesktop(
    DWORD dwFlags, BOOL fInherit, ACCESS_MASK dwDesiredAccess)
{
    if (! Gui_DummyInputDesktopHandle)
        Gui_DummyInputDesktopHandle = CreateEvent(NULL, FALSE, FALSE, NULL);
    return Gui_DummyInputDesktopHandle;
}


//---------------------------------------------------------------------------
// Gui_GetUserObjectInformationW
//---------------------------------------------------------------------------


_FX BOOL Gui_GetUserObjectInformationW(
    HANDLE hObj, int nIndex,
    void *pvInfo, DWORD nLength, DWORD *lpnLengthNeeded)
{
    if (Gui_DummyInputDesktopHandle && hObj == Gui_DummyInputDesktopHandle && nIndex == UOI_NAME) {

        ULONG Default_len = (7 + 1) * sizeof(WCHAR);
        if (lpnLengthNeeded)
            *lpnLengthNeeded = Default_len;
        if (pvInfo && nLength >= Default_len) {
            memcpy(pvInfo, L"Default", Default_len);
            return TRUE;
        }
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    return __sys_GetUserObjectInformationW(
                        hObj, nIndex, pvInfo, nLength, lpnLengthNeeded);
}


//---------------------------------------------------------------------------
//
// Clipboard support functions
//
// The sandboxed process is running in a restricting job, so it can't
// get clipboard data that was copied by a process outside the sandbox.
//
// a second problem on UIPI systems is that an integrity level is given
// to each clipboard data item, so a process outside the sandbox copies
// data to the clipboard with integrity level zero.  this prevents a
// process outside the sandbox from pasting (perhaps a bug in win32k).
//
// to work around both issues we use the SbieSvc GUI Proxy.
//
//---------------------------------------------------------------------------


//---------------------------------------------------------------------------
// Gui_OpenClipboard
//---------------------------------------------------------------------------


_FX BOOL Gui_OpenClipboard(HWND hwnd)
{
    BOOL ok = __sys_OpenClipboard(hwnd);
    if (ok) {
        Gui_OpenClipboard_hwnd = hwnd;
        Gui_OpenClipboard_tid = GetCurrentThreadId();
        Gui_OpenClipboard_seq = __sys_GetClipboardSequenceNumber();
    }
    return ok;
}


//---------------------------------------------------------------------------
// Gui_CloseClipboard
//---------------------------------------------------------------------------


_FX BOOL Gui_CloseClipboard(void)
{
    //
    // this request is used on Windows Vista and later where we need to
    // adjust the internal integrity level numbers stored in the clipboard.
    // see CloseClipboardSlave in core/svc/GuiServer.cpp,
    // and also see core/drv/gui.c
    //
    // there is a potential problem if there is a clipboard viewer program
    // (registered via SetClipboardViewer or AddClipboardFormatListener),
    // in which case we have a race condition:  do we get to fix the
    // clipboard before the viewer gets the clipboard notification?
    // SbieSvc is an "above normal" priority process, so we rely on that.
    //

    BOOL ok = __sys_CloseClipboard();
    ULONG err = GetLastError();

    if (ok && Dll_OsBuild >= 6000) {

        ULONG new_seq = __sys_GetClipboardSequenceNumber();
        if (new_seq != Gui_OpenClipboard_seq) {

            ULONG req = GUI_CLOSE_CLIPBOARD;

            // While calling the Service we want to suppress
            // any informational msg boxes that the application
            // my through up. This is because we're bringing
            // forward the rendering of "Delay rendered"
            // clipboard types (e.g. the Enhanced Metafile type
            // from Office applications).
            // This causes some issues because sometimes
            // the application (e.g. Excel) will pop up message
            // box where it normally wouldn't do so. There
            // is also no option to turn off that informational
            // message. 
            THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
            ULONG * rpl = NULL;

            if (TlsData)
                TlsData->gui_should_suppress_msgbox = TRUE;

            rpl = Gui_CallProxyEx(
                &req, sizeof(ULONG), sizeof(ULONG), TRUE);
            if (TlsData)
                TlsData->gui_should_suppress_msgbox = FALSE;

            if (rpl)
                Dll_Free(rpl);

            Gui_OpenClipboard_seq = new_seq;
        }
    }

    if (ok) {
        Gui_OpenClipboard_hwnd = NULL;
        Gui_OpenClipboard_tid  = 0;
    }

    SetLastError(err);
    return ok;
}


//---------------------------------------------------------------------------
// Gui_GetClipboardData
//---------------------------------------------------------------------------


_FX HANDLE Gui_GetClipboardData(UINT uFormat)
{
    static HANDLE *_hGlobalClipData = NULL;
    HANDLE hGlobalRet;
    HWND OpenClipboardHwnd;
    GUI_GET_CLIPBOARD_DATA_REQ req;
    GUI_GET_CLIPBOARD_DATA_RPL *rpl;
    ULONG error;
    ULONG retry;

    //
    // first we try the system GetClipboardData.  this always fails with
    // ERROR_ACCESS_DENIED if the process is running in a job that has a
    // UIRestriction which prevents clipboard access.  it fails with
    // ERROR_INVALID_HANDLE if the job has UIRestriction which prevents
    // access to handles outside the job, and the clipboard data belongs
    // to a process outside the job
    //
    // in either case, we go to SbieSvc GUI Proxy Server to ask for the
    // clipboard data
    //

    hGlobalRet = __sys_GetClipboardData(uFormat);
    if (hGlobalRet)
        return hGlobalRet;
    error = GetLastError();
    if (error != ERROR_ACCESS_DENIED && error != ERROR_INVALID_HANDLE)
        return hGlobalRet;

    //
    // we have to close the clipboard before another process can access it
    // (the other process being the SbieSvc GuiProxy process)
    //
    // if we can't close the clipboard (which means the thread has not
    // actually opened the clipboard), then abort
    //

    OpenClipboardHwnd = __sys_GetOpenClipboardWindow();

    if (OpenClipboardHwnd != Gui_OpenClipboard_hwnd ||
                    GetCurrentThreadId() != Gui_OpenClipboard_tid) {
        SetLastError(ERROR_ACCESS_DENIED);
        return NULL;
    }

    if (! __sys_CloseClipboard()) {
        SetLastError(ERROR_ACCESS_DENIED);
        return NULL;
    }

    //
    // ask SbieSvc to call GetClipboardData for us
    //

    req.msgid = GUI_GET_CLIPBOARD_DATA;
    req.format = uFormat;
    rpl = Gui_CallProxyEx(&req, sizeof(req), sizeof(*rpl), TRUE);
    if (! rpl)
        error = GetLastError();
    else {

        if (! rpl->result)
            error = rpl->error;
        else {

            //
            // if the request was successful, we were given a handle to a
            // section which contains the data from the clipboard, but we
            // have to convert this into an HGLOBAL
            //

            void *src = MapViewOfFileEx(
                                (HANDLE)(ULONG_PTR)rpl->section_handle,
                                FILE_MAP_READ, 0, 0, 0, NULL);
            if (! src)
                error = GetLastError();
            else {

                HGLOBAL hGlobal =
                    GlobalAlloc(GMEM_FIXED, (ULONG_PTR)rpl->section_length);
                if (hGlobal) {

                    void *dst = GlobalLock(hGlobal);
                    if (! dst)
                        error = GetLastError();
                    else {

                        memcpy(dst, src, (ULONG_PTR)rpl->section_length);

                        if (uFormat == CF_BITMAP)// || uFormat == CF_DIB)
                            Gui_GetClipboardData_BMP(dst, uFormat);

                        if (uFormat == CF_ENHMETAFILE) {
                            Gui_GetClipboardData_EnhMF(
                                dst, (ULONG)(ULONG_PTR)rpl->section_length,
                                uFormat);
                        }

                        if (uFormat == CF_METAFILEPICT) {
                            Gui_GetClipboardData_MF(
                              dst, (ULONG)(ULONG_PTR)rpl->section_length,
                              uFormat);
                        }

                        GlobalUnlock(hGlobal);

                        hGlobalRet = hGlobal;
                        error = ERROR_SUCCESS;
                    }
                }

                UnmapViewOfFile(src);
            }

            CloseHandle((HANDLE)(ULONG_PTR)rpl->section_handle);
        }

        Dll_Free(rpl);
    }

    //
    // applications aren't supposed to free clipboard HGLOBALs, so we have
    // to free the HGLOBAL we returned last time, and keep the HGLOBAL that
    // we return now, to free it on the next call to this function
    //

    if (hGlobalRet) {

        if (_hGlobalClipData)
        {   
            // reuse the previous returned data if it is the same
            BOOL    bReuse = FALSE;
            SIZE_T  nSizeOld = GlobalSize(_hGlobalClipData);
            SIZE_T  nSizeNew = GlobalSize(hGlobalRet);
            
            if (nSizeOld && nSizeOld == nSizeNew)
            {
                if (memcmp(hGlobalRet, _hGlobalClipData, nSizeOld) == 0)
                {
                    bReuse = TRUE;
                }
            }

            if (bReuse)
            {
                GlobalFree(hGlobalRet);
                hGlobalRet = _hGlobalClipData;
            }
            else
            {
                GlobalFree(_hGlobalClipData);
            }
        }
        _hGlobalClipData = hGlobalRet;

        //
        // if this is a bitmap paste, Gui_GetClipboardData_BMP should have
        // stored the HBITMAP handle at the top of the global data buffer
        //

        if (uFormat == CF_BITMAP) { // || uFormat == CF_DIB) {

            void *dst = GlobalLock(_hGlobalClipData);
            hGlobalRet = *(HANDLE *)dst;
            GlobalUnlock(_hGlobalClipData);
        }
        else if (uFormat == CF_ENHMETAFILE)
        {
            void *dst = GlobalLock(_hGlobalClipData);
            hGlobalRet = *(HANDLE *)dst;
            GlobalUnlock(_hGlobalClipData);
        }
    }

    //
    // grab ownership of the clipboard again before returning
    //

    for (retry = 0; retry < 5000; ++retry) {
        BOOL ok = Gui_OpenClipboard(OpenClipboardHwnd);
        if (ok)
            break;
        Sleep(1);
    }

    SetLastError(error);

    return hGlobalRet;
}


//---------------------------------------------------------------------------
// Gui_GetClipboardData_BMP
//---------------------------------------------------------------------------


_FX void Gui_GetClipboardData_BMP(void *buf, ULONG fmt)
{
    //
    // GetClipboardData(CF_BITMAP) returns an HBITMAP that is valid in the
    // context of SbieSvc.  GuiServer::GetClipboardBitmapSlave already sent
    // us the bitmap data in the reply buffer, we just need to turn it into
    // a local HBITMAP handle
    //

    P_CreateCompatibleBitmap    CreateCompatibleBitmap = NULL;
    P_SetDIBits                 SetDIBits = NULL;
    P_GetDC                     GetDC = NULL;
    P_ReleaseDC                 ReleaseDC = NULL;

    HBITMAP hBitmap = NULL;

    if (fmt != CF_BITMAP)
        SbieApi_Log(2205, L"Clipboard Bitmap (fmt %04X)", fmt);

    else {
        BITMAPINFO *pBitmapInfo = (BITMAPINFO *)buf;

        CreateCompatibleBitmap  = Ldr_GetProcAddrNew(DllName_gdi32, L"CreateCompatibleBitmap", "CreateCompatibleBitmap");
        SetDIBits               = Ldr_GetProcAddrNew(DllName_gdi32, L"SetDIBits", "SetDIBits");
        GetDC                   = Ldr_GetProcAddrNew(DllName_user32, L"GetDC", "GetDC");
        ReleaseDC               = Ldr_GetProcAddrNew(DllName_user32, L"ReleaseDC", "ReleaseDC");

        if (CreateCompatibleBitmap && GetDC && SetDIBits && ReleaseDC)
        {
            HDC hdc = GetDC(NULL);

            if(hdc)
            {
                hBitmap = CreateCompatibleBitmap(hdc, pBitmapInfo->bmiHeader.biWidth, pBitmapInfo->bmiHeader.biHeight);

                if (hBitmap)
                {
                    SetDIBits(hdc, hBitmap, 0, pBitmapInfo->bmiHeader.biHeight, (UCHAR *)buf + 128, pBitmapInfo, DIB_RGB_COLORS);
                }

                ReleaseDC(NULL, hdc);
            }
        }
    }
    

    *(HANDLE *)buf = hBitmap;
}


//---------------------------------------------------------------------------
// Gui_GetClipboardData_EnhMF
//---------------------------------------------------------------------------


_FX void Gui_GetClipboardData_EnhMF(void *buf, ULONG sz, ULONG fmt)
{
    //
    // GetClipboardData(CF_ENHMETAFILE) returns an HENHMETAFILE object.
    //
    P_SetEnhMetaFileBits SetEnhMetaFileBits;
    HENHMETAFILE hEnhMetaFile;

    if (fmt != CF_ENHMETAFILE) {
        SbieApi_Log(2205, L"Clipboard Enhanced MetaFile (fmt %04X sz %d)", fmt, sz);
        return;
    }

    SetEnhMetaFileBits = Ldr_GetProcAddrNew(DllName_gdi32, L"SetEnhMetaFileBits","SetEnhMetaFileBits");
    if (! SetEnhMetaFileBits)
        return;

    hEnhMetaFile = SetEnhMetaFileBits(sz, buf);
    *(HANDLE*)buf = hEnhMetaFile;
}


//---------------------------------------------------------------------------
// Gui_GetClipboardData_MF
//---------------------------------------------------------------------------


_FX void Gui_GetClipboardData_MF(void *buf, ULONG sz, ULONG fmt)
{
    GUI_GET_CLIPBOARD_DATA_REQ req;
    GUI_GET_CLIPBOARD_DATA_RPL *rpl;
    P_SetMetaFileBitsEx SetMetaFileBitsEx;

    //
    // GetClipboardData(CF_METAFILEPICT) returns a structure which contains
    // an HMETAFILE handle that is valid in the context of SbieSvc, so we
    // make a secondary call to SbieSvc to get the data bytes so that we can
    // create a local HMETAFILE handle
    //

    if ((fmt != CF_METAFILEPICT) || (sz != sizeof(METAFILEPICT))) {
        SbieApi_Log(2205, L"Clipboard MetaFile (fmt %04X sz %d)", fmt, sz);
        return;
    }

    SetMetaFileBitsEx = Ldr_GetProcAddrNew(DllName_gdi32, L"SetMetaFileBitsEx","SetMetaFileBitsEx");
    if (! SetMetaFileBitsEx)
        return;

    req.msgid = GUI_GET_CLIPBOARD_METAFILE;
    req.format = fmt;
    rpl = Gui_CallProxyEx(&req, sizeof(req), sizeof(*rpl), TRUE);
    if (! rpl)
        return;

    if (rpl->result) {

        //
        // if the request was successful, we were given a handle to a
        // section which contains the data from the clipboard, but we
        // have to convert this into an HGLOBAL
        //

        void *src = MapViewOfFileEx(
                            (HANDLE)(ULONG_PTR)rpl->section_handle,
                            FILE_MAP_READ, 0, 0, 0, NULL);
        if (src) {

            HGLOBAL hGlobal =
                GlobalAlloc(GMEM_FIXED, (ULONG_PTR)rpl->section_length);
            if (hGlobal) {

                void *dst = GlobalLock(hGlobal);
                if (dst) {

                    HMETAFILE hmf = SetMetaFileBitsEx(
                        (ULONG)(ULONG_PTR)rpl->section_length, src);

                    if (hmf) {

                        ((METAFILEPICT *)buf)->hMF = hmf;
                    }

                    GlobalUnlock(hGlobal);
                }
            }

            UnmapViewOfFile(src);
        }

        CloseHandle((HANDLE)(ULONG_PTR)rpl->section_handle);
    }

    Dll_Free(rpl);
}


//---------------------------------------------------------------------------
// Gui_ChangeDisplaySettingsEx_impl
//---------------------------------------------------------------------------


_FX LONG Gui_ChangeDisplaySettingsEx_impl(
    void* lpszDeviceName, void* lpDevMode, HWND hwnd,
    DWORD dwflags, void* lParam, BOOLEAN bUnicode)
{
    GUI_CHANGE_DISPLAY_SETTINGS_REQ req;
    GUI_CHANGE_DISPLAY_SETTINGS_RPL* rpl;

    if ((dwflags & ~(CDS_UNKNOWNFLAG | CDS_RESET | CDS_FULLSCREEN | CDS_TEST)) || lParam || hwnd) {
        SbieApi_Log(2205, L"ChangeDisplaySettingsEx %08X", dwflags);
        SetLastError(ERROR_ACCESS_DENIED);
        return DISP_CHANGE_FAILED;
    }

    req.msgid = GUI_CHANGE_DISPLAY_SETTINGS;
    req.flags = dwflags;
    req.unicode = bUnicode;

    if (lpszDeviceName) {
        if (bUnicode) {
            WCHAR* name = (WCHAR*)req.devname;
            ULONG len = wcslen(lpszDeviceName);
            if (len > 62)
                len = 62;
            wmemcpy(name, lpszDeviceName, len);
            name[len] = L'\0';
        }
        else {
            UCHAR* name = (UCHAR*)req.devname;
            ULONG len = strlen(lpszDeviceName);
            if (len > 62)
                len = 62;
            memcpy(name, lpszDeviceName, len);
            name[len] = L'\0';
        }
        req.have_devname = TRUE;
    }
    else {
        memzero(req.devname, sizeof(req.devname));
        req.have_devname = FALSE;
    }

    if (lpDevMode) {
        memcpy(&req.devmode, lpDevMode, bUnicode ? sizeof(DEVMODEW) : sizeof(DEVMODEA));
        req.have_devmode = TRUE;
    }
    else
        req.have_devmode = FALSE;

    rpl = Gui_CallProxy(&req, sizeof(req), sizeof(*rpl));
    if (!rpl)
        return DISP_CHANGE_FAILED;
    else {
        ULONG error = rpl->error;
        ULONG retval = rpl->retval;
        Dll_Free(rpl);
        SetLastError(error);
        return retval;
    }
}

//---------------------------------------------------------------------------
// Gui_ChangeDisplaySettingsExA
//---------------------------------------------------------------------------


_FX LONG Gui_ChangeDisplaySettingsExA(
    void *lpszDeviceName, void *lpDevMode, HWND hwnd,
    DWORD dwflags, void *lParam)
{
    return Gui_ChangeDisplaySettingsEx_impl(lpszDeviceName, lpDevMode, hwnd, dwflags, lParam, FALSE);
}


//---------------------------------------------------------------------------
// Gui_ChangeDisplaySettingsExW
//---------------------------------------------------------------------------


_FX LONG Gui_ChangeDisplaySettingsExW(
    void *lpszDeviceName, void *lpDevMode, HWND hwnd,
    DWORD dwflags, void *lParam)
{
    return Gui_ChangeDisplaySettingsEx_impl(lpszDeviceName, lpDevMode, hwnd, dwflags, lParam, TRUE);
}


//---------------------------------------------------------------------------
// Gui_GetRawInputDeviceInfo_impl
//---------------------------------------------------------------------------


_FX LONG Gui_GetRawInputDeviceInfo_impl(
    _In_opt_ HANDLE hDevice, _In_ UINT uiCommand,
    _Inout_ LPVOID pData, _Inout_ PUINT pcbSize, BOOLEAN bUnicode)
{
    GUI_GET_RAW_INPUT_DEVICE_INFO_REQ* req;
    GUI_GET_RAW_INPUT_DEVICE_INFO_RPL* rpl;

    // Note: pcbSize seems to be in tchars not in bytes!
    ULONG lenData = 0;
    if (pData && pcbSize)
        lenData = (*pcbSize) * (bUnicode ? sizeof(WCHAR) : 1);

    ULONG reqSize = sizeof(GUI_GET_RAW_INPUT_DEVICE_INFO_REQ) + lenData + 10;
    req = Dll_Alloc(reqSize);

    LPVOID reqData = (BYTE*)req + sizeof(GUI_GET_RAW_INPUT_DEVICE_INFO_REQ);

    req->msgid = GUI_GET_RAW_INPUT_DEVICE_INFO;
    req->hDevice = (ULONG64)hDevice;
    req->uiCommand = uiCommand;
    req->unicode = bUnicode;
    if (lenData) {
        memcpy(reqData, pData, lenData);
        req->hasData = TRUE;
    } else
        req->hasData = FALSE;
    req->cbSize = pcbSize ? *pcbSize : -1;

    rpl = Gui_CallProxy(req, reqSize, sizeof(*rpl));

    Dll_Free(req);

    if (!rpl)
        return -1;
    else {
        ULONG error = rpl->error;
        ULONG retval = rpl->retval;

        if (pcbSize)
            *pcbSize = rpl->cbSize;
        if (lenData) {
            LPVOID rplData = (BYTE*)rpl + sizeof(GUI_GET_RAW_INPUT_DEVICE_INFO_RPL);
            memcpy(pData, rplData, lenData);
        }

        Dll_Free(rpl);
        SetLastError(error);
        return retval;
    }
}


//---------------------------------------------------------------------------
// Gui_GetRawInputDeviceInfoA
//---------------------------------------------------------------------------


_FX LONG Gui_GetRawInputDeviceInfoA(
    _In_opt_ HANDLE hDevice, _In_ UINT uiCommand,
    _Inout_ LPVOID pData, _Inout_ PUINT pcbSize)
{
    return Gui_GetRawInputDeviceInfo_impl(hDevice, uiCommand, pData, pcbSize, FALSE);
}


//---------------------------------------------------------------------------
// Gui_GetRawInputDeviceInfoW
//---------------------------------------------------------------------------


_FX LONG Gui_GetRawInputDeviceInfoW(
    _In_opt_ HANDLE hDevice, _In_ UINT uiCommand,
    _Inout_ LPVOID pData, _Inout_ PUINT pcbSize)
{
    return Gui_GetRawInputDeviceInfo_impl(hDevice, uiCommand, pData, pcbSize, TRUE);
}


//---------------------------------------------------------------------------
//
// IMM32 - ImmAssociateContext and ImmAssociateContextEx
//
// kernel side handlers for ImmAssociateContext and ImmAssociateContextEx
// check that the specified HWND and HIMC objects are on the same desktop.
// but the default HIMC is created during WIN32K thread creation, at which
// time the thread is connected to a dummy desktop (see more about this in
// Gui_ConnectToWindowStationAndDesktop).
//
// when a thread calls these functions it is already connected to the real
// desktop, so we can easily work around this problem by creating a new
// HIMC object in the real desktop and passing it instead.
//
//---------------------------------------------------------------------------


static ULONG_PTR Gui_ImmAssociateContext(ULONG_PTR hwnd, ULONG_PTR imc);

static BOOL Gui_ImmAssociateContextEx(
    ULONG_PTR hwnd, ULONG_PTR imc, ULONG flags);

typedef ULONG_PTR (*P_ImmAssociateContext)(ULONG_PTR hwnd, ULONG_PTR imc);

typedef BOOL (*P_ImmAssociateContextEx)(
                ULONG_PTR hwnd, ULONG_PTR imc, ULONG flags);

typedef ULONG_PTR (*P_ImmCreateContext)(void);

static P_ImmAssociateContext    __sys_ImmAssociateContext   = NULL;
static P_ImmAssociateContextEx  __sys_ImmAssociateContextEx = NULL;
static P_ImmCreateContext       __sys_ImmCreateContext      = NULL;


//---------------------------------------------------------------------------
// Gui_Init_IMM32
//---------------------------------------------------------------------------


_FX BOOLEAN Gui_Init_IMM32(HMODULE module)
{
    // NoSbieDesk BEGIN
    if (Dll_CompartmentMode || SbieApi_QueryConfBool(NULL, L"NoSandboxieDesktop", FALSE))
        return TRUE;
	// NoSbieDesk END

    __sys_ImmAssociateContext = (P_ImmAssociateContext)
                GetProcAddress(module, "ImmAssociateContext");

    __sys_ImmAssociateContextEx = (P_ImmAssociateContextEx)
                GetProcAddress(module, "ImmAssociateContextEx");

    __sys_ImmCreateContext = (P_ImmCreateContext)
                GetProcAddress(module, "ImmCreateContext");

    if (__sys_ImmCreateContext) {

        if (__sys_ImmAssociateContext) {
            SBIEDLL_HOOK_GUI(ImmAssociateContext);
        }

        if (__sys_ImmAssociateContextEx) {
            SBIEDLL_HOOK_GUI(ImmAssociateContextEx);
        }
    }

    return TRUE;
}


//---------------------------------------------------------------------------
// Gui_ImmAssociateContext
//---------------------------------------------------------------------------


_FX ULONG_PTR Gui_ImmAssociateContext(ULONG_PTR hwnd, ULONG_PTR imc)
{
    ULONG LastError;
    THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);

    imc = __sys_ImmAssociateContext(hwnd, imc);
    if ((! imc) && (GetLastError() == ERROR_ACCESS_DENIED)) {

        imc = TlsData->gui_himc;
        if (! imc) {
            imc = __sys_ImmCreateContext();
            if (! imc) {
                SetLastError(ERROR_ACCESS_DENIED);
                return FALSE;
            }
            TlsData->gui_himc = imc;
        }

        imc = __sys_ImmAssociateContext(hwnd, imc);
    }

    return imc;
}


//---------------------------------------------------------------------------
// Gui_ImmAssociateContextEx
//---------------------------------------------------------------------------


_FX BOOL Gui_ImmAssociateContextEx(
    ULONG_PTR hwnd, ULONG_PTR imc, ULONG flags)
{
    ULONG LastError;
    THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);

    BOOL ok = __sys_ImmAssociateContextEx(hwnd, imc, flags);
    if ((! ok) && (GetLastError() == ERROR_ACCESS_DENIED)) {

        imc = TlsData->gui_himc;
        if (! imc) {
            imc = __sys_ImmCreateContext();
            if (! imc) {
                SetLastError(ERROR_ACCESS_DENIED);
                return FALSE;
            }
            TlsData->gui_himc = imc;
        }

        ok = __sys_ImmAssociateContextEx(hwnd, imc, 0);
    }

    return ok;
}
