[c-host] tons of windows specific fixes

This commit is contained in:
Geoffrey McRae 2019-04-10 21:07:56 +10:00
parent 3f13485ced
commit 0cac3e1c40
16 changed files with 346 additions and 59 deletions

View File

@ -1 +1 @@
a12-135-gd6805cfa0f+1 a12-142-g3f13485ced+1

View File

@ -76,6 +76,7 @@ typedef struct CaptureInterface
const char * (*getName )(); const char * (*getName )();
bool (*create )(); bool (*create )();
bool (*init )(void * pointerShape, const unsigned int pointerSize); bool (*init )(void * pointerShape, const unsigned int pointerSize);
void (*stop )();
bool (*deinit )(); bool (*deinit )();
void (*free )(); void (*free )();
unsigned int (*getMaxFrameSize)(); unsigned int (*getMaxFrameSize)();

View File

@ -47,5 +47,6 @@ typedef struct osEventHandle osEventHandle;
osEventHandle * os_createEvent(bool autoReset); osEventHandle * os_createEvent(bool autoReset);
void os_freeEvent (osEventHandle * handle); void os_freeEvent (osEventHandle * handle);
bool os_waitEvent (osEventHandle * handle, unsigned int timeout); bool os_waitEvent (osEventHandle * handle, unsigned int timeout);
bool os_waitEvents (osEventHandle * handles[], int count, bool waitAll, unsigned int timeout);
bool os_signalEvent(osEventHandle * handle); bool os_signalEvent(osEventHandle * handle);
bool os_resetEvent (osEventHandle * handle); bool os_resetEvent (osEventHandle * handle);

Binary file not shown.

View File

@ -8,11 +8,20 @@ include_directories(
add_library(platform_Windows STATIC add_library(platform_Windows STATIC
src/platform.c src/platform.c
src/windebug.c src/windebug.c
src/mousehook.c
) )
add_subdirectory("capture") add_subdirectory("capture")
FIND_PROGRAM(WINDRES_EXECUTABLE NAMES "windres.exe" DOC "windres executable")
ADD_CUSTOM_COMMAND(TARGET platform_Windows POST_BUILD
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMAND ${WINDRES_EXECUTABLE} -i resource.rc -o "${PROJECT_BINARY_DIR}/resource.o"
VERBATIM
)
target_link_libraries(platform_Windows target_link_libraries(platform_Windows
"${PROJECT_BINARY_DIR}/resource.o"
capture capture
setupapi setupapi
) )

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="hello" type="win32"/>
<description>Hello World</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View File

@ -55,7 +55,7 @@ Pointer;
struct iface struct iface
{ {
bool initialized; bool initialized;
bool reinit; bool stop;
IDXGIFactory1 * factory; IDXGIFactory1 * factory;
IDXGIAdapter1 * adapter; IDXGIAdapter1 * adapter;
IDXGIOutput * output; IDXGIOutput * output;
@ -158,7 +158,7 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
this->pointerSize = pointerSize; this->pointerSize = pointerSize;
this->pointerUsed = 0; this->pointerUsed = 0;
this->reinit = false; this->stop = false;
this->texRIndex = 0; this->texRIndex = 0;
this->texWIndex = 0; this->texWIndex = 0;
os_resetEvent(this->frameEvent); os_resetEvent(this->frameEvent);
@ -402,6 +402,13 @@ fail:
return false; return false;
} }
static void dxgi_stop()
{
this->stop = true;
os_signalEvent(this->frameEvent );
os_signalEvent(this->pointerEvent);
}
static bool dxgi_deinit() static bool dxgi_deinit()
{ {
assert(this); assert(this);
@ -497,7 +504,7 @@ static unsigned int dxgi_getMaxFrameSize()
return this->height * this->pitch; return this->height * this->pitch;
} }
inline static CaptureResult dxgi_capture_int() static CaptureResult dxgi_capture()
{ {
assert(this); assert(this);
assert(this->initialized); assert(this->initialized);
@ -641,36 +648,18 @@ inline static CaptureResult dxgi_capture_int()
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult dxgi_capture(bool * hasFrameUpdate, bool * hasPointerUpdate)
{
CaptureResult result = dxgi_capture_int(hasFrameUpdate, hasPointerUpdate);
// signal pending events if the result was any form of failure or reinit
if (result != CAPTURE_RESULT_OK && result != CAPTURE_RESULT_TIMEOUT)
{
this->reinit = true;
os_signalEvent(this->frameEvent );
os_signalEvent(this->pointerEvent);
}
return result;
}
static CaptureResult dxgi_getFrame(CaptureFrame * frame) static CaptureResult dxgi_getFrame(CaptureFrame * frame)
{ {
assert(this); assert(this);
assert(this->initialized); assert(this->initialized);
if (this->reinit)
return CAPTURE_RESULT_REINIT;
if (!os_waitEvent(this->frameEvent, TIMEOUT_INFINITE)) if (!os_waitEvent(this->frameEvent, TIMEOUT_INFINITE))
{ {
DEBUG_ERROR("Failed to wait on the frame event"); DEBUG_ERROR("Failed to wait on the frame event");
return CAPTURE_RESULT_ERROR; return CAPTURE_RESULT_ERROR;
} }
if (this->reinit) if (this->stop)
return CAPTURE_RESULT_REINIT; return CAPTURE_RESULT_REINIT;
Texture * tex = &this->texture[this->texRIndex]; Texture * tex = &this->texture[this->texRIndex];
@ -695,16 +684,13 @@ static CaptureResult dxgi_getPointer(CapturePointer * pointer)
assert(this); assert(this);
assert(this->initialized); assert(this->initialized);
if (this->reinit)
return CAPTURE_RESULT_REINIT;
if (!os_waitEvent(this->pointerEvent, TIMEOUT_INFINITE)) if (!os_waitEvent(this->pointerEvent, TIMEOUT_INFINITE))
{ {
DEBUG_ERROR("Failed to wait on the pointer event"); DEBUG_ERROR("Failed to wait on the pointer event");
return CAPTURE_RESULT_ERROR; return CAPTURE_RESULT_ERROR;
} }
if (this->reinit) if (this->stop)
return CAPTURE_RESULT_REINIT; return CAPTURE_RESULT_REINIT;
Pointer p; Pointer p;
@ -761,6 +747,7 @@ struct CaptureInterface Capture_DXGI =
.getName = dxgi_getName, .getName = dxgi_getName,
.create = dxgi_create, .create = dxgi_create,
.init = dxgi_init, .init = dxgi_init,
.stop = dxgi_stop,
.deinit = dxgi_deinit, .deinit = dxgi_deinit,
.free = dxgi_free, .free = dxgi_free,
.getMaxFrameSize = dxgi_getMaxFrameSize, .getMaxFrameSize = dxgi_getMaxFrameSize,

View File

@ -12,3 +12,7 @@ target_include_directories(capture_NVFBC
PRIVATE PRIVATE
src src
) )
target_link_libraries(capture_NVFBC
platform_Windows
)

View File

@ -19,8 +19,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h" #include "interface/capture.h"
#include "interface/platform.h" #include "interface/platform.h"
#include "debug.h" #include "windows/platform.h"
#include "windows/windebug.h" #include "windows/windebug.h"
#include "windows/mousehook.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <windows.h> #include <windows.h>
@ -30,7 +31,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
struct iface struct iface
{ {
bool reinit; bool stop;
NvFBCHandle nvfbc; NvFBCHandle nvfbc;
void * pointerShape; void * pointerShape;
@ -43,7 +44,10 @@ struct iface
NvFBCFrameGrabInfo grabInfo; NvFBCFrameGrabInfo grabInfo;
osEventHandle * frameEvent; osEventHandle * frameEvent;
HANDLE cursorEvent; osEventHandle * cursorEvents[2];
int mouseX, mouseY, mouseHotX, mouseHotY;
bool mouseVisible;
}; };
static struct iface * this = NULL; static struct iface * this = NULL;
@ -64,6 +68,13 @@ static void getDesktopSize(unsigned int * width, unsigned int * height)
*height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; *height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
} }
static void on_mouseMove(int x, int y)
{
this->mouseX = x;
this->mouseY = y;
os_signalEvent(this->cursorEvents[1]);
}
static const char * nvfbc_getName() static const char * nvfbc_getName()
{ {
return "NVFBC (NVidia Frame Buffer Capture)"; return "NVFBC (NVidia Frame Buffer Capture)";
@ -95,13 +106,14 @@ static bool nvfbc_create()
static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize) static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize)
{ {
this->reinit = false; this->stop = false;
this->pointerShape = pointerShape; this->pointerShape = pointerShape;
this->pointerSize = pointerSize; this->pointerSize = pointerSize;
getDesktopSize(&this->width, &this->height); getDesktopSize(&this->width, &this->height);
os_resetEvent(this->frameEvent); os_resetEvent(this->frameEvent);
HANDLE event;
if (!NvFBCToSysSetup( if (!NvFBCToSysSetup(
this->nvfbc, this->nvfbc,
BUFFER_FMT_ARGB10, BUFFER_FMT_ARGB10,
@ -111,18 +123,30 @@ static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize)
0, 0,
(void **)&this->frameBuffer, (void **)&this->frameBuffer,
NULL, NULL,
&this->cursorEvent &event
)) ))
{ {
return false; return false;
} }
this->cursorEvents[0] = os_wrapEvent(event);
this->cursorEvents[1] = os_createEvent(true);
mouseHook_install(on_mouseMove);
Sleep(100); Sleep(100);
return true; return true;
} }
static void nvfbc_stop()
{
this->stop = true;
os_signalEvent(this->cursorEvents[1]);
os_signalEvent(this->frameEvent);
}
static bool nvfbc_deinit() static bool nvfbc_deinit()
{ {
mouseHook_remove();
return true; return true;
} }
@ -172,7 +196,7 @@ static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
return CAPTURE_RESULT_ERROR; return CAPTURE_RESULT_ERROR;
} }
if (this->reinit) if (this->stop)
return CAPTURE_RESULT_REINIT; return CAPTURE_RESULT_REINIT;
frame->width = this->grabInfo.dwWidth; frame->width = this->grabInfo.dwWidth;
@ -195,37 +219,33 @@ static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
static CaptureResult nvfbc_getPointer(CapturePointer * pointer) static CaptureResult nvfbc_getPointer(CapturePointer * pointer)
{ {
while(true) osEventHandle * events[2];
memcpy(&events, &this->cursorEvents, sizeof(osEventHandle *) * 2);
if (!os_waitEvents(events, 2, false, TIMEOUT_INFINITE))
{ {
bool sig = false; DEBUG_ERROR("Failed to wait on the cursor events");
switch(WaitForSingleObject((HANDLE)this->cursorEvent, INFINITE))
{
case WAIT_OBJECT_0:
sig = true;
break;
case WAIT_ABANDONED:
continue;
case WAIT_TIMEOUT:
continue;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for cursor event failed", GetLastError());
return CAPTURE_RESULT_ERROR; return CAPTURE_RESULT_ERROR;
} }
if (sig) if (this->stop)
break;
DEBUG_ERROR("Unknown wait event return code");
return CAPTURE_RESULT_ERROR;
}
if (this->reinit)
return CAPTURE_RESULT_REINIT; return CAPTURE_RESULT_REINIT;
return NvFBCToSysGetCursor(this->nvfbc, pointer, this->pointerShape, this->pointerSize); CaptureResult result;
pointer->shapeUpdate = false;
if (events[0])
{
result = NvFBCToSysGetCursor(this->nvfbc, pointer, this->pointerShape, this->pointerSize);
this->mouseVisible = pointer->visible;
this->mouseHotX = pointer->x;
this->mouseHotY = pointer->y;
if (result != CAPTURE_RESULT_OK)
return result;
}
pointer->visible = this->mouseVisible;
pointer->x = this->mouseX - this->mouseHotX;
pointer->y = this->mouseY - this->mouseHotY;
return CAPTURE_RESULT_OK;
} }
struct CaptureInterface Capture_NVFBC = struct CaptureInterface Capture_NVFBC =
@ -233,6 +253,7 @@ struct CaptureInterface Capture_NVFBC =
.getName = nvfbc_getName, .getName = nvfbc_getName,
.create = nvfbc_create, .create = nvfbc_create,
.init = nvfbc_init, .init = nvfbc_init,
.stop = nvfbc_stop,
.deinit = nvfbc_deinit, .deinit = nvfbc_deinit,
.free = nvfbc_free, .free = nvfbc_free,
.getMaxFrameSize = nvfbc_getMaxFrameSize, .getMaxFrameSize = nvfbc_getMaxFrameSize,

View File

@ -0,0 +1,23 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.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 2 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, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
typedef void (*MouseHookFn)(int x, int y);
void mouseHook_install(MouseHookFn callback);
void mouseHook_remove();

View File

@ -0,0 +1,23 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.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 2 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, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/platform.h"
#include <windows.h>
osEventHandle * os_wrapEvent(HANDLE event);

View File

@ -0,0 +1,3 @@
#include "winuser.h"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "app.manifest"

View File

@ -0,0 +1,98 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.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 2 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, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "windows/mousehook.h"
#include "windows/windebug.h"
#include "platform.h"
#include <windows.h>
#include <stdbool.h>
struct mouseHook
{
bool installed;
HHOOK hook;
MouseHookFn callback;
};
static struct mouseHook mouseHook = { 0 };
// forwards
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam);
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam);
void mouseHook_install(MouseHookFn callback)
{
struct MSG_CALL_FUNCTION cf;
cf.fn = msg_callback;
cf.wParam = 1;
cf.lParam = (LPARAM)callback;
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
}
void mouseHook_remove()
{
struct MSG_CALL_FUNCTION cf;
cf.fn = msg_callback;
cf.wParam = 0;
cf.lParam = 0;
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
}
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam)
{
if (wParam)
{
if (mouseHook.installed)
{
DEBUG_WARN("Mouse hook already installed");
return 0;
}
mouseHook.hook = SetWindowsHookEx(WH_MOUSE_LL, mouseHook_hook, NULL, 0);
if (!mouseHook.hook)
{
DEBUG_WINERROR("Failed to install the mouse hook", GetLastError());
return 0;
}
mouseHook.installed = true;
mouseHook.callback = (MouseHookFn)lParam;
}
else
{
if (!mouseHook.installed)
return 0;
UnhookWindowsHookEx(mouseHook.hook);
mouseHook.installed = false;
}
return 0;
}
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION && wParam == WM_MOUSEMOVE)
{
MSLLHOOKSTRUCT *msg = (MSLLHOOKSTRUCT *)lParam;
mouseHook.callback(msg->pt.x, msg->pt.y);
}
return CallNextHookEx(mouseHook.hook, nCode, wParam, lParam);
}

View File

@ -17,6 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include "platform.h"
#include "windows/platform.h"
#include "windows/mousehook.h"
#include <windows.h> #include <windows.h>
#include <setupapi.h> #include <setupapi.h>
@ -37,6 +40,7 @@ struct osThreadHandle
void * opaque; void * opaque;
HANDLE handle; HANDLE handle;
DWORD threadID; DWORD threadID;
int resultCode; int resultCode;
}; };
@ -45,6 +49,7 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
switch(msg) switch(msg)
{ {
case WM_CLOSE: case WM_CLOSE:
mouseHook_remove();
DestroyWindow(hwnd); DestroyWindow(hwnd);
break; break;
@ -52,6 +57,12 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
PostQuitMessage(0); PostQuitMessage(0);
break; break;
case WM_CALL_FUNCTION:
{
struct MSG_CALL_FUNCTION * cf = (struct MSG_CALL_FUNCTION *)lParam;
return cf->fn(cf->wParam, cf->lParam);
}
default: default:
return DefWindowProc(hwnd, msg, wParam, lParam); return DefWindowProc(hwnd, msg, wParam, lParam);
} }
@ -65,6 +76,22 @@ static int appThread(void * opaque)
return result; return result;
} }
LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam)
{
return SendMessage(messageWnd, Msg, wParam, lParam);
}
static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
if (dwCtrlType == CTRL_C_EVENT)
{
SendMessage(messageWnd, WM_CLOSE, 0, 0);
return TRUE;
}
return FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{ {
int result = 0; int result = 0;
@ -139,6 +166,9 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
free(infData); free(infData);
SetupDiDestroyDeviceInfoList(deviceInfoSet); SetupDiDestroyDeviceInfoList(deviceInfoSet);
// setup a handler for ctrl+c
SetConsoleCtrlHandler(CtrlHandler, TRUE);
// create a message window so that our message pump works // create a message window so that our message pump works
WNDCLASSEX wx = {}; WNDCLASSEX wx = {};
wx.cbSize = sizeof(WNDCLASSEX); wx.cbSize = sizeof(WNDCLASSEX);
@ -169,6 +199,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
{ {
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessage(&msg); DispatchMessage(&msg);
continue;
} }
else if (bRet < 0) else if (bRet < 0)
{ {
@ -312,6 +343,11 @@ osEventHandle * os_createEvent(bool autoReset)
return (osEventHandle*)event; return (osEventHandle*)event;
} }
osEventHandle * os_wrapEvent(HANDLE event)
{
return (osEventHandle*)event;
}
void os_freeEvent(osEventHandle * handle) void os_freeEvent(osEventHandle * handle)
{ {
CloseHandle((HANDLE)handle); CloseHandle((HANDLE)handle);
@ -346,6 +382,42 @@ bool os_waitEvent(osEventHandle * handle, unsigned int timeout)
} }
} }
bool os_waitEvents(osEventHandle * handles[], int count, bool waitAll, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
DWORD result = WaitForMultipleObjects(count, (HANDLE*)handles, waitAll, to);
if (result >= WAIT_OBJECT_0 && result < count)
{
// null non signalled events from the handle list
for(int i = 0; i < count; ++i)
if (i != result && !os_waitEvent(handles[i], 0))
handles[i] = NULL;
return true;
}
if (result >= WAIT_ABANDONED_0 && result - WAIT_ABANDONED_0 < count)
continue;
switch(result)
{
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
return false;
}
DEBUG_ERROR("Unknown wait event return code");
return false;
}
}
bool os_signalEvent(osEventHandle * handle) bool os_signalEvent(osEventHandle * handle)
{ {
return SetEvent((HANDLE)handle); return SetEvent((HANDLE)handle);

View File

@ -0,0 +1,32 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.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 2 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, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <windows.h>
#define WM_CALL_FUNCTION (WM_USER+1)
typedef LRESULT (*CallFunction)(WPARAM wParam, LPARAM lParam);
struct MSG_CALL_FUNCTION
{
CallFunction fn;
WPARAM wParam;
LPARAM lParam;
};
LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam);

View File

@ -368,6 +368,7 @@ int app_main()
} }
finish: finish:
iface->stop();
stopThreads(); stopThreads();
exit: exit: