5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 01:19:12 +08:00

Support loading multiple tray icons

This commit is contained in:
Lea Anthony 2020-12-23 06:55:45 +11:00
parent 16b872352d
commit a0774cf71c
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
7 changed files with 136 additions and 35 deletions

View File

@ -6,6 +6,7 @@
#include <CoreGraphics/CoreGraphics.h> #include <CoreGraphics/CoreGraphics.h>
#include "json.h" #include "json.h"
#include "hashmap.h" #include "hashmap.h"
#include "stdlib.h"
// Macros to make it slightly more sane // Macros to make it slightly more sane
#define msg objc_msgSend #define msg objc_msgSend
@ -80,8 +81,7 @@ extern const unsigned char *assets[];
extern const unsigned char runtime; extern const unsigned char runtime;
// Tray icon // Tray icon
extern const unsigned int trayIconLength; extern const unsigned char *trayIcons[];
extern const unsigned char *trayIcon[];
// MAIN DEBUG FLAG // MAIN DEBUG FLAG
int debug; 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 // RadioGroup map for the tray menu. Maps a menuitem id with its associated radio group items
struct hashmap_s radioGroupMapForTrayMenu; 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 // contextMenuMap is a hashmap of context menus keyed on a string ID
struct hashmap_s contextMenuMap; struct hashmap_s contextMenuMap;
@ -215,6 +218,7 @@ struct Application {
const char *trayMenuAsJSON; const char *trayMenuAsJSON;
const char *trayType; const char *trayType;
const char *trayLabel; const char *trayLabel;
const char *trayIconName;
JsonNode *processedTrayMenu; JsonNode *processedTrayMenu;
id statusItem; id statusItem;
@ -730,6 +734,13 @@ void allocateTrayHashMaps(struct Application *app) {
Fatal(app, "Not enough memory to allocate radioGroupMapForTrayMenu!"); Fatal(app, "Not enough memory to allocate radioGroupMapForTrayMenu!");
return; 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) { 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->trayMenuAsJSON = NULL;
result->trayType = NULL; result->trayType = NULL;
result->trayLabel = NULL; result->trayLabel = NULL;
result->trayIconName = NULL;
result->processedTrayMenu = NULL; result->processedTrayMenu = NULL;
result->statusItem = NULL; result->statusItem = NULL;
@ -819,7 +831,7 @@ int freeHashmapItem(void *const context, struct hashmap_element_s *const e) {
return -1; 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")); msg(e->data, s("release"));
return -1; return -1;
} }
@ -873,7 +885,7 @@ void destroyContextMenus(struct Application *app) {
// Free context menus // Free context menus
if( hashmap_num_entries(&contextMenuMap) > 0 ) { 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!"); Fatal(app, "failed to deallocate hashmap entries!");
} }
} }
@ -892,6 +904,16 @@ void destroyContextMenus(struct Application *app) {
void destroyTray(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 we don't have a tray, exit!
if( app->trayMenuAsJSON == NULL ) { if( app->trayMenuAsJSON == NULL ) {
return; return;
@ -1306,10 +1328,11 @@ void SetMenu(struct Application *app, const char *menuAsJSON) {
} }
// SetTray sets the initial tray menu for the application // 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->trayMenuAsJSON = trayMenuAsJSON;
app->trayType = trayType; app->trayType = trayType;
app->trayLabel = trayLabel; app->trayLabel = trayLabel;
app->trayIconName = trayIconName;
} }
// SetContextMenus sets the context menu map for this application // 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)); 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) { void parseTrayData(struct Application *app) {
// Allocate the hashmaps we need // Allocate the hashmaps we need
allocateTrayHashMaps(app); allocateTrayHashMaps(app);
// Process Tray icon data
processTrayIconData(app);
// Create a new menu // Create a new menu
id traymenu = createMenu(str("")); id traymenu = createMenu(str(""));
@ -2247,11 +2301,9 @@ void parseTrayData(struct Application *app) {
id statusBarButton = msg(statusItem, s("button")); id statusBarButton = msg(statusItem, s("button"));
if( STREQ(app->trayType, "icon") ) { if( STREQ(app->trayType, "icon") ) {
// If we have a tray icon // Get the icon
if ( trayIconLength > 0 ) { if ( app->trayIconName != NULL ) {
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), trayIcon, trayIconLength); id trayImage = hashmap_get(&trayIconCache, app->trayIconName, strlen(app->trayIconName));
id trayImage = ALLOC("NSImage");
msg(trayImage, s("initWithData:"), imageData);
msg(statusBarButton, s("setImage:"), trayImage); msg(statusBarButton, s("setImage:"), trayImage);
} }
} }

View File

@ -99,7 +99,7 @@ func (a *Application) processPlatformSettings() error {
if err != nil { if err != nil {
return err 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 // Process context menus

View File

@ -13,7 +13,7 @@ extern void SetAppearance(void *, const char *);
extern void WebviewIsTransparent(void *); extern void WebviewIsTransparent(void *);
extern void WindowBackgroundIsTranslucent(void *); extern void WindowBackgroundIsTranslucent(void *);
extern void SetMenu(void *, const char *); 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 *); extern void SetContextMenus(void *, const char *);
#endif #endif

View File

@ -4,55 +4,91 @@ package build
import ( import (
"fmt" "fmt"
"github.com/wailsapp/wails/v2/internal/fs" "github.com/leaanthony/slicer"
"io/ioutil" "io/ioutil"
"log"
"path/filepath" "path/filepath"
"strconv"
"strings" "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 <projectdir>/trayicon.png into the application // desktop_linux.go will compile the tray icon found at <projectdir>/trayicon.png into the application
func (d *DesktopBuilder) processTrayIcons(assetDir string, options *Options) error { func (d *DesktopBuilder) processTrayIcons(assetDir string, options *Options) error {
// Determine icon file
iconFile := filepath.Join(options.ProjectData.Path, "trayicon.png")
var err error 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 // Setup target
targetFilename := "trayicon" targetFilename := "trayicons"
targetFile := filepath.Join(assetDir, targetFilename+".c") targetFile := filepath.Join(assetDir, targetFilename+".c")
d.addFileToDelete(targetFile) d.addFileToDelete(targetFile)
var dataBytes []byte 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 // Use a strings builder
var cdata strings.Builder var cdata strings.Builder
// Write header // Write header
header := `// trayicon.c header := `// trayicons.c
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL. // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL.
// This file was auto-generated. DO NOT MODIFY. // This file was auto-generated. DO NOT MODIFY.
` `
cdata.WriteString(header) 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 var variableList slicer.StringSlicer
for _, b := range dataBytes {
cdata.WriteString(fmt.Sprintf("0x%x, ", b)) // 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) err = ioutil.WriteFile(targetFile, []byte(cdata.String()), 0600)
if err != nil { if err != nil {

View File

@ -7,8 +7,20 @@ const (
TrayLabel TrayType = "label" TrayLabel TrayType = "label"
) )
// TrayOptions are the options
type TrayOptions struct { 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 Label string
Menu *Menu
// Icon is the name of the tray icon we wish to display.
// These are read up during build from <projectdir>/trayicons and
// the filenames are used as IDs, minus the extension
// EG: <projectdir>/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
} }

View File

@ -32,6 +32,7 @@ func main() {
//Type: menu.TrayLabel, //Type: menu.TrayLabel,
Type: menu.TrayIcon, Type: menu.TrayIcon,
Label: "Hi Go BitBar!", Label: "Hi Go BitBar!",
Icon: "main",
Menu: createApplicationTray(), Menu: createApplicationTray(),
}, },
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB