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:
parent
16b872352d
commit
a0774cf71c
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 |
Loading…
Reference in New Issue
Block a user