mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
[v3 mac] Window alignment to systray
This commit is contained in:
parent
90e66a7ad4
commit
16ce9e562f
5 changed files with 219 additions and 93 deletions
|
|
@ -68,9 +68,11 @@ func main() {
|
|||
app.Quit()
|
||||
})
|
||||
|
||||
systemTray.SetMenu(myMenu)
|
||||
if runtime.GOOS != "darwin" {
|
||||
systemTray.SetMenu(myMenu)
|
||||
}
|
||||
|
||||
systemTray.OnClick(func() {
|
||||
showWindow := func() {
|
||||
window.SetTitle(fmt.Sprintf("Clicked %d times", clickCount()))
|
||||
err := systemTray.PositionWindow(window)
|
||||
if err != nil {
|
||||
|
|
@ -78,7 +80,8 @@ func main() {
|
|||
return
|
||||
}
|
||||
window.Show().Focus()
|
||||
})
|
||||
}
|
||||
systemTray.OnClick(showWindow)
|
||||
|
||||
err := app.Run()
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,28 @@ Screen getScreenForWindow(void* window){
|
|||
return processScreen(screen);
|
||||
}
|
||||
|
||||
// Get the screen for the system tray
|
||||
Screen getScreenForSystemTray(void* nsStatusItem) {
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
NSRect frame = statusItem.button.frame;
|
||||
NSArray<NSScreen *> *screens = NSScreen.screens;
|
||||
NSScreen *associatedScreen = nil;
|
||||
|
||||
for (NSScreen *screen in screens) {
|
||||
if (NSPointInRect(frame.origin, screen.frame)) {
|
||||
associatedScreen = screen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return processScreen(associatedScreen);
|
||||
}
|
||||
|
||||
void* getWindowForSystray(void* nsStatusItem) {
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
return statusItem.button.window;
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
|
@ -149,3 +171,11 @@ func getScreenForWindow(window *macosWebviewWindow) (*Screen, error) {
|
|||
cScreen := C.getScreenForWindow(window.nsWindow)
|
||||
return cScreenToScreen(cScreen), nil
|
||||
}
|
||||
|
||||
func getScreenForSystray(systray *macosSystemTray) (*Screen, error) {
|
||||
// Get the Window for the status item
|
||||
// https://stackoverflow.com/a/5875019/4188138
|
||||
window := C.getWindowForSystray(systray.nsStatusItem)
|
||||
cScreen := C.getScreenForWindow(window)
|
||||
return cScreenToScreen(cScreen), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,91 +8,7 @@ package application
|
|||
|
||||
#include "Cocoa/Cocoa.h"
|
||||
#include "menuitem_darwin.h"
|
||||
|
||||
// Create a new system tray
|
||||
void* systemTrayNew() {
|
||||
NSStatusItem *statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain];
|
||||
return (void*)statusItem;
|
||||
}
|
||||
|
||||
void systemTraySetLabel(void* nsStatusItem, char *label) {
|
||||
if( label == NULL ) {
|
||||
return;
|
||||
}
|
||||
// Set the label on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
statusItem.button.title = [NSString stringWithUTF8String:label];
|
||||
free(label);
|
||||
});
|
||||
}
|
||||
|
||||
// Create an nsimage from a byte array
|
||||
NSImage* imageFromBytes(const unsigned char *bytes, int length) {
|
||||
NSData *data = [NSData dataWithBytes:bytes length:length];
|
||||
NSImage *image = [[NSImage alloc] initWithData:data];
|
||||
return image;
|
||||
}
|
||||
|
||||
// Set the icon on the system tray
|
||||
void systemTraySetIcon(void* nsStatusItem, void* nsImage, int position, bool isTemplate) {
|
||||
// Set the icon on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
NSImage *image = (NSImage *)nsImage;
|
||||
|
||||
NSStatusBar *statusBar = [NSStatusBar systemStatusBar];
|
||||
CGFloat thickness = [statusBar thickness];
|
||||
[image setSize:NSMakeSize(thickness, thickness)];
|
||||
if( isTemplate ) {
|
||||
[image setTemplate:YES];
|
||||
}
|
||||
statusItem.button.image = [image autorelease];
|
||||
statusItem.button.imagePosition = position;
|
||||
});
|
||||
}
|
||||
|
||||
// Add menu to system tray
|
||||
void systemTraySetMenu(void* nsStatusItem, void* nsMenu) {
|
||||
// Set the menu on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
NSMenu *menu = (NSMenu *)nsMenu;
|
||||
statusItem.menu = menu;
|
||||
});
|
||||
}
|
||||
|
||||
// Destroy system tray
|
||||
void systemTrayDestroy(void* nsStatusItem) {
|
||||
// Remove the status item from the status bar and its associated menu
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
[[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
|
||||
[statusItem release];
|
||||
});
|
||||
}
|
||||
|
||||
void systemTrayGetBounds(void* nsStatusItem, NSRect *rect) {
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
*rect = statusItem.button.window.frame;
|
||||
}
|
||||
|
||||
// Get the screen for the system tray
|
||||
NSScreen* getScreenForSystemTray(void* nsStatusItem) {
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
NSRect frame = statusItem.button.frame;
|
||||
NSArray<NSScreen *> *screens = NSScreen.screens;
|
||||
NSScreen *associatedScreen = nil;
|
||||
|
||||
for (NSScreen *screen in screens) {
|
||||
if (NSPointInRect(frame.origin, screen.frame)) {
|
||||
associatedScreen = screen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return associatedScreen;
|
||||
}
|
||||
|
||||
#include "systemtray_darwin.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
@ -110,6 +26,27 @@ type macosSystemTray struct {
|
|||
nsMenu unsafe.Pointer
|
||||
iconPosition int
|
||||
isTemplateIcon bool
|
||||
parent *SystemTray
|
||||
}
|
||||
|
||||
type button int
|
||||
|
||||
const (
|
||||
left button = iota
|
||||
right
|
||||
)
|
||||
|
||||
// system tray map
|
||||
var systemTrayMap = make(map[uint]*macosSystemTray)
|
||||
|
||||
//export systrayClickCallback
|
||||
func systrayClickCallback(id C.long, buttonID C.int) {
|
||||
// Get the system tray
|
||||
systemTray := systemTrayMap[uint(id)]
|
||||
if systemTray == nil {
|
||||
return
|
||||
}
|
||||
systemTray.processClick(button(buttonID))
|
||||
}
|
||||
|
||||
func (s *macosSystemTray) setIconPosition(position int) {
|
||||
|
|
@ -192,13 +129,21 @@ func (s *macosSystemTray) positionWindow(window *WebviewWindow) error {
|
|||
}
|
||||
|
||||
func (s *macosSystemTray) getScreen() (*Screen, error) {
|
||||
cScreen := C.getScreenForSystemTray(s.nsStatusItem)
|
||||
return cScreenToScreen(cScreen), nil
|
||||
return getScreenForSystray(s)
|
||||
}
|
||||
|
||||
func (s *macosSystemTray) bounds() (*Rect, error) {
|
||||
var rect C.NSRect
|
||||
rect = C.systemTrayGetBounds(s.nsStatusItem)
|
||||
C.systemTrayGetBounds(s.nsStatusItem, &rect)
|
||||
// Get the screen height for the screen that the systray is on
|
||||
screen, err := getScreenForSystray(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Invert Y axis based on screen height
|
||||
rect.origin.y = C.double(screen.Bounds.Height) - rect.origin.y - rect.size.height
|
||||
|
||||
return &Rect{
|
||||
X: int(rect.origin.x),
|
||||
Y: int(rect.origin.y),
|
||||
|
|
@ -212,7 +157,8 @@ func (s *macosSystemTray) run() {
|
|||
if s.nsStatusItem != nil {
|
||||
Fatal("System tray '%d' already running", s.id)
|
||||
}
|
||||
s.nsStatusItem = unsafe.Pointer(C.systemTrayNew())
|
||||
s.nsStatusItem = unsafe.Pointer(C.systemTrayNew(C.long(s.id)))
|
||||
|
||||
if s.label != "" {
|
||||
C.systemTraySetLabel(s.nsStatusItem, C.CString(s.label))
|
||||
}
|
||||
|
|
@ -252,7 +198,8 @@ func (s *macosSystemTray) setTemplateIcon(icon []byte) {
|
|||
}
|
||||
|
||||
func newSystemTrayImpl(s *SystemTray) systemTrayImpl {
|
||||
return &macosSystemTray{
|
||||
result := &macosSystemTray{
|
||||
parent: s,
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
icon: s.icon,
|
||||
|
|
@ -260,6 +207,8 @@ func newSystemTrayImpl(s *SystemTray) systemTrayImpl {
|
|||
iconPosition: s.iconPosition,
|
||||
isTemplateIcon: s.isTemplateIcon,
|
||||
}
|
||||
systemTrayMap[s.id] = result
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *macosSystemTray) setLabel(label string) {
|
||||
|
|
@ -271,3 +220,24 @@ func (s *macosSystemTray) destroy() {
|
|||
// Remove the status item from the status bar and its associated menu
|
||||
C.systemTrayDestroy(s.nsStatusItem)
|
||||
}
|
||||
|
||||
func (s *macosSystemTray) processClick(b button) {
|
||||
switch b {
|
||||
case left:
|
||||
// Check if we have a callback
|
||||
if s.parent.clickHandler != nil {
|
||||
s.parent.clickHandler()
|
||||
return
|
||||
}
|
||||
case right:
|
||||
// Check if we have a callback
|
||||
if s.parent.rightClickHandler != nil {
|
||||
s.parent.rightClickHandler()
|
||||
return
|
||||
}
|
||||
}
|
||||
// Open the default menu if we have one
|
||||
if s.menu != nil {
|
||||
C.showMenu(s.nsStatusItem)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
15
v3/pkg/application/systemtray_darwin.h
Normal file
15
v3/pkg/application/systemtray_darwin.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
//go:build darwin
|
||||
|
||||
@interface StatusItemController : NSObject
|
||||
@property long id;
|
||||
- (void)statusItemClicked:(id)sender;
|
||||
@end
|
||||
|
||||
void* systemTrayNew(long id);
|
||||
void systemTraySetLabel(void* nsStatusItem, char *label);
|
||||
NSImage* imageFromBytes(const unsigned char *bytes, int length);
|
||||
void systemTraySetIcon(void* nsStatusItem, void* nsImage, int position, bool isTemplate);
|
||||
void systemTraySetMenu(void* nsStatusItem, void* nsMenu);
|
||||
void systemTrayDestroy(void* nsStatusItem);
|
||||
void showMenu(void* nsStatusItem);
|
||||
void systemTrayGetBounds(void* nsStatusItem, NSRect *rect);
|
||||
108
v3/pkg/application/systemtray_darwin.m
Normal file
108
v3/pkg/application/systemtray_darwin.m
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
//go:build darwin
|
||||
|
||||
#include "Cocoa/Cocoa.h"
|
||||
#include "menuitem_darwin.h"
|
||||
#include "systemtray_darwin.h"
|
||||
|
||||
extern void systrayClickCallback(long, int);
|
||||
|
||||
// StatusItemController.m
|
||||
@implementation StatusItemController
|
||||
|
||||
- (void)statusItemClicked:(id)sender {
|
||||
// Get the left or right button
|
||||
NSEvent *event = [NSApp currentEvent];
|
||||
if (event.type == NSEventTypeRightMouseUp) {
|
||||
// Right click
|
||||
systrayClickCallback(self.id, 1);
|
||||
} else {
|
||||
// Left click
|
||||
systrayClickCallback(self.id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Create a new system tray
|
||||
void* systemTrayNew(long id) {
|
||||
StatusItemController *controller = [[StatusItemController alloc] init];
|
||||
controller.id = id;
|
||||
NSStatusItem *statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain];
|
||||
[statusItem setTarget:controller];
|
||||
[statusItem setAction:@selector(statusItemClicked:)];
|
||||
return (void*)statusItem;
|
||||
}
|
||||
|
||||
void systemTraySetLabel(void* nsStatusItem, char *label) {
|
||||
if( label == NULL ) {
|
||||
return;
|
||||
}
|
||||
// Set the label on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
statusItem.button.title = [NSString stringWithUTF8String:label];
|
||||
free(label);
|
||||
});
|
||||
}
|
||||
|
||||
// Create an nsimage from a byte array
|
||||
NSImage* imageFromBytes(const unsigned char *bytes, int length) {
|
||||
NSData *data = [NSData dataWithBytes:bytes length:length];
|
||||
NSImage *image = [[NSImage alloc] initWithData:data];
|
||||
return image;
|
||||
}
|
||||
|
||||
// Set the icon on the system tray
|
||||
void systemTraySetIcon(void* nsStatusItem, void* nsImage, int position, bool isTemplate) {
|
||||
// Set the icon on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
NSImage *image = (NSImage *)nsImage;
|
||||
|
||||
NSStatusBar *statusBar = [NSStatusBar systemStatusBar];
|
||||
CGFloat thickness = [statusBar thickness];
|
||||
[image setSize:NSMakeSize(thickness, thickness)];
|
||||
if( isTemplate ) {
|
||||
[image setTemplate:YES];
|
||||
}
|
||||
statusItem.button.image = [image autorelease];
|
||||
statusItem.button.imagePosition = position;
|
||||
});
|
||||
}
|
||||
|
||||
// Add menu to system tray
|
||||
void systemTraySetMenu(void* nsStatusItem, void* nsMenu) {
|
||||
// Set the menu on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
NSMenu *menu = (NSMenu *)nsMenu;
|
||||
statusItem.menu = menu;
|
||||
});
|
||||
}
|
||||
|
||||
// Destroy system tray
|
||||
void systemTrayDestroy(void* nsStatusItem) {
|
||||
// Remove the status item from the status bar and its associated menu
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
[[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
|
||||
[statusItem release];
|
||||
});
|
||||
}
|
||||
|
||||
void showMenu(void* nsStatusItem) {
|
||||
// Show the menu on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
// Check it's not nil
|
||||
if( statusItem.menu != nil ) {
|
||||
[statusItem popUpStatusItemMenu:statusItem.menu];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void systemTrayGetBounds(void* nsStatusItem, NSRect *rect) {
|
||||
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
|
||||
NSRect buttonFrame = statusItem.button.frame;
|
||||
*rect = [statusItem.button.window convertRectToScreen:buttonFrame];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue