diff --git a/_wip/uninit/init.cpp b/_wip/uninit/init.cpp new file mode 100644 index 00000000..021ebaa3 --- /dev/null +++ b/_wip/uninit/init.cpp @@ -0,0 +1,174 @@ +// 6 april 2015 +#include "uipriv_windows.hpp" +#include "attrstr.hpp" + +HINSTANCE hInstance; +int nCmdShow; + +HFONT hMessageFont; + +// LONGTERM needed? +HBRUSH hollowBrush; + +// the returned pointer is actually to the second character +// if the first character is - then free, otherwise don't +static const char *initerr(const char *message, const WCHAR *label, DWORD value) +{ + WCHAR *sysmsg; + BOOL hassysmsg; + WCHAR *wmessage; + WCHAR *wout; + char *out; + + hassysmsg = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, value, 0, (LPWSTR) (&sysmsg), 0, NULL) != 0; + if (!hassysmsg) + sysmsg = (WCHAR *) L""; // TODO + wmessage = toUTF16(message + 1); + wout = strf(L"-error initializing libui: %s; code %I32d (0x%08I32X) %s", + wmessage, + value, value, + sysmsg); + uiprivFree(wmessage); + if (hassysmsg) + LocalFree(sysmsg); // ignore error + out = toUTF8(wout); + uiprivFree(wout); + return out + 1; +} + +#define ieLastErr(msg) initerr("=" msg, L"GetLastError() ==", GetLastError()) +#define ieHRESULT(msg, hr) initerr("=" msg, L"HRESULT", (DWORD) hr) + +// LONGTERM put this declaration in a common file +uiInitOptions uiprivOptions; + +#define wantedICCClasses ( \ + ICC_STANDARD_CLASSES | /* user32.dll controls */ \ + ICC_PROGRESS_CLASS | /* progress bars */ \ + ICC_TAB_CLASSES | /* tabs */ \ + ICC_LISTVIEW_CLASSES | /* table headers */ \ + ICC_UPDOWN_CLASS | /* spinboxes */ \ + ICC_BAR_CLASSES | /* trackbar */ \ + ICC_DATE_CLASSES | /* date/time picker */ \ + 0) + +const char *uiInit(uiInitOptions *o) +{ + STARTUPINFOW si; + const char *ce; + HICON hDefaultIcon; + HCURSOR hDefaultCursor; + NONCLIENTMETRICSW ncm; + INITCOMMONCONTROLSEX icc; + HRESULT hr; + + uiprivOptions = *o; + + initAlloc(); + + nCmdShow = SW_SHOWDEFAULT; + GetStartupInfoW(&si); + if ((si.dwFlags & STARTF_USESHOWWINDOW) != 0) + nCmdShow = si.wShowWindow; + + // LONGTERM set DPI awareness + + hDefaultIcon = LoadIconW(NULL, IDI_APPLICATION); + if (hDefaultIcon == NULL) + return ieLastErr("loading default icon for window classes"); + hDefaultCursor = LoadCursorW(NULL, IDC_ARROW); + if (hDefaultCursor == NULL) + return ieLastErr("loading default cursor for window classes"); + + ce = initUtilWindow(hDefaultIcon, hDefaultCursor); + if (ce != NULL) + return initerr(ce, L"GetLastError() ==", GetLastError()); + + if (registerWindowClass(hDefaultIcon, hDefaultCursor) == 0) + return ieLastErr("registering uiWindow window class"); + + ZeroMemory(&ncm, sizeof (NONCLIENTMETRICSW)); + ncm.cbSize = sizeof (NONCLIENTMETRICSW); + if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof (NONCLIENTMETRICSW), &ncm, sizeof (NONCLIENTMETRICSW)) == 0) + return ieLastErr("getting default fonts"); + hMessageFont = CreateFontIndirectW(&(ncm.lfMessageFont)); + if (hMessageFont == NULL) + return ieLastErr("loading default messagebox font; this is the default UI font"); + + if (initContainer(hDefaultIcon, hDefaultCursor) == 0) + return ieLastErr("initializing uiWindowsMakeContainer() window class"); + + hollowBrush = (HBRUSH) GetStockObject(HOLLOW_BRUSH); + if (hollowBrush == NULL) + return ieLastErr("getting hollow brush"); + + ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX)); + icc.dwSize = sizeof (INITCOMMONCONTROLSEX); + icc.dwICC = wantedICCClasses; + if (InitCommonControlsEx(&icc) == 0) + return ieLastErr("initializing Common Controls"); + + hr = CoInitialize(NULL); + if (hr != S_OK && hr != S_FALSE) + return ieHRESULT("initializing COM", hr); + // LONGTERM initialize COM security + // LONGTERM (windows vista) turn off COM exception handling + + hr = initDraw(); + if (hr != S_OK) + return ieHRESULT("initializing Direct2D", hr); + + hr = uiprivInitDrawText(); + if (hr != S_OK) + return ieHRESULT("initializing DirectWrite", hr); + + if (registerAreaClass(hDefaultIcon, hDefaultCursor) == 0) + return ieLastErr("registering uiArea window class"); + + if (registerMessageFilter() == 0) + return ieLastErr("registering libui message filter"); + + if (registerD2DScratchClass(hDefaultIcon, hDefaultCursor) == 0) + return ieLastErr("initializing D2D scratch window class"); + + hr = uiprivInitImage(); + if (hr != S_OK) + return ieHRESULT("initializing WIC", hr); + + return NULL; +} + +void uiUninit(void) +{ + uiprivUninitTimers(); + uiprivUninitImage(); + uninitMenus(); + unregisterD2DScratchClass(); + unregisterMessageFilter(); + unregisterArea(); + uiprivUninitDrawText(); + uninitDraw(); + CoUninitialize(); + if (DeleteObject(hollowBrush) == 0) + logLastError(L"error freeing hollow brush"); + uninitContainer(); + if (DeleteObject(hMessageFont) == 0) + logLastError(L"error deleting control font"); + unregisterWindowClass(); + // no need to delete the default icon or cursor; see http://stackoverflow.com/questions/30603077/ + uninitUtilWindow(); + uninitAlloc(); +} + +void uiFreeInitError(const char *err) +{ + if (*(err - 1) == '-') + uiprivFree((void *) (err - 1)); +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + hInstance = hinstDLL; + return TRUE; +} diff --git a/_wip/uninit/main.c b/_wip/uninit/main.c new file mode 100644 index 00000000..5d349d32 --- /dev/null +++ b/_wip/uninit/main.c @@ -0,0 +1,148 @@ +// 6 april 2015 +#include "uipriv_unix.h" + +uiInitOptions uiprivOptions; + +static GHashTable *timers; + +const char *uiInit(uiInitOptions *o) +{ + GError *err = NULL; + const char *msg; + + uiprivOptions = *o; + if (gtk_init_with_args(NULL, NULL, NULL, NULL, NULL, &err) == FALSE) { + msg = g_strdup(err->message); + g_error_free(err); + return msg; + } + uiprivInitAlloc(); + uiprivLoadFutures(); + timers = g_hash_table_new(g_direct_hash, g_direct_equal); + return NULL; +} + +struct timer; // TODO get rid of forward declaration + +static void uninitTimer(gpointer key, gpointer value, gpointer data) +{ + uiprivFree((struct timer *) key); +} + +void uiUninit(void) +{ + g_hash_table_foreach(timers, uninitTimer, NULL); + g_hash_table_destroy(timers); + uiprivUninitMenus(); + uiprivUninitAlloc(); +} + +void uiFreeInitError(const char *err) +{ + g_free((gpointer) err); +} + +static gboolean (*iteration)(gboolean) = NULL; + +void uiMain(void) +{ + iteration = gtk_main_iteration_do; + gtk_main(); +} + +static gboolean stepsQuit = FALSE; + +// the only difference is we ignore the return value from gtk_main_iteration_do(), since it will always be TRUE if gtk_main() was never called +// gtk_main_iteration_do() will still run the main loop regardless +static gboolean stepsIteration(gboolean block) +{ + gtk_main_iteration_do(block); + return stepsQuit; +} + +void uiMainSteps(void) +{ + iteration = stepsIteration; +} + +int uiMainStep(int wait) +{ + gboolean block; + + block = FALSE; + if (wait) + block = TRUE; + return (*iteration)(block) == FALSE; +} + +// gtk_main_quit() may run immediately, or it may wait for other pending events; "it depends" (thanks mclasen in irc.gimp.net/#gtk+) +// PostQuitMessage() on Windows always waits, so we must do so too +// we'll do it by using an idle callback +static gboolean quit(gpointer data) +{ + if (iteration == stepsIteration) + stepsQuit = TRUE; + // TODO run a gtk_main() here just to do the cleanup steps of syncing the clipboard and other stuff gtk_main() does before it returns + else + gtk_main_quit(); + return FALSE; +} + +void uiQuit(void) +{ + gdk_threads_add_idle(quit, NULL); +} + +struct queued { + void (*f)(void *); + void *data; +}; + +static gboolean doqueued(gpointer data) +{ + struct queued *q = (struct queued *) data; + + (*(q->f))(q->data); + g_free(q); + return FALSE; +} + +void uiQueueMain(void (*f)(void *data), void *data) +{ + struct queued *q; + + // we have to use g_new0()/g_free() because uiprivAlloc() is only safe to call on the main thread + // for some reason it didn't affect me, but it did affect krakjoe + q = g_new0(struct queued, 1); + q->f = f; + q->data = data; + gdk_threads_add_idle(doqueued, q); +} + +struct timer { + int (*f)(void *); + void *data; +}; + +static gboolean doTimer(gpointer data) +{ + struct timer *t = (struct timer *) data; + + if (!(*(t->f))(t->data)) { + g_hash_table_remove(timers, t); + uiprivFree(t); + return FALSE; + } + return TRUE; +} + +void uiTimer(int milliseconds, int (*f)(void *data), void *data) +{ + struct timer *t; + + t = uiprivNew(struct timer); + t->f = f; + t->data = data; + g_timeout_add(milliseconds, doTimer, t); + g_hash_table_add(timers, t); +} diff --git a/_wip/uninit/main.m b/_wip/uninit/main.m new file mode 100644 index 00000000..0d02d642 --- /dev/null +++ b/_wip/uninit/main.m @@ -0,0 +1,288 @@ +// 6 april 2015 +#import "uipriv_darwin.h" +#import "attrstr.h" + +static BOOL canQuit = NO; +static NSAutoreleasePool *globalPool; +static uiprivApplicationClass *app; +static uiprivAppDelegate *delegate; + +static BOOL (^isRunning)(void); +static BOOL stepsIsRunning; + +@implementation uiprivApplicationClass + +- (void)sendEvent:(NSEvent *)e +{ + if (uiprivSendAreaEvents(e) != 0) + return; + [super sendEvent:e]; +} + +// NSColorPanel always sends changeColor: to the first responder regardless of whether there's a target set on it +// we can override it here (see colorbutton.m) +// thanks to mikeash in irc.freenode.net/#macdev for informing me this is how the first responder chain is initiated +// it turns out NSFontManager also sends changeFont: through this; let's inhibit that here too (see fontbutton.m) +- (BOOL)sendAction:(SEL)sel to:(id)to from:(id)from +{ + if (uiprivColorButtonInhibitSendAction(sel, from, to)) + return NO; + if (uiprivFontButtonInhibitSendAction(sel, from, to)) + return NO; + return [super sendAction:sel to:to from:from]; +} + +// likewise, NSFontManager also sends NSFontPanelValidation messages to the first responder, however it does NOT use sendAction:from:to:! +// instead, it uses this one (thanks swillits in irc.freenode.net/#macdev) +// we also need to override it (see fontbutton.m) +- (id)targetForAction:(SEL)sel to:(id)to from:(id)from +{ + id override; + + if (uiprivFontButtonOverrideTargetForAction(sel, from, to, &override)) + return override; + return [super targetForAction:sel to:to from:from]; +} + +// hey look! we're overriding terminate:! +// we're going to make sure we can go back to main() whether Cocoa likes it or not! +// and just how are we going to do that, hm? +// (note: this is called after applicationShouldTerminate:) +- (void)terminate:(id)sender +{ + // yes that's right folks: DO ABSOLUTELY NOTHING. + // the magic is [NSApp run] will just... stop. + + // well let's not do nothing; let's actually quit our graceful way + NSEvent *e; + + if (!canQuit) + uiprivImplBug("call to [NSApp terminate:] when not ready to terminate; definitely contact andlabs"); + + [uiprivNSApp() stop:uiprivNSApp()]; + // stop: won't register until another event has passed; let's synthesize one + e = [NSEvent otherEventWithType:NSApplicationDefined + location:NSZeroPoint + modifierFlags:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] + windowNumber:0 + context:[NSGraphicsContext currentContext] + subtype:0 + data1:0 + data2:0]; + [uiprivNSApp() postEvent:e atStart:NO]; // let pending events take priority (this is what PostQuitMessage() on Windows does so we have to do it here too for parity; thanks to mikeash in irc.freenode.net/#macdev for confirming that this parameter should indeed be NO) + + // and in case uiMainSteps() was called + stepsIsRunning = NO; +} + +@end + +@implementation uiprivAppDelegate + +- (void)dealloc +{ + // Apple docs: "Don't Use Accessor Methods in Initializer Methods and dealloc" + [_menuManager release]; + [super dealloc]; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app +{ + // for debugging + NSLog(@"in applicationShouldTerminate:"); + if (uiprivShouldQuit()) { + canQuit = YES; + // this will call terminate:, which is the same as uiQuit() + return NSTerminateNow; + } + return NSTerminateCancel; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app +{ + return NO; +} + +@end + +uiInitOptions uiprivOptions; + +const char *uiInit(uiInitOptions *o) +{ + @autoreleasepool { + uiprivOptions = *o; + app = [[uiprivApplicationClass sharedApplication] retain]; + // don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy! + // see https://github.com/andlabs/ui/issues/6 + [uiprivNSApp() setActivationPolicy:NSApplicationActivationPolicyRegular]; + delegate = [uiprivAppDelegate new]; + [uiprivNSApp() setDelegate:delegate]; + + uiprivInitAlloc(); + uiprivLoadFutures(); + uiprivLoadUndocumented(); + + // always do this so we always have an application menu + uiprivAppDelegate().menuManager = [[uiprivMenuManager new] autorelease]; + [uiprivNSApp() setMainMenu:[uiprivAppDelegate().menuManager makeMenubar]]; + + uiprivSetupFontPanel(); + + uiprivInitUnderlineColors(); + } + + globalPool = [[NSAutoreleasePool alloc] init]; + + return NULL; +} + +void uiUninit(void) +{ + if (!globalPool) + uiprivUserBug("You must call uiInit() first!"); + [globalPool release]; + + @autoreleasepool { + uiprivUninitUnderlineColors(); + [delegate release]; + [uiprivNSApp() setDelegate:nil]; + [app release]; + uiprivUninitAlloc(); + } +} + +void uiFreeInitError(const char *err) +{ +} + +void uiMain(void) +{ + isRunning = ^{ + return [uiprivNSApp() isRunning]; + }; + [uiprivNSApp() run]; +} + +void uiMainSteps(void) +{ + // SDL does this and it seems to be necessary for the menubar to work (see #182) + [uiprivNSApp() finishLaunching]; + isRunning = ^{ + return stepsIsRunning; + }; + stepsIsRunning = YES; +} + +int uiMainStep(int wait) +{ + uiprivNextEventArgs nea; + + nea.mask = NSAnyEventMask; + + // ProPuke did this in his original PR requesting this + // I'm not sure if this will work, but I assume it will... + nea.duration = [NSDate distantPast]; + if (wait) // but this is normal so it will work + nea.duration = [NSDate distantFuture]; + + nea.mode = NSDefaultRunLoopMode; + nea.dequeue = YES; + + return uiprivMainStep(&nea, ^(NSEvent *e) { + return NO; + }); +} + +// see also: +// - http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html +// - https://github.com/gnustep/gui/blob/master/Source/NSApplication.m +int uiprivMainStep(uiprivNextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *e)) +{ + NSDate *expire; + NSEvent *e; + NSEventType type; + + @autoreleasepool { + if (!isRunning()) + return 0; + + e = [uiprivNSApp() nextEventMatchingMask:nea->mask + untilDate:nea->duration + inMode:nea->mode + dequeue:nea->dequeue]; + if (e == nil) + return 1; + + type = [e type]; + if (!interceptEvent(e)) + [uiprivNSApp() sendEvent:e]; + [uiprivNSApp() updateWindows]; + + // GNUstep does this + // it also updates the Services menu but there doesn't seem to be a public API for that so + if (type != NSPeriodic && type != NSMouseMoved) + [[uiprivNSApp() mainMenu] update]; + + return 1; + } +} + +void uiQuit(void) +{ + canQuit = YES; + [uiprivNSApp() terminate:uiprivNSApp()]; +} + +// thanks to mikeash in irc.freenode.net/#macdev for suggesting the use of Grand Central Dispatch for this +// LONGTERM will dispatch_get_main_queue() break after _CFRunLoopSetCurrent()? +void uiQueueMain(void (*f)(void *data), void *data) +{ + // dispatch_get_main_queue() is a serial queue so it will not execute multiple uiQueueMain() functions concurrently + // the signature of f matches dispatch_function_t + dispatch_async_f(dispatch_get_main_queue(), data, f); +} + +@interface uiprivTimerDelegate : NSObject { + int (*f)(void *data); + void *data; +} +- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData; +- (void)doTimer:(NSTimer *)timer; +@end + +@implementation uiprivTimerDelegate + +- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData +{ + self = [super init]; + if (self) { + self->f = callback; + self->data = callbackData; + } + return self; +} + +- (void)doTimer:(NSTimer *)timer +{ + if (!(*(self->f))(self->data)) + [timer invalidate]; +} + +@end + +void uiTimer(int milliseconds, int (*f)(void *data), void *data) +{ + uiprivTimerDelegate *delegate; + + delegate = [[uiprivTimerDelegate alloc] initWithCallback:f data:data]; + [NSTimer scheduledTimerWithTimeInterval:(milliseconds / 1000.0) + target:delegate + selector:@selector(doTimer:) + userInfo:nil + repeats:YES]; + [delegate release]; +} + +// TODO figure out the best way to clean the above up in uiUninit(), if it's even necessary +// TODO that means figure out if timers can still fire without the main loop diff --git a/darwin/main.m b/darwin/main.m index b12ffe9f..80e31957 100644 --- a/darwin/main.m +++ b/darwin/main.m @@ -28,7 +28,3 @@ int uiInit(void *options, uiInitError *err) uiprivMarkInitialized(); return 1; } - -void uiUninit(void) -{ -} diff --git a/doc/init-main.md b/doc/init-main.md index 06c4acef..fbdb806c 100644 --- a/doc/init-main.md +++ b/doc/init-main.md @@ -51,12 +51,6 @@ If `uiInit()` fails, no other libui function is safe to call. This means that yo **Notes for language binding authors**: Your language will likely provide its own preferred mechanism for reporting errors. You should wrap `uiInit()` to return errors this way, creating and managing the memory for `uiInitError` yourself and transforming the returned error according to both the format of `uiInitError` described below and the rules for encoding errors in your language of choice. -### `uiUninit()` - -```c -void uiUninit(void); -``` - ### `uiInitError` ```c diff --git a/test/initmain.c b/test/initmain.c index 43b40586..c0915865 100644 --- a/test/initmain.c +++ b/test/initmain.c @@ -47,10 +47,6 @@ testingTestBefore(Init) testingTErrorf(t, "uiInit() after a previous successful call returned bad error message: got %s, want %s", err.Message, errAlreadyInitialized); } -testingTestAfter(Uninit) -{ -} - static void queued(void *data) { int *flag = (int *) data; diff --git a/ui.h b/ui.h index 1310f421..1a3b324a 100644 --- a/ui.h +++ b/ui.h @@ -38,7 +38,6 @@ struct uiInitError { }; uiprivExtern int uiInit(void *options, uiInitError *err); -uiprivExtern void uiUninit(void); #ifdef __cplusplus } diff --git a/unix/main.c b/unix/main.c index 728d0778..dc63d1ec 100644 --- a/unix/main.c +++ b/unix/main.c @@ -17,7 +17,3 @@ int uiInit(void *options, uiInitError *err) uiprivMarkInitialized(); return 1; } - -void uiUninit(void) -{ -} diff --git a/windows/init.cpp b/windows/init.cpp index c031e102..6d13f059 100644 --- a/windows/init.cpp +++ b/windows/init.cpp @@ -70,11 +70,6 @@ int uiInit(void *options, uiInitError *err) return 1; } -void uiUninit(void) -{ -// CoUninitialize(); -} - #ifndef uiStatic BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpvReserved)