mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-16 15:45:50 +01:00
* new events
* macOS dnd improvements
* wailsio adds for dropzone
* update example
* sorta working
the top 300px of the window are not dropabble for some reason i suspect it has to do with the drag enter/drag leave xy as the performOperation needed to use the ContentView for appropriate X/Y
* implement attribute detection for data-wails-dropzone
* docs
* pass x/y dnd linux
* cleanup exmample
* changelog
* pass all attributes to golang on dragdrop
* filetree example
* fix dnd build windows
* Fix windows dnd
* update docs
* remove debug log
* appease the security bot
* Fix changelog
* Fix changelog
* Revert "Fix event generation issues."
This reverts commit ae4ed4fe
* Fix events
* Fix merge conflicts. Fix events generation formatting
* Update docs
* Fix duplicate bundledassets import causing build failures
Remove duplicate import of bundledassets package that was causing
compilation errors in PR #4318. The import was declared twice in
the same import block, causing "bundledassets redeclared" errors.
Fixes build issues in GitHub Actions for drag-and-drop zones feature.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Replace fmt.Printf debug statements with globalApplication.debug
Replace all fmt.Printf debug logging statements in drag-and-drop
functionality with proper globalApplication.debug calls. This provides:
- Consistent logging with the rest of the application
- Proper key-value structured logging
- Better integration with the application's logging system
- Cleaner debug output format
Changes:
- application_darwin.go: Replace 2 fmt.Printf calls
- webview_window.go: Replace 6 fmt.Printf calls
- webview_window_windows.go: Replace 13 fmt.Printf calls
- Remove unused fmt import from application_darwin.go
All debug messages maintain the same information but now use
structured logging with key-value pairs instead of printf formatting.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add nil checks to WindowEventContext methods
Ensure all WindowEventContext methods properly handle nil c.data
by initializing the map when it's nil. This prevents panics when
methods are called on contexts that haven't been properly initialized.
Changes:
- DroppedFiles(): Add nil check and map initialization
- setCoordinates(): Add nil check and map initialization
- setDropZoneDetails(): Add nil check and map initialization
- DropZoneDetails(): Add nil check and map initialization
All methods now follow the same pattern as setDroppedFiles()
where a nil data map is automatically initialized to prevent
runtime panics during drag-and-drop operations.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update v3/pkg/application/webview_window_darwin.m
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* reinstate events docs.
---------
Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
468 lines
12 KiB
Go
468 lines
12 KiB
Go
//go:build darwin
|
|
|
|
package application
|
|
|
|
/*
|
|
|
|
#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c
|
|
#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13
|
|
|
|
#include "application_darwin.h"
|
|
#include "application_darwin_delegate.h"
|
|
#include "webview_window_darwin.h"
|
|
#include <stdlib.h>
|
|
|
|
extern void registerListener(unsigned int event);
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <Foundation/Foundation.h>
|
|
|
|
static AppDelegate *appDelegate = nil;
|
|
|
|
static void init(void) {
|
|
[NSApplication sharedApplication];
|
|
appDelegate = [[AppDelegate alloc] init];
|
|
[NSApp setDelegate:appDelegate];
|
|
|
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
|
|
NSWindow* eventWindow = [event window];
|
|
if (eventWindow == nil ) {
|
|
return event;
|
|
}
|
|
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate];
|
|
if (windowDelegate == nil) {
|
|
return event;
|
|
}
|
|
if ([windowDelegate respondsToSelector:@selector(handleLeftMouseDown:)]) {
|
|
[windowDelegate handleLeftMouseDown:event];
|
|
}
|
|
return event;
|
|
}];
|
|
|
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
|
|
NSWindow* eventWindow = [event window];
|
|
if (eventWindow == nil ) {
|
|
return event;
|
|
}
|
|
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate];
|
|
if (windowDelegate == nil) {
|
|
return event;
|
|
}
|
|
if ([windowDelegate respondsToSelector:@selector(handleLeftMouseUp:)]) {
|
|
[windowDelegate handleLeftMouseUp:eventWindow];
|
|
}
|
|
return event;
|
|
}];
|
|
|
|
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
|
|
[center addObserver:appDelegate selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil];
|
|
|
|
// Register the custom URL scheme handler
|
|
StartCustomProtocolHandler();
|
|
}
|
|
|
|
static bool isDarkMode(void) {
|
|
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
|
|
if (userDefaults == nil) {
|
|
return false;
|
|
}
|
|
|
|
NSString *interfaceStyle = [userDefaults stringForKey:@"AppleInterfaceStyle"];
|
|
if (interfaceStyle == nil) {
|
|
return false;
|
|
}
|
|
|
|
return [interfaceStyle isEqualToString:@"Dark"];
|
|
}
|
|
|
|
static char* getAccentColor(void) {
|
|
@autoreleasepool {
|
|
NSColor *accentColor;
|
|
if (@available(macOS 10.14, *)) {
|
|
accentColor = [NSColor controlAccentColor];
|
|
} else {
|
|
// Fallback to system blue for older macOS versions
|
|
accentColor = [NSColor systemBlueColor];
|
|
}
|
|
// Convert to RGB color space
|
|
NSColor *rgbColor = [accentColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
|
|
if (rgbColor == nil) {
|
|
rgbColor = accentColor;
|
|
}
|
|
// Get RGB components
|
|
CGFloat red, green, blue, alpha;
|
|
[rgbColor getRed:&red green:&green blue:&blue alpha:&alpha];
|
|
// Convert to 0-255 range and format as rgb() string
|
|
int r = (int)(red * 255);
|
|
int g = (int)(green * 255);
|
|
int b = (int)(blue * 255);
|
|
NSString *colorString = [NSString stringWithFormat:@"rgb(%d,%d,%d)", r, g, b];
|
|
return strdup([colorString UTF8String]);
|
|
}
|
|
}
|
|
|
|
static void setApplicationShouldTerminateAfterLastWindowClosed(bool shouldTerminate) {
|
|
// Get the NSApp delegate
|
|
AppDelegate *appDelegate = (AppDelegate*)[NSApp delegate];
|
|
// Set the applicationShouldTerminateAfterLastWindowClosed boolean
|
|
appDelegate.shouldTerminateWhenLastWindowClosed = shouldTerminate;
|
|
}
|
|
|
|
static void setActivationPolicy(int policy) {
|
|
[NSApp setActivationPolicy:policy];
|
|
}
|
|
|
|
static void activateIgnoringOtherApps() {
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
}
|
|
|
|
static void run(void) {
|
|
@autoreleasepool {
|
|
[NSApp run];
|
|
[appDelegate release];
|
|
[NSApp abortModal];
|
|
}
|
|
}
|
|
|
|
// destroyApp destroys the application
|
|
static void destroyApp(void) {
|
|
[NSApp terminate:nil];
|
|
}
|
|
|
|
// Set the application menu
|
|
static void setApplicationMenu(void *menu) {
|
|
NSMenu *nsMenu = (__bridge NSMenu *)menu;
|
|
[NSApp setMainMenu:menu];
|
|
}
|
|
|
|
// Get the application name
|
|
static char* getAppName(void) {
|
|
NSString *appName = [NSRunningApplication currentApplication].localizedName;
|
|
if( appName == nil ) {
|
|
appName = [[NSProcessInfo processInfo] processName];
|
|
}
|
|
return strdup([appName UTF8String]);
|
|
}
|
|
|
|
// get the current window ID
|
|
static unsigned int getCurrentWindowID(void) {
|
|
NSWindow *window = [NSApp keyWindow];
|
|
// Get the window delegate
|
|
WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)[window delegate];
|
|
return delegate.windowId;
|
|
}
|
|
|
|
// Set the application icon
|
|
static void setApplicationIcon(void *icon, int length) {
|
|
// On main thread
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]];
|
|
[NSApp setApplicationIconImage:image];
|
|
});
|
|
}
|
|
|
|
// Hide the application
|
|
static void hide(void) {
|
|
[NSApp hide:nil];
|
|
}
|
|
|
|
// Show the application
|
|
static void show(void) {
|
|
[NSApp unhide:nil];
|
|
}
|
|
|
|
static const char* serializationNSDictionary(void *dict) {
|
|
@autoreleasepool {
|
|
NSDictionary *nsDict = (__bridge NSDictionary *)dict;
|
|
|
|
if ([NSJSONSerialization isValidJSONObject:nsDict]) {
|
|
NSError *error;
|
|
NSData *data = [NSJSONSerialization dataWithJSONObject:nsDict options:kNilOptions error:&error];
|
|
NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
|
|
|
|
return strdup([result UTF8String]);
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
static void startSingleInstanceListener(const char *uniqueID) {
|
|
// Convert to NSString
|
|
NSString *uid = [NSString stringWithUTF8String:uniqueID];
|
|
[[NSDistributedNotificationCenter defaultCenter] addObserver:appDelegate
|
|
selector:@selector(handleSecondInstanceNotification:) name:uid object:nil];
|
|
}
|
|
*/
|
|
import "C"
|
|
import (
|
|
"encoding/json"
|
|
"unsafe"
|
|
|
|
"github.com/wailsapp/wails/v3/internal/operatingsystem"
|
|
|
|
"github.com/wailsapp/wails/v3/internal/assetserver/webview"
|
|
"github.com/wailsapp/wails/v3/pkg/events"
|
|
)
|
|
|
|
type macosApp struct {
|
|
applicationMenu unsafe.Pointer
|
|
parent *App
|
|
}
|
|
|
|
func (m *macosApp) isDarkMode() bool {
|
|
return bool(C.isDarkMode())
|
|
}
|
|
|
|
func (m *macosApp) getAccentColor() string {
|
|
accentColorC := C.getAccentColor()
|
|
defer C.free(unsafe.Pointer(accentColorC))
|
|
return C.GoString(accentColorC)
|
|
}
|
|
|
|
func getNativeApplication() *macosApp {
|
|
return globalApplication.impl.(*macosApp)
|
|
}
|
|
|
|
func (m *macosApp) hide() {
|
|
C.hide()
|
|
}
|
|
|
|
func (m *macosApp) show() {
|
|
C.show()
|
|
}
|
|
|
|
func (m *macosApp) on(eventID uint) {
|
|
C.registerListener(C.uint(eventID))
|
|
}
|
|
|
|
func (m *macosApp) setIcon(icon []byte) {
|
|
C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon)))
|
|
}
|
|
|
|
func (m *macosApp) name() string {
|
|
appName := C.getAppName()
|
|
defer C.free(unsafe.Pointer(appName))
|
|
return C.GoString(appName)
|
|
}
|
|
|
|
func (m *macosApp) getCurrentWindowID() uint {
|
|
return uint(C.getCurrentWindowID())
|
|
}
|
|
|
|
func (m *macosApp) setApplicationMenu(menu *Menu) {
|
|
if menu == nil {
|
|
// Create a default menu for mac
|
|
menu = DefaultApplicationMenu()
|
|
}
|
|
menu.Update()
|
|
|
|
// Convert impl to macosMenu object
|
|
m.applicationMenu = (menu.impl).(*macosMenu).nsMenu
|
|
C.setApplicationMenu(m.applicationMenu)
|
|
}
|
|
|
|
func (m *macosApp) run() error {
|
|
if m.parent.options.SingleInstance != nil {
|
|
cUniqueID := C.CString(m.parent.options.SingleInstance.UniqueID)
|
|
defer C.free(unsafe.Pointer(cUniqueID))
|
|
C.startSingleInstanceListener(cUniqueID)
|
|
}
|
|
// Add a hook to the ApplicationDidFinishLaunching event
|
|
m.parent.Event.OnApplicationEvent(
|
|
events.Mac.ApplicationDidFinishLaunching,
|
|
func(*ApplicationEvent) {
|
|
C.setApplicationShouldTerminateAfterLastWindowClosed(
|
|
C.bool(m.parent.options.Mac.ApplicationShouldTerminateAfterLastWindowClosed),
|
|
)
|
|
C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy))
|
|
C.activateIgnoringOtherApps()
|
|
},
|
|
)
|
|
m.setupCommonEvents()
|
|
// setup event listeners
|
|
for eventID := range m.parent.applicationEventListeners {
|
|
m.on(eventID)
|
|
}
|
|
C.run()
|
|
return nil
|
|
}
|
|
|
|
func (m *macosApp) destroy() {
|
|
C.destroyApp()
|
|
}
|
|
|
|
func (m *macosApp) GetFlags(options Options) map[string]any {
|
|
if options.Flags == nil {
|
|
options.Flags = make(map[string]any)
|
|
}
|
|
return options.Flags
|
|
}
|
|
|
|
func newPlatformApp(app *App) *macosApp {
|
|
C.init()
|
|
return &macosApp{
|
|
parent: app,
|
|
}
|
|
}
|
|
|
|
//export processApplicationEvent
|
|
func processApplicationEvent(eventID C.uint, data unsafe.Pointer) {
|
|
event := newApplicationEvent(events.ApplicationEventType(eventID))
|
|
|
|
if data != nil {
|
|
dataCStrJSON := C.serializationNSDictionary(data)
|
|
if dataCStrJSON != nil {
|
|
defer C.free(unsafe.Pointer(dataCStrJSON))
|
|
|
|
dataJSON := C.GoString(dataCStrJSON)
|
|
var result map[string]any
|
|
err := json.Unmarshal([]byte(dataJSON), &result)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
event.Context().setData(result)
|
|
}
|
|
}
|
|
|
|
switch event.Id {
|
|
case uint(events.Mac.ApplicationDidChangeTheme):
|
|
isDark := globalApplication.Env.IsDarkMode()
|
|
event.Context().setIsDarkMode(isDark)
|
|
}
|
|
applicationEvents <- event
|
|
}
|
|
|
|
//export processWindowEvent
|
|
func processWindowEvent(windowID C.uint, eventID C.uint) {
|
|
windowEvents <- &windowEvent{
|
|
WindowID: uint(windowID),
|
|
EventID: uint(eventID),
|
|
}
|
|
}
|
|
|
|
//export processMessage
|
|
func processMessage(windowID C.uint, message *C.char) {
|
|
windowMessageBuffer <- &windowMessage{
|
|
windowId: uint(windowID),
|
|
message: C.GoString(message),
|
|
}
|
|
}
|
|
|
|
//export processURLRequest
|
|
func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) {
|
|
window, ok := globalApplication.Window.GetByID(uint(windowID))
|
|
if !ok || window == nil {
|
|
globalApplication.debug("could not find window with id: %d", windowID)
|
|
return
|
|
}
|
|
|
|
webviewRequests <- &webViewAssetRequest{
|
|
Request: webview.NewRequest(wkUrlSchemeTask),
|
|
windowId: uint(windowID),
|
|
windowName: window.Name(),
|
|
}
|
|
}
|
|
|
|
//export processWindowKeyDownEvent
|
|
func processWindowKeyDownEvent(windowID C.uint, acceleratorString *C.char) {
|
|
windowKeyEvents <- &windowKeyEvent{
|
|
windowId: uint(windowID),
|
|
acceleratorString: C.GoString(acceleratorString),
|
|
}
|
|
}
|
|
|
|
//export processDragItems
|
|
func processDragItems(windowID C.uint, arr **C.char, length C.int, x C.int, y C.int) {
|
|
var filenames []string
|
|
// Convert the C array to a Go slice
|
|
goSlice := (*[1 << 30]*C.char)(unsafe.Pointer(arr))[:length:length]
|
|
for _, str := range goSlice {
|
|
filenames = append(filenames, C.GoString(str))
|
|
}
|
|
|
|
globalApplication.debug(
|
|
"[DragDropDebug] processDragItems called",
|
|
"windowID",
|
|
windowID,
|
|
"fileCount",
|
|
len(filenames),
|
|
"x",
|
|
x,
|
|
"y",
|
|
y,
|
|
)
|
|
targetWindow, ok := globalApplication.Window.GetByID(uint(windowID))
|
|
if !ok || targetWindow == nil {
|
|
println("Error: processDragItems could not find window with ID:", uint(windowID))
|
|
return
|
|
}
|
|
|
|
globalApplication.debug(
|
|
"[DragDropDebug] processDragItems: Calling targetWindow.InitiateFrontendDropProcessing",
|
|
)
|
|
targetWindow.InitiateFrontendDropProcessing(filenames, int(x), int(y))
|
|
}
|
|
|
|
//export processMenuItemClick
|
|
func processMenuItemClick(menuID C.uint) {
|
|
menuItemClicked <- uint(menuID)
|
|
}
|
|
|
|
//export shouldQuitApplication
|
|
func shouldQuitApplication() C.bool {
|
|
// TODO: This should be configurable
|
|
return C.bool(globalApplication.shouldQuit())
|
|
}
|
|
|
|
//export cleanup
|
|
func cleanup() {
|
|
globalApplication.cleanup()
|
|
}
|
|
|
|
func (a *App) logPlatformInfo() {
|
|
info, err := operatingsystem.Info()
|
|
if err != nil {
|
|
a.error("error getting OS info: %w", err)
|
|
return
|
|
}
|
|
|
|
a.info("Platform Info:", info.AsLogSlice()...)
|
|
|
|
}
|
|
|
|
func (a *App) platformEnvironment() map[string]any {
|
|
return map[string]any{}
|
|
}
|
|
|
|
func fatalHandler(errFunc func(error)) {
|
|
return
|
|
}
|
|
|
|
//export HandleOpenFile
|
|
func HandleOpenFile(filePath *C.char) {
|
|
goFilepath := C.GoString(filePath)
|
|
// Create new application event context
|
|
eventContext := newApplicationEventContext()
|
|
eventContext.setOpenedWithFile(goFilepath)
|
|
// EmitEvent application started event
|
|
applicationEvents <- &ApplicationEvent{
|
|
Id: uint(events.Common.ApplicationOpenedWithFile),
|
|
ctx: eventContext,
|
|
}
|
|
}
|
|
|
|
//export HandleCustomProtocol
|
|
func HandleCustomProtocol(urlCString *C.char) {
|
|
urlString := C.GoString(urlCString)
|
|
eventContext := newApplicationEventContext()
|
|
eventContext.setURL(urlString)
|
|
|
|
// Emit the standard event with the URL string as data
|
|
applicationEvents <- &ApplicationEvent{
|
|
Id: uint(events.Common.ApplicationLaunchedWithUrl),
|
|
ctx: eventContext,
|
|
}
|
|
}
|