diff --git a/v2/internal/ffenestri/ffenestri_darwin.c b/v2/internal/ffenestri/ffenestri_darwin.c index 3dbe7c425..a6a95c152 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.c +++ b/v2/internal/ffenestri/ffenestri_darwin.c @@ -6,6 +6,7 @@ #include #include "json.h" #include "hashmap.h" +#include "stdlib.h" // Macros to make it slightly more sane #define msg objc_msgSend @@ -80,8 +81,7 @@ extern const unsigned char *assets[]; extern const unsigned char runtime; // Tray icon -extern const unsigned int trayIconLength; -extern const unsigned char *trayIcon[]; +extern const unsigned char *trayIcons[]; // MAIN DEBUG FLAG int debug; @@ -98,6 +98,9 @@ struct hashmap_s menuItemMapForTrayMenu; // RadioGroup map for the tray menu. Maps a menuitem id with its associated radio group items struct hashmap_s radioGroupMapForTrayMenu; +// A cache for all our tray icons +struct hashmap_s trayIconCache; + // contextMenuMap is a hashmap of context menus keyed on a string ID struct hashmap_s contextMenuMap; @@ -215,6 +218,7 @@ struct Application { const char *trayMenuAsJSON; const char *trayType; const char *trayLabel; + const char *trayIconName; JsonNode *processedTrayMenu; id statusItem; @@ -730,6 +734,13 @@ void allocateTrayHashMaps(struct Application *app) { Fatal(app, "Not enough memory to allocate radioGroupMapForTrayMenu!"); return; } + + // Allocate the Tray Icons + if( 0 != hashmap_create((const unsigned)4, &trayIconCache)) { + // Couldn't allocate map + Fatal(app, "Not enough memory to allocate radioGroupMapForTrayMenu!"); + return; + } } void allocateContextMenuHashMaps(struct Application *app) { @@ -798,6 +809,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in result->trayMenuAsJSON = NULL; result->trayType = NULL; result->trayLabel = NULL; + result->trayIconName = NULL; result->processedTrayMenu = NULL; result->statusItem = NULL; @@ -819,7 +831,7 @@ int freeHashmapItem(void *const context, struct hashmap_element_s *const e) { return -1; } -int freeNSMenu(void *const context, struct hashmap_element_s *const e) { +int releaseNSObject(void *const context, struct hashmap_element_s *const e) { msg(e->data, s("release")); return -1; } @@ -873,7 +885,7 @@ void destroyContextMenus(struct Application *app) { // Free context menus if( hashmap_num_entries(&contextMenuMap) > 0 ) { - if (0!=hashmap_iterate_pairs(&contextMenuMap, freeNSMenu, NULL)) { + if (0!=hashmap_iterate_pairs(&contextMenuMap, releaseNSObject, NULL)) { Fatal(app, "failed to deallocate hashmap entries!"); } } @@ -892,6 +904,16 @@ void destroyContextMenus(struct Application *app) { void destroyTray(struct Application *app) { + // Release the tray cache images + if( hashmap_num_entries(&trayIconCache) > 0 ) { + if (0!=hashmap_iterate_pairs(&radioGroupMapForApplicationMenu, releaseNSObject, NULL)) { + Fatal(app, "failed to release hashmap entries!"); + } + } + + //Free radio groups hashmap + hashmap_destroy(&trayIconCache); + // If we don't have a tray, exit! if( app->trayMenuAsJSON == NULL ) { return; @@ -1306,10 +1328,11 @@ void SetMenu(struct Application *app, const char *menuAsJSON) { } // SetTray sets the initial tray menu for the application -void SetTray(struct Application *app, const char *trayMenuAsJSON, const char *trayType, const char *trayLabel) { +void SetTray(struct Application *app, const char *trayMenuAsJSON, const char *trayType, const char *trayLabel, const char *trayIconName) { app->trayMenuAsJSON = trayMenuAsJSON; app->trayType = trayType; app->trayLabel = trayLabel; + app->trayIconName = trayIconName; } // SetContextMenus sets the context menu map for this application @@ -2228,11 +2251,42 @@ void UpdateTrayLabel(struct Application *app, const char *label) { msg(statusBarButton, s("setTitle:"), str(label)); } +void processTrayIconData(struct Application *app) { + + unsigned int count = 0; + while( 1 ) { + const unsigned char *name = trayIcons[count++]; + if( name == 0x00 ) { + break; + } + const unsigned char *lengthAsString = trayIcons[count++]; + if( name == 0x00 ) { + break; + } + const unsigned char *data = trayIcons[count++]; + if( data == 0x00 ) { + break; + } + int length = atoi((const char *)lengthAsString); + printf("Got tray name: %s with data %p\n", name, data); + printf("Length = %d\n", length); + + // Create the icon and add to the hashmap + id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length); + id trayImage = ALLOC("NSImage"); + msg(trayImage, s("initWithData:"), imageData); + hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage); + } +} + void parseTrayData(struct Application *app) { // Allocate the hashmaps we need allocateTrayHashMaps(app); + // Process Tray icon data + processTrayIconData(app); + // Create a new menu id traymenu = createMenu(str("")); @@ -2247,11 +2301,9 @@ void parseTrayData(struct Application *app) { id statusBarButton = msg(statusItem, s("button")); if( STREQ(app->trayType, "icon") ) { - // If we have a tray icon - if ( trayIconLength > 0 ) { - id imageData = msg(c("NSData"), s("dataWithBytes:length:"), trayIcon, trayIconLength); - id trayImage = ALLOC("NSImage"); - msg(trayImage, s("initWithData:"), imageData); + // Get the icon + if ( app->trayIconName != NULL ) { + id trayImage = hashmap_get(&trayIconCache, app->trayIconName, strlen(app->trayIconName)); msg(statusBarButton, s("setImage:"), trayImage); } } diff --git a/v2/internal/ffenestri/ffenestri_darwin.go b/v2/internal/ffenestri/ffenestri_darwin.go index 8d4a7d6e0..afa162b67 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.go +++ b/v2/internal/ffenestri/ffenestri_darwin.go @@ -99,7 +99,7 @@ func (a *Application) processPlatformSettings() error { if err != nil { return err } - C.SetTray(a.app, a.string2CString(string(trayMenuJSON)), a.string2CString(string(tray.Type)), a.string2CString(tray.Label)) + C.SetTray(a.app, a.string2CString(string(trayMenuJSON)), a.string2CString(string(tray.Type)), a.string2CString(tray.Label), a.string2CString(tray.Icon)) } // Process context menus diff --git a/v2/internal/ffenestri/ffenestri_darwin.h b/v2/internal/ffenestri/ffenestri_darwin.h index 3ca846044..76260164d 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.h +++ b/v2/internal/ffenestri/ffenestri_darwin.h @@ -13,7 +13,7 @@ extern void SetAppearance(void *, const char *); extern void WebviewIsTransparent(void *); extern void WindowBackgroundIsTranslucent(void *); extern void SetMenu(void *, const char *); -extern void SetTray(void *, const char *, const char *, const char *); +extern void SetTray(void *, const char *, const char *, const char *, const char *); extern void SetContextMenus(void *, const char *); #endif \ No newline at end of file diff --git a/v2/pkg/commands/build/desktop_darwin.go b/v2/pkg/commands/build/desktop_darwin.go index 020bbf0d5..42940bf9f 100644 --- a/v2/pkg/commands/build/desktop_darwin.go +++ b/v2/pkg/commands/build/desktop_darwin.go @@ -4,55 +4,91 @@ package build import ( "fmt" - "github.com/wailsapp/wails/v2/internal/fs" + "github.com/leaanthony/slicer" "io/ioutil" + "log" "path/filepath" + "strconv" "strings" ) +func (d *DesktopBuilder) convertToHexLiteral(bytes []byte) string { + result := "" + for _, b := range bytes { + result += fmt.Sprintf("0x%x, ", b) + } + return result +} + // desktop_linux.go will compile the tray icon found at /trayicon.png into the application func (d *DesktopBuilder) processTrayIcons(assetDir string, options *Options) error { - // Determine icon file - iconFile := filepath.Join(options.ProjectData.Path, "trayicon.png") - var err error + // Get all the tray icon filenames + trayIconDirectory := filepath.Join(options.ProjectData.Path, "trayicons") + trayIconFilenames, err := filepath.Glob(trayIconDirectory + "/*.png") + + if err != nil { + log.Fatal(err) + return err + } + // Setup target - targetFilename := "trayicon" + targetFilename := "trayicons" targetFile := filepath.Join(assetDir, targetFilename+".c") d.addFileToDelete(targetFile) var dataBytes []byte - // If the icon file exists, load it up - if fs.FileExists(iconFile) { - // Load the tray icon - dataBytes, err = ioutil.ReadFile(iconFile) - if err != nil { - return err - } - } - // Use a strings builder var cdata strings.Builder // Write header - header := `// trayicon.c + header := `// trayicons.c // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL. // This file was auto-generated. DO NOT MODIFY. ` cdata.WriteString(header) - cdata.WriteString(fmt.Sprintf("const unsigned int trayIconLength = %d;\n", len(dataBytes))) - cdata.WriteString("const unsigned char trayIcon[] = { ") - // Convert each byte to hex - for _, b := range dataBytes { - cdata.WriteString(fmt.Sprintf("0x%x, ", b)) + var variableList slicer.StringSlicer + + // Loop over icons + for count, filename := range trayIconFilenames { + + // Load the tray icon + dataBytes, err = ioutil.ReadFile(filename) + if err != nil { + return err + } + + iconname := strings.TrimSuffix(filepath.Base(filename), ".png") + trayIconName := fmt.Sprintf("trayIcon%dName", count) + variableList.Add(trayIconName) + cdata.WriteString(fmt.Sprintf("const unsigned char %s[] = { %s0x00 };\n", trayIconName, d.convertToHexLiteral([]byte(iconname)))) + + trayIconLength := fmt.Sprintf("trayIcon%dLength", count) + variableList.Add(trayIconLength) + lengthAsString := strconv.Itoa(len(dataBytes)) + cdata.WriteString(fmt.Sprintf("const unsigned char %s[] = { %s0x00 };\n", trayIconLength, d.convertToHexLiteral([]byte(lengthAsString)))) + + trayIconData := fmt.Sprintf("trayIcon%dData", count) + variableList.Add(trayIconData) + cdata.WriteString(fmt.Sprintf("const unsigned char %s[] = { ", trayIconData)) + + // Convert each byte to hex + for _, b := range dataBytes { + cdata.WriteString(fmt.Sprintf("0x%x, ", b)) + } + + cdata.WriteString("0x00 };\n") } - cdata.WriteString("0x00 };\n") + // Write out main trayIcons data + cdata.WriteString("const unsigned char *trayIcons[] = { ") + cdata.WriteString(variableList.Join(", ")) + cdata.WriteString(", 0x00 };\n") err = ioutil.WriteFile(targetFile, []byte(cdata.String()), 0600) if err != nil { diff --git a/v2/pkg/menu/trayoptions.go b/v2/pkg/menu/trayoptions.go index a0c7372ec..a45c9d39d 100644 --- a/v2/pkg/menu/trayoptions.go +++ b/v2/pkg/menu/trayoptions.go @@ -7,8 +7,20 @@ const ( TrayLabel TrayType = "label" ) +// TrayOptions are the options type TrayOptions struct { - Type TrayType + // Type is the type of tray item we want + Type TrayType + + // Label is what is displayed initially when the type is TrayLabel Label string - Menu *Menu + + // Icon is the name of the tray icon we wish to display. + // These are read up during build from /trayicons and + // the filenames are used as IDs, minus the extension + // EG: /trayicons/main.png can be referenced here with "main" + Icon string + + // Menu is the initial menu we wish to use for the tray + Menu *Menu } diff --git a/v2/test/kitchensink/main.go b/v2/test/kitchensink/main.go index fa8689e59..c720eb2fc 100644 --- a/v2/test/kitchensink/main.go +++ b/v2/test/kitchensink/main.go @@ -32,6 +32,7 @@ func main() { //Type: menu.TrayLabel, Type: menu.TrayIcon, Label: "Hi Go BitBar!", + Icon: "main", Menu: createApplicationTray(), }, }, diff --git a/v2/test/kitchensink/trayicon.png b/v2/test/kitchensink/trayicon.png deleted file mode 100644 index a46bbdd85..000000000 Binary files a/v2/test/kitchensink/trayicon.png and /dev/null differ