#ifdef FFENESTRI_DARWIN #include "ffenestri_darwin.h" #include "menu_darwin.h" #include "contextmenus_darwin.h" #include "traymenustore_darwin.h" #include "traymenu_darwin.h" // References to assets #include "assets.h" extern const unsigned char runtime; // Dialog icons extern const unsigned char *defaultDialogIcons[]; #include "userdialogicons.h" // MAIN DEBUG FLAG int debug; // A cache for all our dialog icons struct hashmap_s dialogIconCache; // Dispatch Method typedef void (^dispatchMethod)(void); // dispatch will execute the given `func` pointer void dispatch(dispatchMethod func) { dispatch_async(dispatch_get_main_queue(), func); } // yes command simply returns YES! BOOL yes(id self, SEL cmd) { return YES; } // Prints a hashmap entry int hashmap_log(void *const context, struct hashmap_element_s *const e) { printf("%s: %p ", (char*)e->key, e->data); return 0; } // Utility function to visualise a hashmap void dumpHashmap(const char *name, struct hashmap_s *hashmap) { printf("%s = { ", name); if (0!=hashmap_iterate_pairs(hashmap, hashmap_log, NULL)) { fprintf(stderr, "Failed to dump hashmap entries\n"); } printf("}\n"); } extern void messageFromWindowCallback(const char *); typedef void (*ffenestriCallback)(const char *); void HideMouse() { msg(c("NSCursor"), s("hide")); } void ShowMouse() { msg(c("NSCursor"), s("unhide")); } struct Application { // Cocoa data id application; id delegate; id mainWindow; id wkwebview; id manager; id config; id mouseEvent; id mouseDownMonitor; id mouseUpMonitor; // Window Data const char *title; int width; int height; int minWidth; int minHeight; int maxWidth; int maxHeight; int resizable; int devtools; int fullscreen; int red; int green; int blue; int alpha; int webviewIsTranparent; const char *appearance; int decorations; int logLevel; // Features int frame; int startHidden; int maximised; int titlebarAppearsTransparent; int hideTitle; int hideTitleBar; int fullSizeContent; int useToolBar; int hideToolbarSeparator; int windowBackgroundIsTranslucent; // Menu Menu *applicationMenu; // Tray TrayMenuStore* trayMenuStore; // Context Menus ContextMenuStore *contextMenuStore; // Callback ffenestriCallback sendMessageToBackend; // Bindings const char *bindings; }; // Debug works like sprintf but mutes if the global debug flag is true // Credit: https://stackoverflow.com/a/20639708 #define MAXMESSAGE 1024*10 char logbuffer[MAXMESSAGE]; void Debug(struct Application *app, const char *message, ... ) { if ( debug ) { const char *temp = concat("LTFfenestri (C) | ", message); va_list args; va_start(args, message); vsnprintf(logbuffer, MAXMESSAGE, temp, args); app->sendMessageToBackend(&logbuffer[0]); MEMFREE(temp); va_end(args); } } void Fatal(struct Application *app, const char *message, ... ) { const char *temp = concat("LFFfenestri (C) | ", message); va_list args; va_start(args, message); vsnprintf(logbuffer, MAXMESSAGE, temp, args); app->sendMessageToBackend(&logbuffer[0]); MEMFREE(temp); va_end(args); } bool isRetina(struct Application *app) { CGFloat scale = GET_BACKINGSCALEFACTOR(app->mainWindow); if( (int)scale == 1 ) { return false; } return true; } void TitlebarAppearsTransparent(struct Application* app) { app->titlebarAppearsTransparent = 1; } void HideTitle(struct Application *app) { app->hideTitle = 1; } void HideTitleBar(struct Application *app) { app->hideTitleBar = 1; } void HideToolbarSeparator(struct Application *app) { app->hideToolbarSeparator = 1; } void UseToolbar(struct Application *app) { app->useToolBar = 1; } // WebviewIsTransparent will make the webview transparent // revealing the Cocoa window underneath void WebviewIsTransparent(struct Application *app) { app->webviewIsTranparent = 1; } // SetAppearance will set the window's Appearance to the // given value void SetAppearance(struct Application *app, const char *appearance) { app->appearance = appearance; } void applyWindowColour(struct Application *app) { // Apply the colour only if the window has been created if( app->mainWindow != NULL ) { ON_MAIN_THREAD( id colour = msg(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"), (float)app->red / 255.0, (float)app->green / 255.0, (float)app->blue / 255.0, (float)app->alpha / 255.0); msg(app->mainWindow, s("setBackgroundColor:"), colour); ); } } void SetColour(struct Application *app, int red, int green, int blue, int alpha) { app->red = red; app->green = green; app->blue = blue; app->alpha = alpha; applyWindowColour(app); } void FullSizeContent(struct Application *app) { app->fullSizeContent = 1; } void Hide(struct Application *app) { ON_MAIN_THREAD( msg(app->application, s("hide:")) ); } void Show(struct Application *app) { ON_MAIN_THREAD( msg(app->mainWindow, s("makeKeyAndOrderFront:"), NULL); msg(app->application, s("activateIgnoringOtherApps:"), YES); ); } void WindowBackgroundIsTranslucent(struct Application *app) { app->windowBackgroundIsTranslucent = 1; } // Sends messages to the backend void messageHandler(id self, SEL cmd, id contentController, id message) { struct Application *app = (struct Application *)objc_getAssociatedObject( self, "application"); const char *name = (const char *)msg(msg(message, s("name")), s("UTF8String")); if( strcmp(name, "completed") == 0) { // Delete handler msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed")); // TODO: Notify backend we're ready and get them to call back for the Show() if (app->startHidden == 0) { Show(app); } // TODO: Check this actually does reduce flicker msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering")); // Notify backend we are ready (system startup) app->sendMessageToBackend("SS"); } else if( strcmp(name, "windowDrag") == 0 ) { // Guard against null events if( app->mouseEvent != NULL ) { HideMouse(); ON_MAIN_THREAD( msg(app->mainWindow, s("performWindowDragWithEvent:"), app->mouseEvent); ); } } else if( strcmp(name, "contextMenu") == 0 ) { // Did we get a context menu selector? if( message == NULL) { return; } const char *contextMenuMessage = cstr(msg(message, s("body"))); if( contextMenuMessage == NULL ) { Debug(app, "EMPTY CONTEXT MENU MESSAGE!!\n"); return; } // Parse the message JsonNode *contextMenuMessageJSON = json_decode(contextMenuMessage); if( contextMenuMessageJSON == NULL ) { Debug(app, "Error decoding context menu message: %s", contextMenuMessage); return; } // Get menu ID JsonNode *contextMenuIDNode = json_find_member(contextMenuMessageJSON, "id"); if( contextMenuIDNode == NULL ) { Debug(app, "Error decoding context menu ID: %s", contextMenuMessage); json_delete(contextMenuMessageJSON); return; } if( contextMenuIDNode->tag != JSON_STRING ) { Debug(app, "Error decoding context menu ID (Not a string): %s", contextMenuMessage); json_delete(contextMenuMessageJSON); return; } // Get menu Data JsonNode *contextMenuDataNode = json_find_member(contextMenuMessageJSON, "data"); if( contextMenuDataNode == NULL ) { Debug(app, "Error decoding context menu data: %s", contextMenuMessage); json_delete(contextMenuMessageJSON); return; } if( contextMenuDataNode->tag != JSON_STRING ) { Debug(app, "Error decoding context menu data (Not a string): %s", contextMenuMessage); json_delete(contextMenuMessageJSON); return; } // We need to copy these as the JSON node will be destroyed on this thread and the // string data will become corrupt. These need to be freed by the context menu code. const char* contextMenuID = STRCOPY(contextMenuIDNode->string_); const char* contextMenuData = STRCOPY(contextMenuDataNode->string_); ON_MAIN_THREAD( ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData); ); json_delete(contextMenuMessageJSON); } else { // const char *m = (const char *)msg(msg(message, s("body")), s("UTF8String")); const char *m = cstr(msg(message, s("body"))); app->sendMessageToBackend(m); } } // closeWindow is called when the close button is pressed void closeWindow(id self, SEL cmd, id sender) { struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application"); app->sendMessageToBackend("WC"); } bool isDarkMode(struct Application *app) { id userDefaults = msg(c("NSUserDefaults"), s("standardUserDefaults")); const char *mode = cstr(msg(userDefaults, s("stringForKey:"), str("AppleInterfaceStyle"))); return ( mode != NULL && strcmp(mode, "Dark") == 0 ); } void ExecJS(struct Application *app, const char *js) { ON_MAIN_THREAD( msg(app->wkwebview, s("evaluateJavaScript:completionHandler:"), str(js), NULL); ); } void willFinishLaunching(id self, SEL cmd, id sender) { struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application"); messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}"); } void emitThemeChange(struct Application *app) { bool currentThemeIsDark = isDarkMode(app); if (currentThemeIsDark) { messageFromWindowCallback("Ej{\"name\":\"wails:system:themechange\",\"data\":[true]}"); } else { messageFromWindowCallback("Ej{\"name\":\"wails:system:themechange\",\"data\":[false]}"); } } void themeChanged(id self, SEL cmd, id sender) { struct Application *app = (struct Application *)objc_getAssociatedObject( self, "application"); // emitThemeChange(app); bool currentThemeIsDark = isDarkMode(app); if ( currentThemeIsDark ) { ExecJS(app, "window.wails.Events.Emit( 'wails:system:themechange', true );"); } else { ExecJS(app, "window.wails.Events.Emit( 'wails:system:themechange', false );"); } } // void willFinishLaunching(id self) { // struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application"); // Debug(app, "willFinishLaunching called!"); // } int releaseNSObject(void *const context, struct hashmap_element_s *const e) { msg(e->data, s("release")); return -1; } void destroyContextMenus(struct Application *app) { DeleteContextMenuStore(app->contextMenuStore); } void freeDialogIconCache(struct Application *app) { // Release the dialog cache images if( hashmap_num_entries(&dialogIconCache) > 0 ) { if (0!=hashmap_iterate_pairs(&dialogIconCache, releaseNSObject, NULL)) { Fatal(app, "failed to release hashmap entries!"); } } //Free radio groups hashmap hashmap_destroy(&dialogIconCache); } void DestroyApplication(struct Application *app) { Debug(app, "Destroying Application"); // Free the bindings if (app->bindings != NULL) { MEMFREE(app->bindings); } else { Debug(app, "Almost a double free for app->bindings"); } // Remove mouse monitors if( app->mouseDownMonitor != NULL ) { msg( c("NSEvent"), s("removeMonitor:"), app->mouseDownMonitor); } if( app->mouseUpMonitor != NULL ) { msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor); } // Delete the application menu if we have one if( app->applicationMenu != NULL ) { DeleteMenu(app->applicationMenu); } // Delete the tray menu store DeleteTrayMenuStore(app->trayMenuStore); // Delete the context menu store DeleteContextMenuStore(app->contextMenuStore); // Destroy the context menus destroyContextMenus(app); // Free dialog icon cache freeDialogIconCache(app); // Unload the tray Icons UnloadTrayIcons(); // Remove script handlers msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu")); msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag")); msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external")); // Close main window msg(app->mainWindow, s("close")); // Terminate app msg(c("NSApp"), s("terminate:"), NULL); Debug(app, "Finished Destroying Application"); } // Quit will stop the cocoa application and free up all the memory // used by the application void Quit(struct Application *app) { Debug(app, "Quit Called"); DestroyApplication(app); } // SetTitle sets the main window title to the given string void SetTitle(struct Application *app, const char *title) { Debug(app, "SetTitle Called"); ON_MAIN_THREAD( msg(app->mainWindow, s("setTitle:"), str(title)); ); } void ToggleFullscreen(struct Application *app) { ON_MAIN_THREAD( app->fullscreen = !app->fullscreen; MAIN_WINDOW_CALL("toggleFullScreen:"); ); } bool isFullScreen(struct Application *app) { int mask = (int)msg(app->mainWindow, s("styleMask")); bool result = (mask & NSWindowStyleMaskFullscreen) == NSWindowStyleMaskFullscreen; return result; } // Fullscreen sets the main window to be fullscreen void Fullscreen(struct Application *app) { Debug(app, "Fullscreen Called"); if( ! isFullScreen(app) ) { ToggleFullscreen(app); } } // UnFullscreen resets the main window after a fullscreen void UnFullscreen(struct Application *app) { Debug(app, "UnFullscreen Called"); if( isFullScreen(app) ) { ToggleFullscreen(app); } } void Center(struct Application *app) { Debug(app, "Center Called"); ON_MAIN_THREAD( MAIN_WINDOW_CALL("center"); ); } void ToggleMaximise(struct Application *app) { ON_MAIN_THREAD( app->maximised = !app->maximised; MAIN_WINDOW_CALL("zoom:"); ); } void Maximise(struct Application *app) { if( app->maximised == 0) { ToggleMaximise(app); } } void Unmaximise(struct Application *app) { if( app->maximised == 1) { ToggleMaximise(app); } } void Minimise(struct Application *app) { ON_MAIN_THREAD( MAIN_WINDOW_CALL("miniaturize:"); ); } void Unminimise(struct Application *app) { ON_MAIN_THREAD( MAIN_WINDOW_CALL("deminiaturize:"); ); } id getCurrentScreen(struct Application *app) { id screen = NULL; screen = msg(app->mainWindow, s("screen")); if( screen == NULL ) { screen = msg(c("NSScreen"), u("mainScreen")); } return screen; } void dumpFrame(struct Application *app, const char *message, CGRect frame) { Debug(app, message); Debug(app, "origin.x %f", frame.origin.x); Debug(app, "origin.y %f", frame.origin.y); Debug(app, "size.width %f", frame.size.width); Debug(app, "size.height %f", frame.size.height); } void SetSize(struct Application *app, int width, int height) { ON_MAIN_THREAD( id screen = getCurrentScreen(app); // Get the rect for the window CGRect frame = GET_FRAME(app->mainWindow); // Credit: https://github.com/patr0nus/DeskGap/blob/73c0ac9f2c73f55b6e81f64f6673a7962b5719cd/lib/src/platform/mac/util/NSScreen%2BGeometry.m frame.origin.y = (frame.origin.y + frame.size.height) - (float)height; frame.size.width = (float)width; frame.size.height = (float)height; msg(app->mainWindow, s("setFrame:display:animate:"), frame, 1, 0); ); } void SetPosition(struct Application *app, int x, int y) { ON_MAIN_THREAD( id screen = getCurrentScreen(app); CGRect screenFrame = GET_FRAME(screen); CGRect windowFrame = GET_FRAME(app->mainWindow); windowFrame.origin.x = screenFrame.origin.x + (float)x; windowFrame.origin.y = (screenFrame.origin.y + screenFrame.size.height) - windowFrame.size.height - (float)y; msg(app->mainWindow, s("setFrame:display:animate:"), windowFrame, 1, 0); ); } void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *defaultButton) { // If this button is set if( STR_HAS_CHARS(buttonTitle) ) { id button = msg(alert, s("addButtonWithTitle:"), str(buttonTitle)); if ( STREQ( buttonTitle, defaultButton) ) { msg(button, s("setKeyEquivalent:"), str("\r")); } if ( STREQ( buttonTitle, cancelButton) ) { msg(button, s("setKeyEquivalent:"), str("\033")); } } } extern void MessageDialog(struct Application *app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) { ON_MAIN_THREAD( id alert = ALLOC_INIT("NSAlert"); char *dialogType = type; char *dialogIcon = type; // Default to info type if( dialogType == NULL ) { dialogType = "info"; } // Set the dialog style if( STREQ(dialogType, "info") || STREQ(dialogType, "question") ) { msg(alert, s("setAlertStyle:"), NSAlertStyleInformational); } else if( STREQ(dialogType, "warning") ) { msg(alert, s("setAlertStyle:"), NSAlertStyleWarning); } else if( STREQ(dialogType, "error") ) { msg(alert, s("setAlertStyle:"), NSAlertStyleCritical); } // Set title if given if( strlen(title) > 0 ) { msg(alert, s("setMessageText:"), str(title)); } // Set message if given if( strlen(message) > 0) { msg(alert, s("setInformativeText:"), str(message)); } // Process buttons processDialogButton(alert, button1, cancelButton, defaultButton); processDialogButton(alert, button2, cancelButton, defaultButton); processDialogButton(alert, button3, cancelButton, defaultButton); processDialogButton(alert, button4, cancelButton, defaultButton); // Check for custom dialog icon if( strlen(icon) > 0 ) { dialogIcon = icon; } // TODO: move dialog icons + methods to own file // Determine what dialog icon we are looking for id dialogImage = NULL; // Look for `name-theme2x` first char *themeIcon = concat(dialogIcon, (isDarkMode(app) ? "-dark" : "-light") ); if( isRetina(app) ) { char *dialogIcon2x = concat(themeIcon, "2x"); dialogImage = hashmap_get(&dialogIconCache, dialogIcon2x, strlen(dialogIcon2x)); // if (dialogImage != NULL ) printf("Using %s\n", dialogIcon2x); MEMFREE(dialogIcon2x); // Now look for non-themed icon `name2x` if ( dialogImage == NULL ) { dialogIcon2x = concat(dialogIcon, "2x"); dialogImage = hashmap_get(&dialogIconCache, dialogIcon2x, strlen(dialogIcon2x)); // if (dialogImage != NULL ) printf("Using %s\n", dialogIcon2x); MEMFREE(dialogIcon2x); } } // If we don't have a retina icon, try the 1x name-theme icon if( dialogImage == NULL ) { dialogImage = hashmap_get(&dialogIconCache, themeIcon, strlen(themeIcon)); // if (dialogImage != NULL ) printf("Using %s\n", themeIcon); } // Free the theme icon memory MEMFREE(themeIcon); // Finally try the name itself if( dialogImage == NULL ) { dialogImage = hashmap_get(&dialogIconCache, dialogIcon, strlen(dialogIcon)); // if (dialogImage != NULL ) printf("Using %s\n", dialogIcon); } if (dialogImage != NULL ) { msg(alert, s("setIcon:"), dialogImage); } // Run modal char *buttonPressed; int response = (int)msg(alert, s("runModal")); if( response == NSAlertFirstButtonReturn ) { buttonPressed = button1; } else if( response == NSAlertSecondButtonReturn ) { buttonPressed = button2; } else if( response == NSAlertThirdButtonReturn ) { buttonPressed = button3; } else { buttonPressed = button4; } // Construct callback message. Format "DS|" const char *callback = concat("DM", callbackID); const char *header = concat(callback, "|"); const char *responseMessage = concat(header, buttonPressed); // Send message to backend app->sendMessageToBackend(responseMessage); // Free memory MEMFREE(header); MEMFREE(callback); MEMFREE(responseMessage); ); } // OpenDialog opens a dialog to select files/directories void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) { Debug(app, "OpenDialog Called with callback id: %s", callbackID); // Create an open panel ON_MAIN_THREAD( // Create the dialog id dialog = msg(c("NSOpenPanel"), s("openPanel")); // Valid but appears to do nothing.... :/ msg(dialog, s("setTitle:"), str(title)); // Filters if( filters != NULL && strlen(filters) > 0) { id filterString = msg(str(filters), s("stringByReplacingOccurrencesOfString:withString:"), str("*."), str("")); filterString = msg(filterString, s("stringByReplacingOccurrencesOfString:withString:"), str(" "), str("")); id filterList = msg(filterString, s("componentsSeparatedByString:"), str(",")); msg(dialog, s("setAllowedFileTypes:"), filterList); } else { msg(dialog, s("setAllowsOtherFileTypes:"), YES); } // Default Directory if( defaultDir != NULL && strlen(defaultDir) > 0 ) { msg(dialog, s("setDirectoryURL:"), url(defaultDir)); } // Default Filename if( defaultFilename != NULL && strlen(defaultFilename) > 0 ) { msg(dialog, s("setNameFieldStringValue:"), str(defaultFilename)); } // Setup Options msg(dialog, s("setCanChooseFiles:"), allowFiles); msg(dialog, s("setCanChooseDirectories:"), allowDirs); msg(dialog, s("setAllowsMultipleSelection:"), allowMultiple); msg(dialog, s("setShowsHiddenFiles:"), showHiddenFiles); msg(dialog, s("setCanCreateDirectories:"), canCreateDirectories); msg(dialog, s("setResolvesAliases:"), resolvesAliases); msg(dialog, s("setTreatsFilePackagesAsDirectories:"), treatPackagesAsDirectories); // Setup callback handler msg(dialog, s("beginSheetModalForWindow:completionHandler:"), app->mainWindow, ^(id result) { // Create the response JSON object JsonNode *response = json_mkarray(); // If the user selected some files if( result == (id)1 ) { // Grab the URLs returned id urls = msg(dialog, s("URLs")); // Iterate over all the selected files long noOfResults = (long)msg(urls, s("count")); for( int index = 0; index < noOfResults; index++ ) { // Extract the filename id url = msg(urls, s("objectAtIndex:"), index); const char *filename = (const char *)msg(msg(url, s("path")), s("UTF8String")); // Add the the response array json_append_element(response, json_mkstring(filename)); } } // Create JSON string and free json memory char *encoded = json_stringify(response, ""); json_delete(response); // Construct callback message. Format "D|" const char *callback = concat("DO", callbackID); const char *header = concat(callback, "|"); const char *responseMessage = concat(header, encoded); // Send message to backend app->sendMessageToBackend(responseMessage); // Free memory MEMFREE(header); MEMFREE(callback); MEMFREE(responseMessage); }); msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow); ); } // SaveDialog opens a dialog to select files/directories void SaveDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) { Debug(app, "SaveDialog Called with callback id: %s", callbackID); // Create an open panel ON_MAIN_THREAD( // Create the dialog id dialog = msg(c("NSSavePanel"), s("savePanel")); // Valid but appears to do nothing.... :/ msg(dialog, s("setTitle:"), str(title)); // Filters if( filters != NULL && strlen(filters) > 0) { id filterString = msg(str(filters), s("stringByReplacingOccurrencesOfString:withString:"), str("*."), str("")); filterString = msg(filterString, s("stringByReplacingOccurrencesOfString:withString:"), str(" "), str("")); id filterList = msg(filterString, s("componentsSeparatedByString:"), str(",")); msg(dialog, s("setAllowedFileTypes:"), filterList); } else { msg(dialog, s("setAllowsOtherFileTypes:"), YES); } // Default Directory if( defaultDir != NULL && strlen(defaultDir) > 0 ) { msg(dialog, s("setDirectoryURL:"), url(defaultDir)); } // Default Filename if( defaultFilename != NULL && strlen(defaultFilename) > 0 ) { msg(dialog, s("setNameFieldStringValue:"), str(defaultFilename)); } // Setup Options msg(dialog, s("setShowsHiddenFiles:"), showHiddenFiles); msg(dialog, s("setCanCreateDirectories:"), canCreateDirectories); msg(dialog, s("setTreatsFilePackagesAsDirectories:"), treatPackagesAsDirectories); // Setup callback handler msg(dialog, s("beginSheetModalForWindow:completionHandler:"), app->mainWindow, ^(id result) { // Default is blank const char *filename = ""; // If the user selected some files if( result == (id)1 ) { // Grab the URL returned id url = msg(dialog, s("URL")); filename = (const char *)msg(msg(url, s("path")), s("UTF8String")); } // Construct callback message. Format "DS|" const char *callback = concat("DS", callbackID); const char *header = concat(callback, "|"); const char *responseMessage = concat(header, filename); // Send message to backend app->sendMessageToBackend(responseMessage); // Free memory MEMFREE(header); MEMFREE(callback); MEMFREE(responseMessage); }); msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow); ); } const char *invoke = "window.external={invoke:function(x){window.webkit.messageHandlers.external.postMessage(x);}};"; // DisableFrame disables the window frame void DisableFrame(struct Application *app) { app->frame = 0; } void setMinMaxSize(struct Application *app) { if (app->maxHeight > 0 && app->maxWidth > 0) { msg(app->mainWindow, s("setMaxSize:"), CGSizeMake(app->maxWidth, app->maxHeight)); } if (app->minHeight > 0 && app->minWidth > 0) { msg(app->mainWindow, s("setMinSize:"), CGSizeMake(app->minWidth, app->minHeight)); } } void SetMinWindowSize(struct Application *app, int minWidth, int minHeight) { app->minWidth = minWidth; app->minHeight = minHeight; // Apply if the window is created if( app->mainWindow != NULL ) { ON_MAIN_THREAD( setMinMaxSize(app); ); } } void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight) { app->maxWidth = maxWidth; app->maxHeight = maxHeight; // Apply if the window is created if( app->mainWindow != NULL ) { ON_MAIN_THREAD( setMinMaxSize(app); ); } } void SetDebug(void *applicationPointer, int flag) { debug = flag; } // SetContextMenus sets the context menu map for this application void AddContextMenu(struct Application *app, const char *contextMenuJSON) { AddContextMenuToStore(app->contextMenuStore, contextMenuJSON); } void UpdateContextMenu(struct Application *app, const char* contextMenuJSON) { UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON); } void AddTrayMenu(struct Application *app, const char *trayMenuJSON) { AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON); } void UpdateTrayMenu(struct Application *app, const char* trayMenuJSON) { UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON); } void SetBindings(struct Application *app, const char *bindings) { const char* temp = concat("window.wailsbindings = \"", bindings); const char* jscall = concat(temp, "\";"); MEMFREE(temp); app->bindings = jscall; } void makeWindowBackgroundTranslucent(struct Application *app) { id contentView = msg(app->mainWindow, s("contentView")); id effectView = msg(c("NSVisualEffectView"), s("alloc")); CGRect bounds = GET_BOUNDS(contentView); effectView = msg(effectView, s("initWithFrame:"), bounds); msg(effectView, s("setAutoresizingMask:"), NSViewWidthSizable | NSViewHeightSizable); msg(effectView, s("setBlendingMode:"), NSVisualEffectBlendingModeBehindWindow); msg(effectView, s("setState:"), NSVisualEffectStateActive); msg(contentView, s("addSubview:positioned:relativeTo:"), effectView, NSWindowBelow, NULL); } void enableBoolConfig(id config, const char *setting) { msg(msg(config, s("preferences")), s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 1), str(setting)); } void disableBoolConfig(id config, const char *setting) { msg(msg(config, s("preferences")), s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str(setting)); } void processDecorations(struct Application *app) { int decorations = 0; if (app->frame == 1 ) { if( app->hideTitleBar == 0) { decorations |= NSWindowStyleMaskTitled; } decorations |= NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; } if (app->resizable) { decorations |= NSWindowStyleMaskResizable; } if (app->fullscreen) { decorations |= NSWindowStyleMaskFullscreen; } if( app->fullSizeContent || app->frame == 0) { decorations |= NSWindowStyleMaskFullSizeContentView; } app->decorations = decorations; } void createApplication(struct Application *app) { id application = msg(c("NSApplication"), s("sharedApplication")); app->application = application; msg(application, s("setActivationPolicy:"), 0); } void DarkModeEnabled(struct Application *app, const char *callbackID) { ON_MAIN_THREAD( const char *result = isDarkMode(app) ? "T" : "F"; // Construct callback message. Format "SD|" const char *callback = concat("SD", callbackID); const char *header = concat(callback, "|"); const char *responseMessage = concat(header, result); // Send message to backend app->sendMessageToBackend(responseMessage); // Free memory MEMFREE(header); MEMFREE(callback); MEMFREE(responseMessage); ); } void createDelegate(struct Application *app) { // Define delegate Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0); bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate")); class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@"); class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@"); class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@"); // All Menu Items use a common callback class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@"); // Script handler class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@"); objc_registerClassPair(delegateClass); // Create delegate id delegate = msg((id)delegateClass, s("new")); objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN); // Theme change listener class_addMethod(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@"); // Get defaultCenter id defaultCenter = msg(c("NSDistributedNotificationCenter"), s("defaultCenter")); msg(defaultCenter, s("addObserver:selector:name:object:"), delegate, s("themeChanged:"), str("AppleInterfaceThemeChangedNotification"), NULL); app->delegate = delegate; msg(app->application, s("setDelegate:"), delegate); } void createMainWindow(struct Application *app) { // Create main window id mainWindow = ALLOC("NSWindow"); mainWindow = msg(mainWindow, s("initWithContentRect:styleMask:backing:defer:"), CGRectMake(0, 0, app->width, app->height), app->decorations, NSBackingStoreBuffered, NO); msg(mainWindow, s("autorelease")); // Set Appearance if( app->appearance != NULL ) { msg(mainWindow, s("setAppearance:"), msg(c("NSAppearance"), s("appearanceNamed:"), str(app->appearance)) ); } // Set Title appearance msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO); msg(mainWindow, s("setTitleVisibility:"), app->hideTitle); app->mainWindow = mainWindow; } const char* getInitialState(struct Application *app) { const char *result = ""; if( isDarkMode(app) ) { result = "window.wails.System.IsDarkMode.set(true);"; } else { result = "window.wails.System.IsDarkMode.set(false);"; } char buffer[999]; snprintf(&buffer[0], sizeof(buffer), "window.wails.System.LogLevel.set(%d);", app->logLevel); result = concat(result, &buffer[0]); Debug(app, "initialstate = %s", result); return result; } void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) { const char *roleName = item->string_; if ( STREQ(roleName, "appMenu") ) { createDefaultAppMenu(parentMenu); return; } if ( STREQ(roleName, "editMenu")) { createDefaultEditMenu(parentMenu); return; } if ( STREQ(roleName, "hide")) { addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE); return; } if ( STREQ(roleName, "hideothers")) { id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE); msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand)); return; } if ( STREQ(roleName, "unhide")) { addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE); return; } if ( STREQ(roleName, "front")) { addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE); return; } if ( STREQ(roleName, "undo")) { addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE); return; } if ( STREQ(roleName, "redo")) { addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE); return; } if ( STREQ(roleName, "cut")) { addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE); return; } if ( STREQ(roleName, "copy")) { addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE); return; } if ( STREQ(roleName, "paste")) { addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE); return; } if ( STREQ(roleName, "delete")) { addMenuItem(parentMenu, "Delete", "delete:", "", FALSE); return; } if( STREQ(roleName, "pasteandmatchstyle")) { id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE); msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand)); } if ( STREQ(roleName, "selectall")) { addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE); return; } if ( STREQ(roleName, "minimize")) { addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE); return; } if ( STREQ(roleName, "zoom")) { addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE); return; } if ( STREQ(roleName, "quit")) { addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE); return; } if ( STREQ(roleName, "togglefullscreen")) { addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE); return; } } id parseTextMenuItem(struct Application *app, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char *menuCallback) { id item = ALLOC("NSMenuItem"); id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid); msg(item, s("setRepresentedObject:"), wrappedId); id key = processAcceleratorKey(acceleratorkey); msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s(menuCallback), key); msg(item, s("setEnabled:"), !disabled); msg(item, s("autorelease")); // Process modifiers if( modifiers != NULL ) { unsigned long modifierFlags = parseModifiers(modifiers); msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags); } msg(parentMenu, s("addItem:"), item); return item; } id parseCheckboxMenuItem(struct Application *app, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key, struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction) { id item = ALLOC("NSMenuItem"); // Store the item in the menu item map hashmap_put(menuItemMap, (char*)menuid, strlen(menuid), item); id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid); msg(item, s("setRepresentedObject:"), wrappedId); msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s(checkboxCallbackFunction), str(key)); msg(item, s("setEnabled:"), !disabled); msg(item, s("autorelease")); msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff)); msg(parentmenu, s("addItem:"), item); return item; } id parseRadioMenuItem(struct Application *app, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey, struct hashmap_s *menuItemMap, const char *radioCallbackFunction) { id item = ALLOC("NSMenuItem"); // Store the item in the menu item map hashmap_put(menuItemMap, (char*)menuid, strlen(menuid), item); id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid); msg(item, s("setRepresentedObject:"), wrappedId); id key = processAcceleratorKey(acceleratorkey); msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s(radioCallbackFunction), key); msg(item, s("setEnabled:"), !disabled); msg(item, s("autorelease")); msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff)); msg(parentmenu, s("addItem:"), item); return item; } void parseMenuItem(struct Application *app, id parentMenu, JsonNode *item, struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char *radioCallbackFunction, const char *menuCallbackFunction) { // Check if this item is hidden and if so, exit early! bool hidden = false; getJSONBool(item, "Hidden", &hidden); if( hidden ) { return; } // Get the role JsonNode *role = json_find_member(item, "Role"); if( role != NULL ) { parseMenuRole(app, parentMenu, role); return; } // Check if this is a submenu JsonNode *submenu = json_find_member(item, "SubMenu"); if( submenu != NULL ) { // Get the label JsonNode *menuNameNode = json_find_member(item, "Label"); const char *name = ""; if ( menuNameNode != NULL) { name = menuNameNode->string_; } id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, ""); id thisMenu = createMenu(str(name)); msg(thisMenuItem, s("setSubmenu:"), thisMenu); msg(parentMenu, s("addItem:"), thisMenuItem); // Loop over submenu items JsonNode *item; json_foreach(item, submenu) { // Get item label parseMenuItem(app, thisMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction); } return; } // This is a user menu. Get the common data // Get the label const char *label = getJSONString(item, "Label"); if ( label == NULL) { label = "(empty)"; } const char *menuid = getJSONString(item, "ID"); if ( menuid == NULL) { menuid = ""; } bool disabled = false; getJSONBool(item, "Disabled", &disabled); // Get the Accelerator JsonNode *accelerator = json_find_member(item, "Accelerator"); const char *acceleratorkey = NULL; const char **modifiers = NULL; // If we have an accelerator if( accelerator != NULL ) { // Get the key acceleratorkey = getJSONString(accelerator, "Key"); // Check if there are modifiers JsonNode *modifiersList = json_find_member(accelerator, "Modifiers"); if ( modifiersList != NULL ) { // Allocate an array of strings int noOfModifiers = json_array_length(modifiersList); // Do we have any? if (noOfModifiers > 0) { modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1)); JsonNode *modifier; int count = 0; // Iterate the modifiers and save a reference to them in our new array json_foreach(modifier, modifiersList) { // Get modifier name modifiers[count] = modifier->string_; count++; } // Null terminate the modifier list modifiers[count] = NULL; } } } // Get the Type JsonNode *type = json_find_member(item, "Type"); if( type != NULL ) { if( STREQ(type->string_, "Text")) { parseTextMenuItem(app, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, menuCallbackFunction); } else if ( STREQ(type->string_, "Separator")) { addSeparator(parentMenu); } else if ( STREQ(type->string_, "Checkbox")) { // Get checked state bool checked = false; getJSONBool(item, "Checked", &checked); parseCheckboxMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, checkboxCallbackFunction); } else if ( STREQ(type->string_, "Radio")) { // Get checked state bool checked = false; getJSONBool(item, "Checked", &checked); parseRadioMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, radioCallbackFunction); } if ( modifiers != NULL ) { free(modifiers); } return; } } void parseMenu(struct Application *app, id parentMenu, JsonNode *menu, struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char *radioCallbackFunction, const char *menuCallbackFunction) { JsonNode *items = json_find_member(menu, "Items"); if( items == NULL ) { // Parse error! Fatal(app, "Unable to find Items in Menu"); return; } // Iterate items JsonNode *item; json_foreach(item, items) { // Get item label parseMenuItem(app, parentMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction); } } void dumpMemberList(const char *name, id *memberList) { void *member = memberList[0]; int count = 0; printf("%s = %p -> [ ", name, memberList); while( member != NULL ) { printf("%p ", member); count = count + 1; member = memberList[count]; } printf("]\n"); } void processRadioGroup(JsonNode *radioGroup, struct hashmap_s *menuItemMap, struct hashmap_s *radioGroupMap) { int groupLength; getJSONInt(radioGroup, "Length", &groupLength); JsonNode *members = json_find_member(radioGroup, "Members"); JsonNode *member; // Allocate array size_t arrayLength = sizeof(id)*(groupLength+1); id memberList[arrayLength]; // Build the radio group items int count=0; json_foreach(member, members) { // Get menu by id id menuItem = (id)hashmap_get(menuItemMap, (char*)member->string_, strlen(member->string_)); // Save Member memberList[count] = menuItem; count = count + 1; } // Null terminate array memberList[groupLength] = 0; // dumpMemberList("memberList", memberList); // Store the members json_foreach(member, members) { // Copy the memberList char *newMemberList = (char *)malloc(arrayLength); memcpy(newMemberList, memberList, arrayLength); // add group to each member of group hashmap_put(radioGroupMap, member->string_, strlen(member->string_), newMemberList); } } // updateMenu replaces the current menu with the given one void updateMenu(struct Application *app, const char *menuAsJSON) { Debug(app, "Menu is now: %s", menuAsJSON); ON_MAIN_THREAD ( DeleteMenu(app->applicationMenu); Menu* newMenu = NewApplicationMenu(menuAsJSON); id menu = GetMenu(newMenu); app->applicationMenu = newMenu; msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu); ); } // SetApplicationMenu sets the initial menu for the application void SetApplicationMenu(struct Application *app, const char *menuAsJSON) { if ( app->applicationMenu == NULL ) { app->applicationMenu = NewApplicationMenu(menuAsJSON); return; } // Update menu updateMenu(app, menuAsJSON); } void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) { unsigned int count = 0; while( 1 ) { const unsigned char *name = dialogIcons[count++]; if( name == 0x00 ) { break; } const unsigned char *lengthAsString = dialogIcons[count++]; if( name == 0x00 ) { break; } const unsigned char *data = dialogIcons[count++]; if( data == 0x00 ) { break; } int length = atoi((const char *)lengthAsString); // Create the icon and add to the hashmap id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length); id dialogImage = ALLOC("NSImage"); msg(dialogImage, s("initWithData:"), imageData); hashmap_put(hashmap, (const char *)name, strlen((const char *)name), dialogImage); } } void processUserDialogIcons(struct Application *app) { // Allocate the Dialog icon hashmap if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) { // Couldn't allocate map Fatal(app, "Not enough memory to allocate dialogIconCache!"); return; } processDialogIcons(&dialogIconCache, defaultDialogIcons); processDialogIcons(&dialogIconCache, userDialogIcons); } void Run(struct Application *app, int argc, char **argv) { // Process window decorations processDecorations(app); // Create the application createApplication(app); // Define delegate createDelegate(app); // Create the main window createMainWindow(app); // Create Content View id contentView = msg( ALLOC("NSView"), s("init") ); msg(app->mainWindow, s("setContentView:"), contentView); // Set the main window title SetTitle(app, app->title); // Center Window Center(app); // Set Colour applyWindowColour(app); // Process translucency if (app->windowBackgroundIsTranslucent) { makeWindowBackgroundTranslucent(app); } // We set it to be invisible by default. It will become visible when everything has initialised msg(app->mainWindow, s("setIsVisible:"), NO); // Setup webview id config = msg(c("WKWebViewConfiguration"), s("new")); msg(config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 1), str("suppressesIncrementalRendering")); if (app->devtools) { Debug(app, "Enabling devtools"); enableBoolConfig(config, "developerExtrasEnabled"); } app->config = config; id manager = msg(config, s("userContentController")); msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("external")); msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("completed")); app->manager = manager; id wkwebview = msg(c("WKWebView"), s("alloc")); app->wkwebview = wkwebview; msg(wkwebview, s("initWithFrame:configuration:"), CGRectMake(0, 0, 0, 0), config); msg(contentView, s("addSubview:"), wkwebview); msg(wkwebview, s("setAutoresizingMask:"), NSViewWidthSizable | NSViewHeightSizable); CGRect contentViewBounds = GET_BOUNDS(contentView); msg(wkwebview, s("setFrame:"), contentViewBounds ); // Disable damn smart quotes // Credit: https://stackoverflow.com/a/31640511 id userDefaults = msg(c("NSUserDefaults"), s("standardUserDefaults")); msg(userDefaults, s("setBool:forKey:"), NO, str("NSAutomaticQuoteSubstitutionEnabled")); // Setup drag message handler msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("windowDrag")); // Add mouse event hooks app->mouseDownMonitor = msg(c("NSEvent"), u("addLocalMonitorForEventsMatchingMask:handler:"), NSEventMaskLeftMouseDown, ^(id incomingEvent) { // Make sure the mouse click was in the window, not the tray id window = msg(incomingEvent, s("window")); if (window == app->mainWindow) { app->mouseEvent = incomingEvent; } return incomingEvent; }); app->mouseUpMonitor = msg(c("NSEvent"), u("addLocalMonitorForEventsMatchingMask:handler:"), NSEventMaskLeftMouseUp, ^(id incomingEvent) { app->mouseEvent = NULL; ShowMouse(); return incomingEvent; }); // Setup context menu message handler msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("contextMenu")); // Toolbar if( app->useToolBar ) { Debug(app, "Setting Toolbar"); id toolbar = msg(c("NSToolbar"),s("alloc")); msg(toolbar, s("initWithIdentifier:"), str("wails.toolbar")); msg(toolbar, s("autorelease")); // Separator if( app->hideToolbarSeparator ) { msg(toolbar, s("setShowsBaselineSeparator:"), NO); } msg(app->mainWindow, s("setToolbar:"), toolbar); } // Fix up resizing if (app->resizable == 0) { app->minHeight = app->maxHeight = app->height; app->minWidth = app->maxWidth = app->width; } setMinMaxSize(app); // Load HTML id html = msg(c("NSURL"), s("URLWithString:"), str(assets[0])); msg(wkwebview, s("loadRequest:"), msg(c("NSURLRequest"), s("requestWithURL:"), html)); Debug(app, "Loading Internal Code"); // We want to evaluate the internal code plus runtime before the assets const char *temp = concat(invoke, app->bindings); const char *internalCode = concat(temp, (const char*)&runtime); MEMFREE(temp); // Add code that sets up the initial state, EG: State Stores. temp = concat(internalCode, getInitialState(app)); MEMFREE(internalCode); internalCode = temp; // Loop over assets and build up one giant Mother Of All Evals int index = 1; while(1) { // Get next asset pointer const unsigned char *asset = assets[index]; // If we have no more assets, break if (asset == 0x00) { break; } temp = concat(internalCode, (const char *)asset); MEMFREE(internalCode); internalCode = temp; index++; }; // Disable context menu if not in debug mode if( debug != 1 ) { temp = concat(internalCode, "wails._.DisableDefaultContextMenu();"); MEMFREE(internalCode); internalCode = temp; } // class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "@@:@"); // Include callback after evaluation temp = concat(internalCode, "webkit.messageHandlers.completed.postMessage(true);"); MEMFREE(internalCode); internalCode = temp; // const char *viewportScriptString = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); meta.setAttribute('initial-scale', '1.0'); meta.setAttribute('maximum-scale', '1.0'); meta.setAttribute('minimum-scale', '1.0'); meta.setAttribute('user-scalable', 'no'); document.getElementsByTagName('head')[0].appendChild(meta);"; // ExecJS(app, viewportScriptString); // This evaluates the MOAE once the Dom has finished loading msg(manager, s("addUserScript:"), msg(msg(c("WKUserScript"), s("alloc")), s("initWithSource:injectionTime:forMainFrameOnly:"), str(internalCode), 1, 1)); // Emit theme change event to notify of current system them emitThemeChange(app); // If we want the webview to be transparent... if( app->webviewIsTranparent == 1 ) { msg(wkwebview, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("drawsBackground")); } // If we have an application menu, process it if( app->applicationMenu != NULL ) { id menu = GetMenu(app->applicationMenu); msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu); } // Setup initial trays ShowTrayMenusInStore(app->trayMenuStore); // Process dialog icons processUserDialogIcons(app); // Finally call run Debug(app, "Run called"); msg(app->application, s("run")); MEMFREE(internalCode); } void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) { // Load the tray icons LoadTrayIcons(); // Setup main application struct struct Application *result = malloc(sizeof(struct Application)); result->title = title; result->width = width; result->height = height; result->minWidth = 0; result->minHeight = 0; result->maxWidth = 0; result->maxHeight = 0; result->resizable = resizable; result->devtools = devtools; result->fullscreen = fullscreen; result->maximised = 0; result->startHidden = startHidden; result->decorations = 0; result->logLevel = logLevel; result->mainWindow = NULL; result->mouseEvent = NULL; result->mouseDownMonitor = NULL; result->mouseUpMonitor = NULL; // Features result->frame = 1; result->hideTitle = 0; result->hideTitleBar = 0; result->fullSizeContent = 0; result->useToolBar = 0; result->hideToolbarSeparator = 0; result->appearance = NULL; result->windowBackgroundIsTranslucent = 0; // Window data result->delegate = NULL; // Menu result->applicationMenu = NULL; // Tray result->trayMenuStore = NewTrayMenuStore(); // Context Menus result->contextMenuStore = NewContextMenuStore(); // Window Appearance result->titlebarAppearsTransparent = 0; result->webviewIsTranparent = 0; result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback; return (void*) result; } #endif