From a0774cf71c3e4c3c772b60cc47e2bc5bdc14da77 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 23 Dec 2020 06:55:45 +1100 Subject: [PATCH] Support loading multiple tray icons --- v2/internal/ffenestri/ffenestri_darwin.c | 72 +++++++++++++++++--- v2/internal/ffenestri/ffenestri_darwin.go | 2 +- v2/internal/ffenestri/ffenestri_darwin.h | 2 +- v2/pkg/commands/build/desktop_darwin.go | 78 ++++++++++++++++------ v2/pkg/menu/trayoptions.go | 16 ++++- v2/test/kitchensink/main.go | 1 + v2/test/kitchensink/trayicon.png | Bin 2459 -> 0 bytes 7 files changed, 136 insertions(+), 35 deletions(-) delete mode 100644 v2/test/kitchensink/trayicon.png 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 a46bbdd85d610ead14967874172003f4a622a718..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2459 zcmai0c~}!?8XsM5k>xB@5Qfl-h~(l<6Cxp;B^(jts4K=~0#lMq$P9!auB%d9P^2iX z8qtb8fIJ{|fu&onw1QEpPeH9>%OVv8#3(8h%7ajL5-8Sd_m4^PeZSxPd%ySi-bq1B zROo7xH6{Q6tPT%jbMbF;?PFwse>zPr@Bl#XD>yC@OXNh-_=uRu6CgVvVzyX{;{ZTs zW=nbebO-}?K&h~VL3rA#B7m@fK}hiCkU7#IC=CwFkwKetqT={D>3n|yff-;z&!*u5 zVhH1b*ZN?Laay27!R%;F3573ty^B(53M4M3A%yi9#fk{z!+i;djur$QyKN8iFBc8X{c| z;1w6k@EeAB`rdAm&6B>3%;EfRx>)>57$_E;g(qSusjr;rcL!*kLJE<%5Q=2V_)u^b zB*9#Cnb78i7B1$cLXnUp6-#3fC_b+N39#VxeXWovQ(K9635>g@zBEaEi7^PiRJ;eY z_0hRWdMl(&H7y3thD3>MSPV%}Ty^;rFTtdDfI)}|k!{A?8p4fqzLx7i(_tJ$fJ2_I zLE%9`F)~C5i*O9(hO)r$;2FXK`KK8nS4l~3k#Q9L{UVD$$2s;FcmK_gAgbb!gzfYV2rkN!Szu{76|%) z+DP19HI%18xahk>BrP3~q`i2*pO8*^BL=#T@Q0g<+b!)<^AuwpNaSLcnY0Koc^%#t2=cC7Vfre}~#NJU~|VkE1kJg}gMY8i3EZn#f4 za>BP_q^NJFC)^OAbXMXp1Pi*7y3*ORr z<3FcW9kp?^aZKq}7Jijy-xMc`oniZ>b?bkIlsb~0Hx3C3m^PnWzheGxP2Qinog5av zP5zFWH$2>AH&!;i%W29oh2Y8f@av>4Rz4OXMpoMo^70>69{6iPJJ#!AmRTU*-M7DR z%4*}s=ht2Tp!kwyI9Fg?s5~-m+}-lf_yj!h-NInUPdU-@npTqO(c(jsKbhR+tQYxx ze|gV+YqM`BfzcDP`H6sE+6y{jZzoTGqcW(rg*-Y|r)qk$5p`W+?8FtBhSuf~^km}? zDS=bD-HlIo9U2E)Hl$=4f7IH$ZghTC%8rH92W!vVu)yCM#?r7P6aZFPX`dB88M^I+-O`jwI}=g&pK=WY!t^=2=KYV=)-7@jvy8CJLt^am^VIt@-s z%|>%a!Ak3`SsIN-JvTQeKM^_diTcjb&dwk*$4aeMZ!#*uS_ceIMU4{MOIHIZir&oU z<*M!Mi_aQ0{J8OWl`%KsmS)P){}5VzbL{cGt5@vp?YkQ4dMN5X3s#=_%CEPYacit+ zI#i=ASh#?+3V5_&z)fH{2-@VC5p`llmKH?wj z45^@;+fvJ~XH}o7?Ha`|YM(!U-VbcwPniDnA>C-`ooi~{bShdwP`oNFZ^)Z+RGVWUYd#XxbK>xlmnNB;`^JK&_SDvyNo)>WO0TZ2Hf*V9{)5{=3kV3PyY=bak&!BTv;i<<8*G{C%A1Us)BdLl4~}Bj Iv$pU3FJ6AQtN;K2