Switch remote deploy to vendored source builds
Move remote deployment to a vendored source bundle built on the target host via Docker so redeploys no longer require local cross-compilation or host Go installation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
33
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/AppDelegate.h
generated
vendored
Normal file
33
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/AppDelegate.h
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// AppDelegate.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#ifndef AppDelegate_h
|
||||
#define AppDelegate_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "WailsContext.h"
|
||||
|
||||
@interface AppDelegate : NSResponder <NSApplicationDelegate, NSTouchBarProvider>
|
||||
|
||||
@property bool alwaysOnTop;
|
||||
@property bool startHidden;
|
||||
@property (retain) NSString* singleInstanceUniqueId;
|
||||
@property bool singleInstanceLockEnabled;
|
||||
@property bool startFullscreen;
|
||||
@property (retain) WailsWindow* mainWindow;
|
||||
|
||||
@end
|
||||
|
||||
extern void HandleOpenFile(char *);
|
||||
|
||||
extern void HandleSecondInstanceData(char * message);
|
||||
|
||||
void SendDataToFirstInstance(char * singleInstanceUniqueId, char * text);
|
||||
|
||||
char* GetMacOsNativeTempDir();
|
||||
|
||||
#endif /* AppDelegate_h */
|
||||
100
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/AppDelegate.m
generated
vendored
Normal file
100
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/AppDelegate.m
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// AppDelegate.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "CustomProtocol.h"
|
||||
#import "message.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
|
||||
{
|
||||
const char* utf8FileName = filename.UTF8String;
|
||||
HandleOpenFile((char*)utf8FileName);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)application:(NSApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<NSUserActivityRestoring>> * _Nullable))restorationHandler {
|
||||
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
||||
NSURL *url = userActivity.webpageURL;
|
||||
if (url) {
|
||||
HandleOpenURL((char*)[[url absoluteString] UTF8String]);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||
processMessage("Q");
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
if (self.alwaysOnTop) {
|
||||
[self.mainWindow setLevel:NSFloatingWindowLevel];
|
||||
}
|
||||
if ( !self.startHidden ) {
|
||||
[self.mainWindow makeKeyAndOrderFront:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
if ( self.startFullscreen ) {
|
||||
NSWindowCollectionBehavior behaviour = [self.mainWindow collectionBehavior];
|
||||
behaviour |= NSWindowCollectionBehaviorFullScreenPrimary;
|
||||
[self.mainWindow setCollectionBehavior:behaviour];
|
||||
[self.mainWindow toggleFullScreen:nil];
|
||||
}
|
||||
|
||||
if ( self.singleInstanceLockEnabled ) {
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleSecondInstanceNotification:) name:self.singleInstanceUniqueId object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
void SendDataToFirstInstance(char * singleInstanceUniqueId, char * message) {
|
||||
// we pass message in object because otherwise sandboxing will prevent us from sending it https://developer.apple.com/forums/thread/129437
|
||||
NSString * myString = [NSString stringWithUTF8String:message];
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:[NSString stringWithUTF8String:singleInstanceUniqueId]
|
||||
object:(__bridge const void *)(myString)
|
||||
userInfo:nil
|
||||
deliverImmediately:YES];
|
||||
}
|
||||
|
||||
char* GetMacOsNativeTempDir() {
|
||||
NSString *tempDir = NSTemporaryDirectory();
|
||||
char *copy = strdup([tempDir UTF8String]);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (void)handleSecondInstanceNotification:(NSNotification *)note;
|
||||
{
|
||||
if (note.object != nil) {
|
||||
NSString * message = (__bridge NSString *)note.object;
|
||||
const char* utf8Message = message.UTF8String;
|
||||
HandleSecondInstanceData((char*)utf8Message);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@synthesize touchBar;
|
||||
|
||||
@end
|
||||
89
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Application.h
generated
vendored
Normal file
89
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Application.h
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Application.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#ifndef Application_h
|
||||
#define Application_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "WailsContext.h"
|
||||
|
||||
#define WindowStartsNormal 0
|
||||
#define WindowStartsMaximised 1
|
||||
#define WindowStartsMinimised 2
|
||||
#define WindowStartsFullscreen 3
|
||||
|
||||
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int zoomable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int contentProtection, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceLockEnabled, const char* singleInstanceUniqueId, bool enableDragAndDrop, bool disableWebViewDragAndDrop);
|
||||
void Run(void*, const char* url);
|
||||
|
||||
void SetTitle(void* ctx, const char *title);
|
||||
void Center(void* ctx);
|
||||
void SetSize(void* ctx, int width, int height);
|
||||
void SetAlwaysOnTop(void* ctx, int onTop);
|
||||
void SetMinSize(void* ctx, int width, int height);
|
||||
void SetMaxSize(void* ctx, int width, int height);
|
||||
void SetPosition(void* ctx, int x, int y);
|
||||
void Fullscreen(void* ctx);
|
||||
void UnFullscreen(void* ctx);
|
||||
void Minimise(void* ctx);
|
||||
void UnMinimise(void* ctx);
|
||||
void ToggleMaximise(void* ctx);
|
||||
void Maximise(void* ctx);
|
||||
void UnMaximise(void* ctx);
|
||||
void Hide(void* ctx);
|
||||
void Show(void* ctx);
|
||||
void HideApplication(void* ctx);
|
||||
void ShowApplication(void* ctx);
|
||||
void SetBackgroundColour(void* ctx, int r, int g, int b, int a);
|
||||
void ExecJS(void* ctx, const char*);
|
||||
void Quit(void*);
|
||||
void WindowPrint(void* ctx);
|
||||
|
||||
const char* GetSize(void *ctx);
|
||||
const char* GetPosition(void *ctx);
|
||||
const bool IsFullScreen(void *ctx);
|
||||
const bool IsMinimised(void *ctx);
|
||||
const bool IsMaximised(void *ctx);
|
||||
|
||||
/* Dialogs */
|
||||
|
||||
void MessageDialog(void *inctx, const char* dialogType, const char* title, const char* message, const char* button1, const char* button2, const char* button3, const char* button4, const char* defaultButton, const char* cancelButton, void* iconData, int iconDataLength);
|
||||
void OpenFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int allowDirectories, int allowFiles, int canCreateDirectories, int treatPackagesAsDirectories, int resolveAliases, int showHiddenFiles, int allowMultipleSelection, const char* filters);
|
||||
void SaveFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int canCreateDirectories, int treatPackagesAsDirectories, int showHiddenFiles, const char* filters);
|
||||
|
||||
/* Application Menu */
|
||||
void* NewMenu(const char* name);
|
||||
void AppendSubmenu(void* parent, void* child);
|
||||
void AppendRole(void *inctx, void *inMenu, int role);
|
||||
void SetAsApplicationMenu(void *inctx, void *inMenu);
|
||||
void UpdateApplicationMenu(void *inctx);
|
||||
|
||||
void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen);
|
||||
void* AppendMenuItem(void* inctx, void* nsmenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID);
|
||||
void AppendSeparator(void* inMenu);
|
||||
void UpdateMenuItem(void* nsmenuitem, int checked);
|
||||
void RunMainLoop(void);
|
||||
void ReleaseContext(void *inctx);
|
||||
|
||||
/* Notifications */
|
||||
bool IsNotificationAvailable(void *inctx);
|
||||
bool CheckBundleIdentifier(void *inctx);
|
||||
bool EnsureDelegateInitialized(void *inctx);
|
||||
void RequestNotificationAuthorization(void *inctx, int channelID);
|
||||
void CheckNotificationAuthorization(void *inctx, int channelID);
|
||||
void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json);
|
||||
void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json);
|
||||
void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle);
|
||||
void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId);
|
||||
void RemoveAllPendingNotifications(void *inctx);
|
||||
void RemovePendingNotification(void *inctx, const char *identifier);
|
||||
void RemoveAllDeliveredNotifications(void *inctx);
|
||||
void RemoveDeliveredNotification(void *inctx, const char *identifier);
|
||||
|
||||
NSString* safeInit(const char* input);
|
||||
|
||||
#endif /* Application_h */
|
||||
501
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Application.m
generated
vendored
Normal file
501
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Application.m
generated
vendored
Normal file
@@ -0,0 +1,501 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// Application.m
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "WailsContext.h"
|
||||
#import "Application.h"
|
||||
#import "AppDelegate.h"
|
||||
#import "WindowDelegate.h"
|
||||
#import "WailsMenu.h"
|
||||
#import "WailsMenuItem.h"
|
||||
|
||||
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int zoomable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int contentProtection, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceLockEnabled, const char* singleInstanceUniqueId, bool enableDragAndDrop, bool disableWebViewDragAndDrop) {
|
||||
|
||||
[NSApplication sharedApplication];
|
||||
|
||||
WailsContext *result = [WailsContext new];
|
||||
|
||||
result.devtoolsEnabled = devtoolsEnabled;
|
||||
result.defaultContextMenuEnabled = defaultContextMenuEnabled;
|
||||
|
||||
if ( windowStartState == WindowStartsFullscreen ) {
|
||||
fullscreen = 1;
|
||||
}
|
||||
|
||||
[result CreateWindow:width :height :frameless :resizable :zoomable :fullscreen :fullSizeContent :hideTitleBar :titlebarAppearsTransparent :hideTitle :useToolbar :hideToolbarSeparator :webviewIsTransparent :hideWindowOnClose :safeInit(appearance) :windowIsTranslucent :minWidth :minHeight :maxWidth :maxHeight :fraudulentWebsiteWarningEnabled :preferences :enableDragAndDrop :disableWebViewDragAndDrop];
|
||||
[result SetTitle:safeInit(title)];
|
||||
[result Center];
|
||||
|
||||
if (contentProtection == 1 &&
|
||||
[result.mainWindow respondsToSelector:@selector(setSharingType:)]) {
|
||||
[result.mainWindow setSharingType:NSWindowSharingNone];
|
||||
}
|
||||
|
||||
switch( windowStartState ) {
|
||||
case WindowStartsMaximised:
|
||||
[result.mainWindow zoom:nil];
|
||||
break;
|
||||
case WindowStartsMinimised:
|
||||
//TODO: Can you start a mac app minimised?
|
||||
break;
|
||||
}
|
||||
|
||||
if ( startsHidden == 1 ) {
|
||||
result.startHidden = true;
|
||||
}
|
||||
|
||||
if ( fullscreen == 1 ) {
|
||||
result.startFullscreen = true;
|
||||
}
|
||||
|
||||
if ( singleInstanceLockEnabled == 1 ) {
|
||||
result.singleInstanceLockEnabled = true;
|
||||
result.singleInstanceUniqueId = safeInit(singleInstanceUniqueId);
|
||||
}
|
||||
|
||||
result.alwaysOnTop = alwaysOnTop;
|
||||
result.hideOnClose = hideWindowOnClose;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ExecJS(void* inctx, const char *script) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *nsscript = safeInit(script);
|
||||
ON_MAIN_THREAD(
|
||||
[ctx ExecJS:nsscript];
|
||||
[nsscript release];
|
||||
);
|
||||
}
|
||||
|
||||
void SetTitle(void* inctx, const char *title) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *_title = safeInit(title);
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetTitle:_title];
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void SetBackgroundColour(void *inctx, int r, int g, int b, int a) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetBackgroundColour:r :g :b :a];
|
||||
);
|
||||
}
|
||||
|
||||
void SetSize(void* inctx, int width, int height) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetSize:width :height];
|
||||
);
|
||||
}
|
||||
|
||||
void SetAlwaysOnTop(void* inctx, int onTop) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetAlwaysOnTop:onTop];
|
||||
);
|
||||
}
|
||||
|
||||
void SetMinSize(void* inctx, int width, int height) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetMinSize:width :height];
|
||||
);
|
||||
}
|
||||
|
||||
void SetMaxSize(void* inctx, int width, int height) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetMaxSize:width :height];
|
||||
);
|
||||
}
|
||||
|
||||
void SetPosition(void* inctx, int x, int y) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetPosition:x :y];
|
||||
);
|
||||
}
|
||||
|
||||
void Center(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Center];
|
||||
);
|
||||
}
|
||||
|
||||
void Fullscreen(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Fullscreen];
|
||||
);
|
||||
}
|
||||
|
||||
void UnFullscreen(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx UnFullscreen];
|
||||
);
|
||||
}
|
||||
|
||||
void Minimise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Minimise];
|
||||
);
|
||||
}
|
||||
|
||||
void UnMinimise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx UnMinimise];
|
||||
);
|
||||
}
|
||||
|
||||
void Maximise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Maximise];
|
||||
);
|
||||
}
|
||||
|
||||
void ToggleMaximise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx ToggleMaximise];
|
||||
);
|
||||
}
|
||||
|
||||
const char* GetSize(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSRect frame = [ctx.mainWindow frame];
|
||||
NSString *result = [NSString stringWithFormat:@"%d,%d", (int)frame.size.width, (int)frame.size.height];
|
||||
return [result UTF8String];
|
||||
}
|
||||
|
||||
const char* GetPosition(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSScreen* screen = [ctx getCurrentScreen];
|
||||
NSRect windowFrame = [ctx.mainWindow frame];
|
||||
NSRect screenFrame = [screen visibleFrame];
|
||||
int x = windowFrame.origin.x - screenFrame.origin.x;
|
||||
int y = windowFrame.origin.y - screenFrame.origin.y;
|
||||
y = screenFrame.size.height - y - windowFrame.size.height;
|
||||
NSString *result = [NSString stringWithFormat:@"%d,%d",x,y];
|
||||
return [result UTF8String];
|
||||
}
|
||||
|
||||
const bool IsFullScreen(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
return [ctx IsFullScreen];
|
||||
}
|
||||
|
||||
const bool IsMinimised(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
return [ctx IsMinimised];
|
||||
}
|
||||
|
||||
const bool IsMaximised(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
return [ctx IsMaximised];
|
||||
}
|
||||
|
||||
void UnMaximise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx UnMaximise];
|
||||
);
|
||||
}
|
||||
|
||||
void Quit(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
[NSApp stop:ctx];
|
||||
[NSApp abortModal];
|
||||
}
|
||||
|
||||
void Hide(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Hide];
|
||||
);
|
||||
}
|
||||
|
||||
void Show(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Show];
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void HideApplication(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx HideApplication];
|
||||
);
|
||||
}
|
||||
|
||||
void ShowApplication(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx ShowApplication];
|
||||
);
|
||||
}
|
||||
|
||||
NSString* safeInit(const char* input) {
|
||||
NSString *result = nil;
|
||||
if (input != nil) {
|
||||
result = [NSString stringWithUTF8String:input];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void MessageDialog(void *inctx, const char* dialogType, const char* title, const char* message, const char* button1, const char* button2, const char* button3, const char* button4, const char* defaultButton, const char* cancelButton, void* iconData, int iconDataLength) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
|
||||
NSString *_dialogType = safeInit(dialogType);
|
||||
NSString *_title = safeInit(title);
|
||||
NSString *_message = safeInit(message);
|
||||
NSString *_button1 = safeInit(button1);
|
||||
NSString *_button2 = safeInit(button2);
|
||||
NSString *_button3 = safeInit(button3);
|
||||
NSString *_button4 = safeInit(button4);
|
||||
NSString *_defaultButton = safeInit(defaultButton);
|
||||
NSString *_cancelButton = safeInit(cancelButton);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
[ctx MessageDialog:_dialogType :_title :_message :_button1 :_button2 :_button3 :_button4 :_defaultButton :_cancelButton :iconData :iconDataLength];
|
||||
)
|
||||
}
|
||||
|
||||
void OpenFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int allowDirectories, int allowFiles, int canCreateDirectories, int treatPackagesAsDirectories, int resolveAliases, int showHiddenFiles, int allowMultipleSelection, const char* filters) {
|
||||
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *_title = safeInit(title);
|
||||
NSString *_defaultFilename = safeInit(defaultFilename);
|
||||
NSString *_defaultDirectory = safeInit(defaultDirectory);
|
||||
NSString *_filters = safeInit(filters);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
[ctx OpenFileDialog:_title :_defaultFilename :_defaultDirectory :allowDirectories :allowFiles :canCreateDirectories :treatPackagesAsDirectories :resolveAliases :showHiddenFiles :allowMultipleSelection :_filters];
|
||||
)
|
||||
}
|
||||
|
||||
void SaveFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int canCreateDirectories, int treatPackagesAsDirectories, int showHiddenFiles, const char* filters) {
|
||||
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *_title = safeInit(title);
|
||||
NSString *_defaultFilename = safeInit(defaultFilename);
|
||||
NSString *_defaultDirectory = safeInit(defaultDirectory);
|
||||
NSString *_filters = safeInit(filters);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SaveFileDialog:_title :_defaultFilename :_defaultDirectory :canCreateDirectories :treatPackagesAsDirectories :showHiddenFiles :_filters];
|
||||
)
|
||||
}
|
||||
|
||||
void AppendRole(void *inctx, void *inMenu, int role) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
|
||||
[menu appendRole :ctx :role];
|
||||
}
|
||||
|
||||
void* NewMenu(const char *name) {
|
||||
NSString *title = @"";
|
||||
if (name != nil) {
|
||||
title = [NSString stringWithUTF8String:name];
|
||||
}
|
||||
WailsMenu *result = [[WailsMenu new] initWithNSTitle:title];
|
||||
return result;
|
||||
}
|
||||
|
||||
void AppendSubmenu(void* inparent, void* inchild) {
|
||||
WailsMenu *parent = (__bridge WailsMenu*) inparent;
|
||||
WailsMenu *child = (__bridge WailsMenu*) inchild;
|
||||
[parent appendSubmenu:child];
|
||||
}
|
||||
|
||||
void SetAsApplicationMenu(void *inctx, void *inMenu) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
|
||||
ctx.applicationMenu = menu;
|
||||
}
|
||||
|
||||
void UpdateApplicationMenu(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
[app setMainMenu:ctx.applicationMenu];
|
||||
)
|
||||
}
|
||||
|
||||
void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *_title = safeInit(title);
|
||||
NSString *_description = safeInit(description);
|
||||
|
||||
[ctx SetAbout :_title :_description :imagedata :datalen];
|
||||
}
|
||||
|
||||
void* AppendMenuItem(void* inctx, void* inMenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
|
||||
NSString *_label = safeInit(label);
|
||||
NSString *_shortcutKey = safeInit(shortcutKey);
|
||||
|
||||
return [menu AppendMenuItem:ctx :_label :_shortcutKey :modifiers :disabled :checked :menuItemID];
|
||||
}
|
||||
|
||||
void UpdateMenuItem(void* nsmenuitem, int checked) {
|
||||
ON_MAIN_THREAD(
|
||||
WailsMenuItem *menuItem = (__bridge WailsMenuItem*) nsmenuitem;
|
||||
[menuItem setState:(checked == 1?NSControlStateValueOn:NSControlStateValueOff)];
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
void AppendSeparator(void* inMenu) {
|
||||
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
|
||||
[menu AppendSeparator];
|
||||
}
|
||||
|
||||
|
||||
bool IsNotificationAvailable(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
return [ctx IsNotificationAvailable];
|
||||
}
|
||||
|
||||
bool CheckBundleIdentifier(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
return [ctx CheckBundleIdentifier];
|
||||
}
|
||||
|
||||
bool EnsureDelegateInitialized(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
return [ctx EnsureDelegateInitialized];
|
||||
}
|
||||
|
||||
void RequestNotificationAuthorization(void *inctx, int channelID) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RequestNotificationAuthorization:channelID];
|
||||
}
|
||||
|
||||
void CheckNotificationAuthorization(void *inctx, int channelID) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx CheckNotificationAuthorization:channelID];
|
||||
}
|
||||
|
||||
void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx SendNotification:channelID :identifier :title :subtitle :body :data_json];
|
||||
}
|
||||
|
||||
void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
|
||||
[ctx SendNotificationWithActions:channelID :identifier :title :subtitle :body :categoryId :actions_json];
|
||||
}
|
||||
|
||||
void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
|
||||
[ctx RegisterNotificationCategory:channelID :categoryId :actions_json :hasReplyField :replyPlaceholder :replyButtonTitle];
|
||||
}
|
||||
|
||||
void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
|
||||
[ctx RemoveNotificationCategory:channelID :categoryId];
|
||||
}
|
||||
|
||||
void RemoveAllPendingNotifications(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RemoveAllPendingNotifications];
|
||||
}
|
||||
|
||||
void RemovePendingNotification(void *inctx, const char *identifier) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RemovePendingNotification:identifier];
|
||||
}
|
||||
|
||||
void RemoveAllDeliveredNotifications(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RemoveAllDeliveredNotifications];
|
||||
}
|
||||
|
||||
void RemoveDeliveredNotification(void *inctx, const char *identifier) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RemoveDeliveredNotification:identifier];
|
||||
}
|
||||
|
||||
|
||||
void Run(void *inctx, const char* url) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
AppDelegate* delegate = [AppDelegate new];
|
||||
[app setDelegate:(id)delegate];
|
||||
ctx.appdelegate = delegate;
|
||||
delegate.mainWindow = ctx.mainWindow;
|
||||
delegate.alwaysOnTop = ctx.alwaysOnTop;
|
||||
delegate.startHidden = ctx.startHidden;
|
||||
delegate.singleInstanceLockEnabled = ctx.singleInstanceLockEnabled;
|
||||
delegate.singleInstanceUniqueId = ctx.singleInstanceUniqueId;
|
||||
delegate.startFullscreen = ctx.startFullscreen;
|
||||
|
||||
NSString *_url = safeInit(url);
|
||||
[ctx loadRequest:_url];
|
||||
[_url release];
|
||||
|
||||
[app setMainMenu:ctx.applicationMenu];
|
||||
}
|
||||
|
||||
void RunMainLoop(void) {
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
[app run];
|
||||
}
|
||||
|
||||
void ReleaseContext(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
[ctx release];
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/q/33319295
|
||||
void WindowPrint(void *inctx) {
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
|
||||
if (@available(macOS 11.0, *)) {
|
||||
ON_MAIN_THREAD(
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
WKWebView* webView = ctx.webview;
|
||||
|
||||
// I think this should be exposed as a config
|
||||
// It directly affects the printed output/PDF
|
||||
NSPrintInfo *pInfo = [NSPrintInfo sharedPrintInfo];
|
||||
pInfo.horizontalPagination = NSPrintingPaginationModeAutomatic;
|
||||
pInfo.verticalPagination = NSPrintingPaginationModeAutomatic;
|
||||
pInfo.verticallyCentered = YES;
|
||||
pInfo.horizontallyCentered = YES;
|
||||
pInfo.orientation = NSPaperOrientationLandscape;
|
||||
pInfo.leftMargin = 0;
|
||||
pInfo.rightMargin = 0;
|
||||
pInfo.topMargin = 0;
|
||||
pInfo.bottomMargin = 0;
|
||||
|
||||
NSPrintOperation *po = [webView printOperationWithPrintInfo:pInfo];
|
||||
po.showsPrintPanel = YES;
|
||||
po.showsProgressPanel = YES;
|
||||
|
||||
po.view.frame = webView.bounds;
|
||||
|
||||
[po runOperationModalForWindow:ctx.mainWindow delegate:ctx.mainWindow.delegate didRunSelector:nil contextInfo:nil];
|
||||
)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
14
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/CustomProtocol.h
generated
vendored
Normal file
14
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/CustomProtocol.h
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef CustomProtocol_h
|
||||
#define CustomProtocol_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
extern void HandleOpenURL(char*);
|
||||
|
||||
@interface CustomProtocolSchemeHandler : NSObject
|
||||
+ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
|
||||
@end
|
||||
|
||||
void StartCustomProtocolHandler(void);
|
||||
|
||||
#endif /* CustomProtocol_h */
|
||||
20
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/CustomProtocol.m
generated
vendored
Normal file
20
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/CustomProtocol.m
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "CustomProtocol.h"
|
||||
|
||||
@implementation CustomProtocolSchemeHandler
|
||||
+ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
|
||||
[event paramDescriptorForKeyword:keyDirectObject];
|
||||
|
||||
NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
|
||||
|
||||
HandleOpenURL((char*)[[[event paramDescriptorForKeyword:keyDirectObject] stringValue] UTF8String]);
|
||||
}
|
||||
@end
|
||||
|
||||
void StartCustomProtocolHandler(void) {
|
||||
NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
|
||||
|
||||
[appleEventManager setEventHandler:[CustomProtocolSchemeHandler class]
|
||||
andSelector:@selector(handleGetURLEvent:withReplyEvent:)
|
||||
forEventClass:kInternetEventClass
|
||||
andEventID: kAEGetURL];
|
||||
}
|
||||
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Role.h
generated
vendored
Normal file
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Role.h
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Role.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 24/10/21.
|
||||
//
|
||||
|
||||
#ifndef Role_h
|
||||
#define Role_h
|
||||
|
||||
typedef int Role;
|
||||
|
||||
static const Role AppMenu = 1;
|
||||
static const Role EditMenu = 2;
|
||||
static const Role WindowMenu = 3;
|
||||
|
||||
#endif /* Role_h */
|
||||
18
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsAlert.h
generated
vendored
Normal file
18
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsAlert.h
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// WailsAlert.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 20/10/21.
|
||||
//
|
||||
|
||||
#ifndef WailsAlert_h
|
||||
#define WailsAlert_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface WailsAlert : NSAlert
|
||||
- (void)addButton:(NSString*)text :(NSString*)defaultButton :(NSString*)cancelButton;
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WailsAlert_h */
|
||||
31
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsAlert.m
generated
vendored
Normal file
31
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsAlert.m
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// WailsAlert.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 20/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "WailsAlert.h"
|
||||
|
||||
@implementation WailsAlert
|
||||
|
||||
- (void)addButton:(NSString*)text :(NSString*)defaultButton :(NSString*)cancelButton {
|
||||
if( text == nil ) {
|
||||
return;
|
||||
}
|
||||
NSButton *button = [self addButtonWithTitle:text];
|
||||
if( defaultButton != nil && [text isEqualToString:defaultButton]) {
|
||||
[button setKeyEquivalent:@"\r"];
|
||||
} else if( cancelButton != nil && [text isEqualToString:cancelButton]) {
|
||||
[button setKeyEquivalent:@"\033"];
|
||||
} else {
|
||||
[button setKeyEquivalent:@""];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
123
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsContext.h
generated
vendored
Normal file
123
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsContext.h
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// WailsContext.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#ifndef WailsContext_h
|
||||
#define WailsContext_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#import "WailsWebView.h"
|
||||
|
||||
#if __has_include(<UniformTypeIdentifiers/UTType.h>)
|
||||
#define USE_NEW_FILTERS
|
||||
#import <UniformTypeIdentifiers/UTType.h>
|
||||
#endif
|
||||
|
||||
#define ON_MAIN_THREAD(str) dispatch_async(dispatch_get_main_queue(), ^{ str; });
|
||||
#define unicode(input) [NSString stringWithFormat:@"%C", input]
|
||||
|
||||
@interface WailsWindow : NSWindow
|
||||
|
||||
@property NSSize userMinSize;
|
||||
@property NSSize userMaxSize;
|
||||
|
||||
- (BOOL) canBecomeKeyWindow;
|
||||
- (void) applyWindowConstraints;
|
||||
- (void) disableWindowConstraints;
|
||||
@end
|
||||
|
||||
@interface WailsContext : NSObject <WKURLSchemeHandler,WKScriptMessageHandler,WKNavigationDelegate,WKUIDelegate>
|
||||
|
||||
@property (retain) WailsWindow* mainWindow;
|
||||
@property (retain) WailsWebView* webview;
|
||||
@property (nonatomic, assign) id appdelegate;
|
||||
|
||||
@property bool hideOnClose;
|
||||
@property bool shuttingDown;
|
||||
@property bool startHidden;
|
||||
@property bool startFullscreen;
|
||||
|
||||
@property bool singleInstanceLockEnabled;
|
||||
@property (retain) NSString* singleInstanceUniqueId;
|
||||
|
||||
@property (retain) NSEvent* mouseEvent;
|
||||
|
||||
@property bool alwaysOnTop;
|
||||
|
||||
@property bool devtoolsEnabled;
|
||||
@property bool defaultContextMenuEnabled;
|
||||
|
||||
@property (retain) WKUserContentController* userContentController;
|
||||
|
||||
@property (retain) NSMenu* applicationMenu;
|
||||
|
||||
@property (retain) NSImage* aboutImage;
|
||||
@property (retain) NSString* aboutTitle;
|
||||
@property (retain) NSString* aboutDescription;
|
||||
|
||||
struct Preferences {
|
||||
bool *tabFocusesLinks;
|
||||
bool *textInteractionEnabled;
|
||||
bool *fullscreenEnabled;
|
||||
};
|
||||
|
||||
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences :(bool)enableDragAndDrop :(bool)disableWebViewDragAndDrop;
|
||||
- (void) SetSize:(int)width :(int)height;
|
||||
- (void) SetPosition:(int)x :(int) y;
|
||||
- (void) SetMinSize:(int)minWidth :(int)minHeight;
|
||||
- (void) SetMaxSize:(int)maxWidth :(int)maxHeight;
|
||||
- (void) SetTitle:(NSString*)title;
|
||||
- (void) SetAlwaysOnTop:(int)onTop;
|
||||
- (void) Center;
|
||||
- (void) Fullscreen;
|
||||
- (void) UnFullscreen;
|
||||
- (bool) IsFullScreen;
|
||||
- (void) Minimise;
|
||||
- (void) UnMinimise;
|
||||
- (bool) IsMinimised;
|
||||
- (void) Maximise;
|
||||
- (void) ToggleMaximise;
|
||||
- (void) UnMaximise;
|
||||
- (bool) IsMaximised;
|
||||
- (void) SetBackgroundColour:(int)r :(int)g :(int)b :(int)a;
|
||||
- (void) HideMouse;
|
||||
- (void) ShowMouse;
|
||||
- (void) Hide;
|
||||
- (void) Show;
|
||||
- (void) HideApplication;
|
||||
- (void) ShowApplication;
|
||||
- (void) Quit;
|
||||
|
||||
- (void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength;
|
||||
- (void) OpenFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)allowDirectories :(bool)allowFiles :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)resolveAliases :(bool)showHiddenFiles :(bool)allowMultipleSelection :(NSString*)filters;
|
||||
- (void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters;
|
||||
|
||||
- (bool) IsNotificationAvailable;
|
||||
- (bool) CheckBundleIdentifier;
|
||||
- (bool) EnsureDelegateInitialized;
|
||||
- (void) RequestNotificationAuthorization:(int)channelID;
|
||||
- (void) CheckNotificationAuthorization:(int)channelID;
|
||||
- (void) SendNotification:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)dataJSON;
|
||||
- (void) SendNotificationWithActions:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)categoryId :(const char *)actionsJSON;
|
||||
- (void) RegisterNotificationCategory:(int)channelID :(const char *)categoryId :(const char *)actionsJSON :(bool)hasReplyField :(const char *)replyPlaceholder :(const char *)replyButtonTitle;
|
||||
- (void) RemoveNotificationCategory:(int)channelID :(const char *)categoryId;
|
||||
- (void) RemoveAllPendingNotifications;
|
||||
- (void) RemovePendingNotification:(const char *)identifier;
|
||||
- (void) RemoveAllDeliveredNotifications;
|
||||
- (void) RemoveDeliveredNotification:(const char *)identifier;
|
||||
|
||||
- (void) loadRequest:(NSString*)url;
|
||||
- (void) ExecJS:(NSString*)script;
|
||||
- (NSScreen*) getCurrentScreen;
|
||||
|
||||
- (void) SetAbout :(NSString*)title :(NSString*)description :(void*)imagedata :(int)datalen;
|
||||
- (void) dealloc;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WailsContext_h */
|
||||
1115
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsContext.m
generated
vendored
Normal file
1115
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsContext.m
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
30
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenu.h
generated
vendored
Normal file
30
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenu.h
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// WailsMenu.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 25/10/21.
|
||||
//
|
||||
|
||||
#ifndef WailsMenu_h
|
||||
#define WailsMenu_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "Role.h"
|
||||
#import "WailsMenu.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
@interface WailsMenu : NSMenu
|
||||
|
||||
//- (void) AddMenuByRole :(Role)role;
|
||||
- (WailsMenu*) initWithNSTitle :(NSString*)title;
|
||||
- (void) appendSubmenu :(WailsMenu*)child;
|
||||
- (void) appendRole :(WailsContext*)ctx :(Role)role;
|
||||
|
||||
- (NSMenuItem*) newMenuItemWithContext :(WailsContext*)ctx :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags;
|
||||
- (void*) AppendMenuItem :(WailsContext*)ctx :(NSString*)label :(NSString *)shortcutKey :(int)modifiers :(bool)disabled :(bool)checked :(int)menuItemID;
|
||||
- (void) AppendSeparator;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WailsMenu_h */
|
||||
340
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenu.m
generated
vendored
Normal file
340
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenu.m
generated
vendored
Normal file
@@ -0,0 +1,340 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// WailsMenu.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 25/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "WailsMenu.h"
|
||||
#import "WailsMenuItem.h"
|
||||
#import "Role.h"
|
||||
|
||||
@implementation WailsMenu
|
||||
|
||||
- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
|
||||
NSMenuItem *result = [[[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:key] autorelease];
|
||||
[result setKeyEquivalentModifierMask:flags];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSMenuItem*) newMenuItemWithContext :(WailsContext*)ctx :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
|
||||
NSMenuItem *result = [NSMenuItem new];
|
||||
if ( title != nil ) {
|
||||
[result setTitle:title];
|
||||
}
|
||||
if (selector != nil) {
|
||||
[result setAction:selector];
|
||||
}
|
||||
if (key) {
|
||||
[result setKeyEquivalent:key];
|
||||
}
|
||||
if( flags != 0 ) {
|
||||
[result setKeyEquivalentModifierMask:flags];
|
||||
}
|
||||
result.target = ctx;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key {
|
||||
return [self newMenuItem :title :selector :key :0];
|
||||
}
|
||||
|
||||
- (WailsMenu*) initWithNSTitle:(NSString *)title {
|
||||
if( title != nil ) {
|
||||
[super initWithTitle:title];
|
||||
} else {
|
||||
[self init];
|
||||
}
|
||||
[self setAutoenablesItems:NO];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) appendSubmenu :(WailsMenu*)child {
|
||||
NSMenuItem *childMenuItem = [[NSMenuItem new] autorelease];
|
||||
[childMenuItem setTitle:child.title];
|
||||
[self addItem:childMenuItem];
|
||||
[childMenuItem setSubmenu:child];
|
||||
}
|
||||
|
||||
- (void) appendRole :(WailsContext*)ctx :(Role)role {
|
||||
|
||||
switch(role) {
|
||||
case AppMenu:
|
||||
{
|
||||
NSString *appName = [NSRunningApplication currentApplication].localizedName;
|
||||
if( appName == nil ) {
|
||||
appName = [[NSProcessInfo processInfo] processName];
|
||||
}
|
||||
WailsMenu *appMenu = [[[WailsMenu new] initWithNSTitle:appName] autorelease];
|
||||
|
||||
if (ctx.aboutTitle != nil) {
|
||||
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
[appMenu addItem:[self newMenuItem:[@"Hide " stringByAppendingString:appName] :@selector(hide:) :@"h" :NSEventModifierFlagCommand]];
|
||||
[appMenu addItem:[self newMenuItem:@"Hide Others" :@selector(hideOtherApplications:) :@"h" :(NSEventModifierFlagOption | NSEventModifierFlagCommand)]];
|
||||
[appMenu addItem:[self newMenuItem:@"Show All" :@selector(unhideAllApplications:) :@""]];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
id quitTitle = [@"Quit " stringByAppendingString:appName];
|
||||
NSMenuItem* quitMenuItem = [self newMenuItem:quitTitle :@selector(Quit) :@"q" :NSEventModifierFlagCommand];
|
||||
quitMenuItem.target = ctx;
|
||||
[appMenu addItem:quitMenuItem];
|
||||
[self appendSubmenu:appMenu];
|
||||
break;
|
||||
}
|
||||
case EditMenu:
|
||||
{
|
||||
WailsMenu *editMenu = [[[WailsMenu new] initWithNSTitle:@"Edit"] autorelease];
|
||||
[editMenu addItem:[self newMenuItem:@"Undo" :@selector(undo:) :@"z" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[self newMenuItem:@"Redo" :@selector(redo:) :@"z" :(NSEventModifierFlagShift | NSEventModifierFlagCommand)]];
|
||||
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||
[editMenu addItem:[self newMenuItem:@"Cut" :@selector(cut:) :@"x" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[self newMenuItem:@"Copy" :@selector(copy:) :@"c" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[self newMenuItem:@"Paste" :@selector(paste:) :@"v" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[self newMenuItem:@"Paste and Match Style" :@selector(pasteAsRichText:) :@"v" :(NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand)]];
|
||||
[editMenu addItem:[self newMenuItem:@"Delete" :@selector(delete:) :[self accel:@"backspace"] :0]];
|
||||
[editMenu addItem:[self newMenuItem:@"Select All" :@selector(selectAll:) :@"a" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||
// NSMenuItem *speechMenuItem = [[NSMenuItem new] autorelease];
|
||||
// [speechMenuItem setTitle:@"Speech"];
|
||||
// [editMenu addItem:speechMenuItem];
|
||||
WailsMenu *speechMenu = [[[WailsMenu new] initWithNSTitle:@"Speech"] autorelease];
|
||||
[speechMenu addItem:[self newMenuItem:@"Start Speaking" :@selector(startSpeaking:) :@""]];
|
||||
[speechMenu addItem:[self newMenuItem:@"Stop Speaking" :@selector(stopSpeaking:) :@""]];
|
||||
[editMenu appendSubmenu:speechMenu];
|
||||
[self appendSubmenu:editMenu];
|
||||
|
||||
break;
|
||||
}
|
||||
case WindowMenu:
|
||||
{
|
||||
WailsMenu *windowMenu = [[[WailsMenu new] initWithNSTitle:@"Window"] autorelease];
|
||||
[windowMenu addItem:[self newMenuItem:@"Minimize" :@selector(performMiniaturize:) :@"m" :NSEventModifierFlagCommand]];
|
||||
[windowMenu addItem:[self newMenuItem:@"Zoom" :@selector(performZoom:) :@""]];
|
||||
[windowMenu addItem:[NSMenuItem separatorItem]];
|
||||
[windowMenu addItem:[self newMenuItem:@"Full Screen" :@selector(enterFullScreenMode:) :@"f" :(NSEventModifierFlagControl | NSEventModifierFlagCommand)]];
|
||||
[self appendSubmenu:windowMenu];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void*) AppendMenuItem :(WailsContext*)ctx :(NSString*)label :(NSString *)shortcutKey :(int)modifiers :(bool)disabled :(bool)checked :(int)menuItemID {
|
||||
|
||||
NSString *nslabel = @"";
|
||||
if (label != nil ) {
|
||||
nslabel = label;
|
||||
}
|
||||
WailsMenuItem *menuItem = [WailsMenuItem new];
|
||||
|
||||
// Label
|
||||
menuItem.title = nslabel;
|
||||
|
||||
// Process callback
|
||||
menuItem.menuItemID = menuItemID;
|
||||
menuItem.action = @selector(handleClick);
|
||||
menuItem.target = menuItem;
|
||||
|
||||
// Shortcut
|
||||
if (shortcutKey != nil) {
|
||||
[menuItem setKeyEquivalent:[self accel:shortcutKey]];
|
||||
[menuItem setKeyEquivalentModifierMask:modifiers];
|
||||
}
|
||||
|
||||
// Enabled/Disabled
|
||||
[menuItem setEnabled:!disabled];
|
||||
|
||||
// Checked
|
||||
[menuItem setState:(checked ? NSControlStateValueOn : NSControlStateValueOff)];
|
||||
|
||||
[self addItem:menuItem];
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
- (void) AppendSeparator {
|
||||
[self addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
|
||||
- (NSString*) accel :(NSString*)key {
|
||||
|
||||
// Guard against no accelerator key
|
||||
if( key == NULL ) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
if( [key isEqualToString:@"backspace"] ) {
|
||||
return unicode(0x0008);
|
||||
}
|
||||
if( [key isEqualToString:@"tab"] ) {
|
||||
return unicode(0x0009);
|
||||
}
|
||||
if( [key isEqualToString:@"return"] ) {
|
||||
return unicode(0x000d);
|
||||
}
|
||||
if( [key isEqualToString:@"enter"] ) {
|
||||
return unicode(0x000d);
|
||||
}
|
||||
if( [key isEqualToString:@"escape"] ) {
|
||||
return unicode(0x001b);
|
||||
}
|
||||
if( [key isEqualToString:@"left"] ) {
|
||||
return unicode(0x001c);
|
||||
}
|
||||
if( [key isEqualToString:@"right"] ) {
|
||||
return unicode(0x001d);
|
||||
}
|
||||
if( [key isEqualToString:@"up"] ) {
|
||||
return unicode(0x001e);
|
||||
}
|
||||
if( [key isEqualToString:@"down"] ) {
|
||||
return unicode(0x001f);
|
||||
}
|
||||
if( [key isEqualToString:@"space"] ) {
|
||||
return unicode(0x0020);
|
||||
}
|
||||
if( [key isEqualToString:@"delete"] ) {
|
||||
return unicode(0x007f);
|
||||
}
|
||||
if( [key isEqualToString:@"home"] ) {
|
||||
return unicode(0x2196);
|
||||
}
|
||||
if( [key isEqualToString:@"end"] ) {
|
||||
return unicode(0x2198);
|
||||
}
|
||||
if( [key isEqualToString:@"page up"] ) {
|
||||
return unicode(0x21de);
|
||||
}
|
||||
if( [key isEqualToString:@"page down"] ) {
|
||||
return unicode(0x21df);
|
||||
}
|
||||
if( [key isEqualToString:@"f1"] ) {
|
||||
return unicode(0xf704);
|
||||
}
|
||||
if( [key isEqualToString:@"f2"] ) {
|
||||
return unicode(0xf705);
|
||||
}
|
||||
if( [key isEqualToString:@"f3"] ) {
|
||||
return unicode(0xf706);
|
||||
}
|
||||
if( [key isEqualToString:@"f4"] ) {
|
||||
return unicode(0xf707);
|
||||
}
|
||||
if( [key isEqualToString:@"f5"] ) {
|
||||
return unicode(0xf708);
|
||||
}
|
||||
if( [key isEqualToString:@"f6"] ) {
|
||||
return unicode(0xf709);
|
||||
}
|
||||
if( [key isEqualToString:@"f7"] ) {
|
||||
return unicode(0xf70a);
|
||||
}
|
||||
if( [key isEqualToString:@"f8"] ) {
|
||||
return unicode(0xf70b);
|
||||
}
|
||||
if( [key isEqualToString:@"f9"] ) {
|
||||
return unicode(0xf70c);
|
||||
}
|
||||
if( [key isEqualToString:@"f10"] ) {
|
||||
return unicode(0xf70d);
|
||||
}
|
||||
if( [key isEqualToString:@"f11"] ) {
|
||||
return unicode(0xf70e);
|
||||
}
|
||||
if( [key isEqualToString:@"f12"] ) {
|
||||
return unicode(0xf70f);
|
||||
}
|
||||
if( [key isEqualToString:@"f13"] ) {
|
||||
return unicode(0xf710);
|
||||
}
|
||||
if( [key isEqualToString:@"f14"] ) {
|
||||
return unicode(0xf711);
|
||||
}
|
||||
if( [key isEqualToString:@"f15"] ) {
|
||||
return unicode(0xf712);
|
||||
}
|
||||
if( [key isEqualToString:@"f16"] ) {
|
||||
return unicode(0xf713);
|
||||
}
|
||||
if( [key isEqualToString:@"f17"] ) {
|
||||
return unicode(0xf714);
|
||||
}
|
||||
if( [key isEqualToString:@"f18"] ) {
|
||||
return unicode(0xf715);
|
||||
}
|
||||
if( [key isEqualToString:@"f19"] ) {
|
||||
return unicode(0xf716);
|
||||
}
|
||||
if( [key isEqualToString:@"f20"] ) {
|
||||
return unicode(0xf717);
|
||||
}
|
||||
if( [key isEqualToString:@"f21"] ) {
|
||||
return unicode(0xf718);
|
||||
}
|
||||
if( [key isEqualToString:@"f22"] ) {
|
||||
return unicode(0xf719);
|
||||
}
|
||||
if( [key isEqualToString:@"f23"] ) {
|
||||
return unicode(0xf71a);
|
||||
}
|
||||
if( [key isEqualToString:@"f24"] ) {
|
||||
return unicode(0xf71b);
|
||||
}
|
||||
if( [key isEqualToString:@"f25"] ) {
|
||||
return unicode(0xf71c);
|
||||
}
|
||||
if( [key isEqualToString:@"f26"] ) {
|
||||
return unicode(0xf71d);
|
||||
}
|
||||
if( [key isEqualToString:@"f27"] ) {
|
||||
return unicode(0xf71e);
|
||||
}
|
||||
if( [key isEqualToString:@"f28"] ) {
|
||||
return unicode(0xf71f);
|
||||
}
|
||||
if( [key isEqualToString:@"f29"] ) {
|
||||
return unicode(0xf720);
|
||||
}
|
||||
if( [key isEqualToString:@"f30"] ) {
|
||||
return unicode(0xf721);
|
||||
}
|
||||
if( [key isEqualToString:@"f31"] ) {
|
||||
return unicode(0xf722);
|
||||
}
|
||||
if( [key isEqualToString:@"f32"] ) {
|
||||
return unicode(0xf723);
|
||||
}
|
||||
if( [key isEqualToString:@"f33"] ) {
|
||||
return unicode(0xf724);
|
||||
}
|
||||
if( [key isEqualToString:@"f34"] ) {
|
||||
return unicode(0xf725);
|
||||
}
|
||||
if( [key isEqualToString:@"f35"] ) {
|
||||
return unicode(0xf726);
|
||||
}
|
||||
// if( [key isEqualToString:@"Insert"] ) {
|
||||
// return unicode(0xf727);
|
||||
// }
|
||||
// if( [key isEqualToString:@"PrintScreen"] ) {
|
||||
// return unicode(0xf72e);
|
||||
// }
|
||||
// if( [key isEqualToString:@"ScrollLock"] ) {
|
||||
// return unicode(0xf72f);
|
||||
// }
|
||||
if( [key isEqualToString:@"numLock"] ) {
|
||||
return unicode(0xf739);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
22
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenuItem.h
generated
vendored
Normal file
22
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenuItem.h
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// WailsMenuItem.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 27/10/21.
|
||||
//
|
||||
|
||||
#ifndef WailsMenuItem_h
|
||||
#define WailsMenuItem_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface WailsMenuItem : NSMenuItem
|
||||
|
||||
@property int menuItemID;
|
||||
|
||||
- (void) handleClick;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WailsMenuItem_h */
|
||||
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenuItem.m
generated
vendored
Normal file
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenuItem.m
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// WailsMenuItem.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 27/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "WailsMenuItem.h"
|
||||
#include "message.h"
|
||||
|
||||
|
||||
@implementation WailsMenuItem
|
||||
|
||||
- (void) handleClick {
|
||||
processCallback(self.menuItemID);
|
||||
}
|
||||
|
||||
@end
|
||||
14
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsWebView.h
generated
vendored
Normal file
14
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsWebView.h
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef WailsWebView_h
|
||||
#define WailsWebView_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
// We will override WKWebView, so we can detect file drop in obj-c
|
||||
// and grab their file path, to then inject into JS
|
||||
@interface WailsWebView : WKWebView
|
||||
@property bool disableWebViewDragAndDrop;
|
||||
@property bool enableDragAndDrop;
|
||||
@end
|
||||
|
||||
#endif /* WailsWebView_h */
|
||||
122
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsWebView.m
generated
vendored
Normal file
122
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsWebView.m
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
#import "WailsWebView.h"
|
||||
#import "message.h"
|
||||
|
||||
|
||||
@implementation WailsWebView
|
||||
@synthesize disableWebViewDragAndDrop;
|
||||
@synthesize enableDragAndDrop;
|
||||
|
||||
- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)sender
|
||||
{
|
||||
if ( !enableDragAndDrop ) {
|
||||
return [super prepareForDragOperation: sender];
|
||||
}
|
||||
|
||||
if ( disableWebViewDragAndDrop ) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [super prepareForDragOperation: sender];
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
if ( !enableDragAndDrop ) {
|
||||
return [super performDragOperation: sender];
|
||||
}
|
||||
|
||||
NSPasteboard *pboard = [sender draggingPasteboard];
|
||||
|
||||
// if no types, then we'll just let the WKWebView handle the drag-n-drop as normal
|
||||
NSArray<NSPasteboardType> * types = [pboard types];
|
||||
if( !types )
|
||||
return [super performDragOperation: sender];
|
||||
|
||||
// getting all NSURL types
|
||||
NSArray<Class> *url_class = @[[NSURL class]];
|
||||
NSDictionary *options = @{};
|
||||
NSArray<NSURL*> *files = [pboard readObjectsForClasses:url_class options:options];
|
||||
|
||||
// collecting all file paths
|
||||
NSMutableArray *files_strs = [[NSMutableArray alloc] init];
|
||||
for (NSURL *url in files)
|
||||
{
|
||||
const char *fs_path = [url fileSystemRepresentation]; //Will be UTF-8 encoded
|
||||
NSString *fs_path_str = [[NSString alloc] initWithCString:fs_path encoding:NSUTF8StringEncoding];
|
||||
[files_strs addObject:fs_path_str];
|
||||
// NSLog( @"performDragOperation: file path: %s", fs_path );
|
||||
}
|
||||
|
||||
NSString *joined=[files_strs componentsJoinedByString:@"\n"];
|
||||
|
||||
// Release the array of file paths
|
||||
[files_strs release];
|
||||
|
||||
int dragXLocation = [sender draggingLocation].x - [self frame].origin.x;
|
||||
int dragYLocation = [self frame].size.height - [sender draggingLocation].y; // Y coordinate is inverted, so we need to subtract from the height
|
||||
|
||||
// NSLog( @"draggingUpdated: X coord: %d", dragXLocation );
|
||||
// NSLog( @"draggingUpdated: Y coord: %d", dragYLocation );
|
||||
|
||||
NSString *message = [NSString stringWithFormat:@"DD:%d:%d:%@", dragXLocation, dragYLocation, joined];
|
||||
|
||||
const char* res = message.UTF8String;
|
||||
|
||||
processMessage(res);
|
||||
|
||||
if ( disableWebViewDragAndDrop ) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [super performDragOperation: sender];
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender {
|
||||
if ( !enableDragAndDrop ) {
|
||||
return [super draggingUpdated: sender];
|
||||
}
|
||||
|
||||
NSPasteboard *pboard = [sender draggingPasteboard];
|
||||
|
||||
// if no types, then we'll just let the WKWebView handle the drag-n-drop as normal
|
||||
NSArray<NSPasteboardType> * types = [pboard types];
|
||||
if( !types ) {
|
||||
return [super draggingUpdated: sender];
|
||||
}
|
||||
|
||||
if ( disableWebViewDragAndDrop ) {
|
||||
// we should call supper as otherwise events will not pass
|
||||
[super draggingUpdated: sender];
|
||||
|
||||
// pass NSDragOperationGeneric = 4 to show regular hover for drag and drop. As we want to ignore webkit behaviours that depends on webpage
|
||||
return 4;
|
||||
}
|
||||
|
||||
return [super draggingUpdated: sender];
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
|
||||
if ( !enableDragAndDrop ) {
|
||||
return [super draggingEntered: sender];
|
||||
}
|
||||
|
||||
NSPasteboard *pboard = [sender draggingPasteboard];
|
||||
|
||||
// if no types, then we'll just let the WKWebView handle the drag-n-drop as normal
|
||||
NSArray<NSPasteboardType> * types = [pboard types];
|
||||
if( !types ) {
|
||||
return [super draggingEntered: sender];
|
||||
}
|
||||
|
||||
if ( disableWebViewDragAndDrop ) {
|
||||
// we should call supper as otherwise events will not pass
|
||||
[super draggingEntered: sender];
|
||||
|
||||
// pass NSDragOperationGeneric = 4 to show regular hover for drag and drop. As we want to ignore webkit behaviours that depends on webpage
|
||||
return 4;
|
||||
}
|
||||
|
||||
return [super draggingEntered: sender];
|
||||
}
|
||||
|
||||
@end
|
||||
25
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WindowDelegate.h
generated
vendored
Normal file
25
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WindowDelegate.h
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// WindowDelegate.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#ifndef WindowDelegate_h
|
||||
#define WindowDelegate_h
|
||||
|
||||
#import "WailsContext.h"
|
||||
|
||||
@interface WindowDelegate : NSObject <NSWindowDelegate>
|
||||
|
||||
@property bool hideOnClose;
|
||||
|
||||
@property (assign) WailsContext* ctx;
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)notification;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WindowDelegate_h */
|
||||
38
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WindowDelegate.m
generated
vendored
Normal file
38
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WindowDelegate.m
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// WindowDelegate.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "WindowDelegate.h"
|
||||
#import "message.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
@implementation WindowDelegate
|
||||
- (BOOL)windowShouldClose:(WailsWindow *)sender {
|
||||
if( self.hideOnClose ) {
|
||||
[NSApp hide:nil];
|
||||
return false;
|
||||
}
|
||||
processMessage("Q");
|
||||
return false;
|
||||
}
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)notification {
|
||||
[self.ctx.mainWindow applyWindowConstraints];
|
||||
}
|
||||
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||
[self.ctx.mainWindow disableWindowConstraints];
|
||||
}
|
||||
|
||||
- (NSApplicationPresentationOptions)window:(WailsWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions {
|
||||
return NSApplicationPresentationAutoHideToolbar | NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationFullScreen;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
24
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/browser.go
generated
vendored
Normal file
24
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/browser.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/utils"
|
||||
)
|
||||
|
||||
// BrowserOpenURL Use the default browser to open the url
|
||||
func (f *Frontend) BrowserOpenURL(rawURL string) {
|
||||
url, err := utils.ValidateAndSanitizeURL(rawURL)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Invalid URL %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Specific method implementation
|
||||
if err := browser.OpenURL(url); err != nil {
|
||||
f.logger.Error("Unable to open default system browser")
|
||||
}
|
||||
}
|
||||
51
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/callbacks.go
generated
vendored
Normal file
51
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/callbacks.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
func (f *Frontend) handleCallback(menuItemID uint) error {
|
||||
menuItem := getMenuItemForID(menuItemID)
|
||||
if menuItem == nil {
|
||||
return errors.New("unknown menuItem ID: " + strconv.Itoa(int(menuItemID)))
|
||||
}
|
||||
|
||||
wailsMenuItem := menuItem.wailsMenuItem
|
||||
if wailsMenuItem.Type == menu.CheckboxType {
|
||||
wailsMenuItem.Checked = !wailsMenuItem.Checked
|
||||
C.UpdateMenuItem(menuItem.nsmenuitem, bool2Cint(wailsMenuItem.Checked))
|
||||
}
|
||||
if wailsMenuItem.Type == menu.RadioType {
|
||||
// Ignore if we clicked the item that is already checked
|
||||
if !wailsMenuItem.Checked {
|
||||
for _, item := range menuItem.radioGroupMembers {
|
||||
if item.wailsMenuItem.Checked {
|
||||
item.wailsMenuItem.Checked = false
|
||||
C.UpdateMenuItem(item.nsmenuitem, C.int(0))
|
||||
}
|
||||
}
|
||||
wailsMenuItem.Checked = true
|
||||
C.UpdateMenuItem(menuItem.nsmenuitem, C.int(1))
|
||||
}
|
||||
}
|
||||
if wailsMenuItem.Click != nil {
|
||||
go wailsMenuItem.Click(&menu.CallbackData{MenuItem: wailsMenuItem})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
34
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/calloc.go
generated
vendored
Normal file
34
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/calloc.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
//go:build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// Calloc handles alloc/dealloc of C data
|
||||
type Calloc struct {
|
||||
pool []unsafe.Pointer
|
||||
}
|
||||
|
||||
// NewCalloc creates a new allocator
|
||||
func NewCalloc() Calloc {
|
||||
return Calloc{}
|
||||
}
|
||||
|
||||
// String creates a new C string and retains a reference to it
|
||||
func (c Calloc) String(in string) *C.char {
|
||||
result := C.CString(in)
|
||||
c.pool = append(c.pool, unsafe.Pointer(result))
|
||||
return result
|
||||
}
|
||||
|
||||
// Free frees all allocated C memory
|
||||
func (c Calloc) Free() {
|
||||
for _, str := range c.pool {
|
||||
C.free(str)
|
||||
}
|
||||
c.pool = []unsafe.Pointer{}
|
||||
}
|
||||
50
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/clipboard.go
generated
vendored
Normal file
50
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/clipboard.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
//go:build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// ensureUTF8Env returns the current environment with LANG set to en_US.UTF-8
|
||||
// if it is not already set. This is needed because packaged macOS apps do not
|
||||
// inherit the terminal's LANG variable, causing pbpaste/pbcopy to default to
|
||||
// an ASCII-compatible encoding that mangles non-ASCII text.
|
||||
func ensureUTF8Env() []string {
|
||||
env := os.Environ()
|
||||
if _, ok := os.LookupEnv("LANG"); !ok {
|
||||
env = append(env, "LANG=en_US.UTF-8")
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func (f *Frontend) ClipboardGetText() (string, error) {
|
||||
pasteCmd := exec.Command("pbpaste")
|
||||
pasteCmd.Env = ensureUTF8Env()
|
||||
out, err := pasteCmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func (f *Frontend) ClipboardSetText(text string) error {
|
||||
copyCmd := exec.Command("pbcopy")
|
||||
copyCmd.Env = ensureUTF8Env()
|
||||
in, err := copyCmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := copyCmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := in.Write([]byte(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := in.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyCmd.Wait()
|
||||
}
|
||||
196
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/dialog.go
generated
vendored
Normal file
196
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/dialog.go
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
// Obj-C dialog methods send the response to this channel
|
||||
var (
|
||||
messageDialogResponse = make(chan int)
|
||||
openFileDialogResponse = make(chan string)
|
||||
saveFileDialogResponse = make(chan string)
|
||||
dialogLock sync.Mutex
|
||||
)
|
||||
|
||||
// OpenDirectoryDialog prompts the user to select a directory
|
||||
func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) {
|
||||
results, err := f.openDialog(&options, false, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var selected string
|
||||
if len(results) > 0 {
|
||||
selected = results[0]
|
||||
}
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
func (f *Frontend) openDialog(options *frontend.OpenDialogOptions, multiple bool, allowfiles bool, allowdirectories bool) ([]string, error) {
|
||||
dialogLock.Lock()
|
||||
defer dialogLock.Unlock()
|
||||
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
title := c.String(options.Title)
|
||||
defaultFilename := c.String(options.DefaultFilename)
|
||||
defaultDirectory := c.String(options.DefaultDirectory)
|
||||
allowDirectories := bool2Cint(allowdirectories)
|
||||
allowFiles := bool2Cint(allowfiles)
|
||||
canCreateDirectories := bool2Cint(options.CanCreateDirectories)
|
||||
treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories)
|
||||
resolveAliases := bool2Cint(options.ResolvesAliases)
|
||||
showHiddenFiles := bool2Cint(options.ShowHiddenFiles)
|
||||
allowMultipleFileSelection := bool2Cint(multiple)
|
||||
|
||||
var filterStrings slicer.StringSlicer
|
||||
if options.Filters != nil {
|
||||
for _, filter := range options.Filters {
|
||||
thesePatterns := strings.Split(filter.Pattern, ";")
|
||||
for _, pattern := range thesePatterns {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern != "" {
|
||||
filterStrings.Add(pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
filterStrings.Deduplicate()
|
||||
}
|
||||
filters := filterStrings.Join(";")
|
||||
C.OpenFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, allowDirectories, allowFiles, canCreateDirectories, treatPackagesAsDirectories, resolveAliases, showHiddenFiles, allowMultipleFileSelection, c.String(filters))
|
||||
|
||||
result := <-openFileDialogResponse
|
||||
|
||||
var parsedResults []string
|
||||
err := json.Unmarshal([]byte(result), &parsedResults)
|
||||
|
||||
return parsedResults, err
|
||||
}
|
||||
|
||||
// OpenFileDialog prompts the user to select a file
|
||||
func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
|
||||
results, err := f.openDialog(&options, false, true, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var selected string
|
||||
if len(results) > 0 {
|
||||
selected = results[0]
|
||||
}
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
// OpenMultipleFilesDialog prompts the user to select a file
|
||||
func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) {
|
||||
return f.openDialog(&options, true, true, false)
|
||||
}
|
||||
|
||||
// SaveFileDialog prompts the user to select a file
|
||||
func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) {
|
||||
dialogLock.Lock()
|
||||
defer dialogLock.Unlock()
|
||||
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
title := c.String(options.Title)
|
||||
defaultFilename := c.String(options.DefaultFilename)
|
||||
defaultDirectory := c.String(options.DefaultDirectory)
|
||||
canCreateDirectories := bool2Cint(options.CanCreateDirectories)
|
||||
treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories)
|
||||
showHiddenFiles := bool2Cint(options.ShowHiddenFiles)
|
||||
|
||||
var filterStrings slicer.StringSlicer
|
||||
if options.Filters != nil {
|
||||
for _, filter := range options.Filters {
|
||||
thesePatterns := strings.Split(filter.Pattern, ";")
|
||||
for _, pattern := range thesePatterns {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern != "" {
|
||||
filterStrings.Add(pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
filterStrings.Deduplicate()
|
||||
}
|
||||
filters := filterStrings.Join(";")
|
||||
C.SaveFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, canCreateDirectories, treatPackagesAsDirectories, showHiddenFiles, c.String(filters))
|
||||
|
||||
result := <-saveFileDialogResponse
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MessageDialog show a message dialog to the user
|
||||
func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
|
||||
dialogLock.Lock()
|
||||
defer dialogLock.Unlock()
|
||||
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
dialogType := c.String(string(options.Type))
|
||||
title := c.String(options.Title)
|
||||
message := c.String(options.Message)
|
||||
defaultButton := c.String(options.DefaultButton)
|
||||
cancelButton := c.String(options.CancelButton)
|
||||
const MaxButtons = 4
|
||||
var buttons [MaxButtons]*C.char
|
||||
for index, buttonText := range options.Buttons {
|
||||
if index == MaxButtons {
|
||||
return "", fmt.Errorf("max %d buttons supported (%d given)", MaxButtons, len(options.Buttons))
|
||||
}
|
||||
buttons[index] = c.String(buttonText)
|
||||
}
|
||||
|
||||
var iconData unsafe.Pointer
|
||||
var iconDataLength C.int
|
||||
if options.Icon != nil {
|
||||
iconData = unsafe.Pointer(&options.Icon[0])
|
||||
iconDataLength = C.int(len(options.Icon))
|
||||
}
|
||||
|
||||
C.MessageDialog(f.mainWindow.context, dialogType, title, message, buttons[0], buttons[1], buttons[2], buttons[3], defaultButton, cancelButton, iconData, iconDataLength)
|
||||
|
||||
result := <-messageDialogResponse
|
||||
|
||||
selectedC := buttons[result]
|
||||
var selected string
|
||||
if selectedC != nil {
|
||||
selected = options.Buttons[result]
|
||||
}
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
//export processMessageDialogResponse
|
||||
func processMessageDialogResponse(selection int) {
|
||||
messageDialogResponse <- selection
|
||||
}
|
||||
|
||||
//export processOpenFileDialogResponse
|
||||
func processOpenFileDialogResponse(cselection *C.char) {
|
||||
selection := C.GoString(cselection)
|
||||
openFileDialogResponse <- selection
|
||||
}
|
||||
|
||||
//export processSaveFileDialogResponse
|
||||
func processSaveFileDialogResponse(cselection *C.char) {
|
||||
selection := C.GoString(cselection)
|
||||
saveFileDialogResponse <- selection
|
||||
}
|
||||
525
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/frontend.go
generated
vendored
Normal file
525
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/frontend.go
generated
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "CustomProtocol.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/originvalidator"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
const startURL = "wails://wails/"
|
||||
|
||||
type bindingsMessage struct {
|
||||
message string
|
||||
source string
|
||||
isMainFrame bool
|
||||
}
|
||||
|
||||
var (
|
||||
messageBuffer = make(chan string, 100)
|
||||
bindingsMessageBuffer = make(chan *bindingsMessage, 100)
|
||||
requestBuffer = make(chan webview.Request, 100)
|
||||
callbackBuffer = make(chan uint, 10)
|
||||
openFilepathBuffer = make(chan string, 100)
|
||||
openUrlBuffer = make(chan string, 100)
|
||||
secondInstanceBuffer = make(chan options.SecondInstanceData, 1)
|
||||
)
|
||||
|
||||
type Frontend struct {
|
||||
// Context
|
||||
ctx context.Context
|
||||
|
||||
frontendOptions *options.App
|
||||
logger *logger.Logger
|
||||
debug bool
|
||||
devtoolsEnabled bool
|
||||
|
||||
// Keep single instance lock file, so that it will not be GC and lock will exist while app is running
|
||||
singleInstanceLockFile *os.File
|
||||
|
||||
// Assets
|
||||
assets *assetserver.AssetServer
|
||||
startURL *url.URL
|
||||
|
||||
// main window handle
|
||||
mainWindow *Window
|
||||
bindings *binding.Bindings
|
||||
dispatcher frontend.Dispatcher
|
||||
|
||||
originValidator *originvalidator.OriginValidator
|
||||
}
|
||||
|
||||
func (f *Frontend) RunMainLoop() {
|
||||
C.RunMainLoop()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowClose() {
|
||||
C.ReleaseContext(f.mainWindow.context)
|
||||
}
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
|
||||
result := &Frontend{
|
||||
frontendOptions: appoptions,
|
||||
logger: myLogger,
|
||||
bindings: appBindings,
|
||||
dispatcher: dispatcher,
|
||||
ctx: ctx,
|
||||
}
|
||||
result.startURL, _ = url.Parse(startURL)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
|
||||
// this should be initialized as early as possible to handle first instance launch
|
||||
C.StartCustomProtocolHandler()
|
||||
|
||||
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
|
||||
result.startURL = _starturl
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
} else {
|
||||
if port, _ := ctx.Value("assetserverport").(string); port != "" {
|
||||
result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
}
|
||||
|
||||
var bindings string
|
||||
var err error
|
||||
if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
|
||||
bindings, err = appBindings.ToJSON()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
appBindings.DB().UpdateObfuscatedCallMap()
|
||||
}
|
||||
|
||||
assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, runtime.RuntimeAssetsBundle)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
assets.ExpectedWebViewHost = result.startURL.Host
|
||||
result.assets = assets
|
||||
|
||||
go result.startRequestProcessor()
|
||||
}
|
||||
|
||||
go result.startMessageProcessor()
|
||||
go result.startBindingsMessageProcessor()
|
||||
go result.startCallbackProcessor()
|
||||
go result.startFileOpenProcessor()
|
||||
go result.startUrlOpenProcessor()
|
||||
go result.startSecondInstanceProcessor()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *Frontend) startFileOpenProcessor() {
|
||||
for filePath := range openFilepathBuffer {
|
||||
f.ProcessOpenFileEvent(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startUrlOpenProcessor() {
|
||||
for url := range openUrlBuffer {
|
||||
f.ProcessOpenUrlEvent(url)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startSecondInstanceProcessor() {
|
||||
for secondInstanceData := range secondInstanceBuffer {
|
||||
if f.frontendOptions.SingleInstanceLock != nil &&
|
||||
f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil {
|
||||
f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startMessageProcessor() {
|
||||
for message := range messageBuffer {
|
||||
f.processMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startBindingsMessageProcessor() {
|
||||
for msg := range bindingsMessageBuffer {
|
||||
// Apple webkit doesn't provide origin of main frame. So we can't verify in case of iFrame that top level origin is allowed.
|
||||
if !msg.isMainFrame {
|
||||
f.logger.Error("Blocked request from not main frame")
|
||||
continue
|
||||
}
|
||||
|
||||
origin, err := f.originValidator.GetOriginFromURL(msg.source)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("failed to get origin for URL %q: %v", msg.source, err))
|
||||
continue
|
||||
}
|
||||
|
||||
allowed := f.originValidator.IsOriginAllowed(origin)
|
||||
if !allowed {
|
||||
f.logger.Error("Blocked request from unauthorized origin: %s", origin)
|
||||
continue
|
||||
}
|
||||
|
||||
f.processMessage(msg.message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startRequestProcessor() {
|
||||
for request := range requestBuffer {
|
||||
f.assets.ServeWebViewRequest(request)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startCallbackProcessor() {
|
||||
for callback := range callbackBuffer {
|
||||
err := f.handleCallback(callback)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReload() {
|
||||
f.ExecJS("runtime.WindowReload();")
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReloadApp() {
|
||||
f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetSystemDefaultTheme() {
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetLightTheme() {
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetDarkTheme() {
|
||||
}
|
||||
|
||||
func (f *Frontend) Run(ctx context.Context) error {
|
||||
f.ctx = ctx
|
||||
|
||||
if f.frontendOptions.SingleInstanceLock != nil {
|
||||
f.singleInstanceLockFile = SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId)
|
||||
}
|
||||
|
||||
_debug := ctx.Value("debug")
|
||||
_devtoolsEnabled := ctx.Value("devtoolsEnabled")
|
||||
|
||||
if _debug != nil {
|
||||
f.debug = _debug.(bool)
|
||||
}
|
||||
if _devtoolsEnabled != nil {
|
||||
f.devtoolsEnabled = _devtoolsEnabled.(bool)
|
||||
}
|
||||
|
||||
mainWindow := NewWindow(f.frontendOptions, f.debug, f.devtoolsEnabled)
|
||||
f.mainWindow = mainWindow
|
||||
f.mainWindow.Center()
|
||||
|
||||
go func() {
|
||||
if f.frontendOptions.OnStartup != nil {
|
||||
f.frontendOptions.OnStartup(f.ctx)
|
||||
}
|
||||
}()
|
||||
mainWindow.Run(f.startURL.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowCenter() {
|
||||
f.mainWindow.Center()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetAlwaysOnTop(onTop bool) {
|
||||
f.mainWindow.SetAlwaysOnTop(onTop)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetPosition(x, y int) {
|
||||
f.mainWindow.SetPosition(x, y)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowGetPosition() (int, int) {
|
||||
return f.mainWindow.GetPosition()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetSize(width, height int) {
|
||||
f.mainWindow.SetSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowGetSize() (int, int) {
|
||||
return f.mainWindow.Size()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetTitle(title string) {
|
||||
f.mainWindow.SetTitle(title)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowFullscreen() {
|
||||
f.mainWindow.Fullscreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowUnfullscreen() {
|
||||
f.mainWindow.UnFullscreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowShow() {
|
||||
f.mainWindow.Show()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowHide() {
|
||||
f.mainWindow.Hide()
|
||||
}
|
||||
|
||||
func (f *Frontend) Show() {
|
||||
f.mainWindow.ShowApplication()
|
||||
}
|
||||
|
||||
func (f *Frontend) Hide() {
|
||||
f.mainWindow.HideApplication()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowMaximise() {
|
||||
f.mainWindow.Maximise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowToggleMaximise() {
|
||||
f.mainWindow.ToggleMaximise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowUnmaximise() {
|
||||
f.mainWindow.UnMaximise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowMinimise() {
|
||||
f.mainWindow.Minimise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowUnminimise() {
|
||||
f.mainWindow.UnMinimise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetMinSize(width int, height int) {
|
||||
f.mainWindow.SetMinSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetMaxSize(width int, height int) {
|
||||
f.mainWindow.SetMaxSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
|
||||
if col == nil {
|
||||
return
|
||||
}
|
||||
f.mainWindow.SetBackgroundColour(col.R, col.G, col.B, col.A)
|
||||
}
|
||||
|
||||
func (f *Frontend) ScreenGetAll() ([]frontend.Screen, error) {
|
||||
return GetAllScreens(f.mainWindow.context)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsMaximised() bool {
|
||||
return f.mainWindow.IsMaximised()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsMinimised() bool {
|
||||
return f.mainWindow.IsMinimised()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsNormal() bool {
|
||||
return f.mainWindow.IsNormal()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsFullscreen() bool {
|
||||
return f.mainWindow.IsFullScreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) Quit() {
|
||||
if f.frontendOptions.OnBeforeClose != nil {
|
||||
go func() {
|
||||
if !f.frontendOptions.OnBeforeClose(f.ctx) {
|
||||
f.mainWindow.Quit()
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
f.mainWindow.Quit()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowPrint() {
|
||||
f.mainWindow.Print()
|
||||
}
|
||||
|
||||
type EventNotify struct {
|
||||
Name string `json:"name"`
|
||||
Data []interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func (f *Frontend) Notify(name string, data ...interface{}) {
|
||||
notification := EventNotify{
|
||||
Name: name,
|
||||
Data: data,
|
||||
}
|
||||
payload, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
|
||||
}
|
||||
|
||||
func (f *Frontend) processMessage(message string) {
|
||||
if message == "DomReady" {
|
||||
if f.frontendOptions.OnDomReady != nil {
|
||||
f.frontendOptions.OnDomReady(f.ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if message == "runtime:ready" {
|
||||
cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue)
|
||||
f.ExecJS(cmd)
|
||||
|
||||
if f.frontendOptions.DragAndDrop != nil && f.frontendOptions.DragAndDrop.EnableFileDrop {
|
||||
f.ExecJS("window.wails.flags.enableWailsDragAndDrop = true;")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if message == "wails:openInspector" {
|
||||
showInspector(f.mainWindow.context)
|
||||
return
|
||||
}
|
||||
|
||||
//if strings.HasPrefix(message, "systemevent:") {
|
||||
// f.processSystemEvent(message)
|
||||
// return
|
||||
//}
|
||||
|
||||
go func() {
|
||||
result, err := f.dispatcher.ProcessMessage(message, f)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
f.Callback(result)
|
||||
return
|
||||
}
|
||||
if result == "" {
|
||||
return
|
||||
}
|
||||
|
||||
switch result[0] {
|
||||
case 'c':
|
||||
// Callback from a method call
|
||||
f.Callback(result[1:])
|
||||
default:
|
||||
f.logger.Info("Unknown message returned from dispatcher: %+v", result)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (f *Frontend) ProcessOpenFileEvent(filePath string) {
|
||||
if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnFileOpen != nil {
|
||||
f.frontendOptions.Mac.OnFileOpen(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) ProcessOpenUrlEvent(url string) {
|
||||
if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnUrlOpen != nil {
|
||||
f.frontendOptions.Mac.OnUrlOpen(url)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) Callback(message string) {
|
||||
escaped, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`)
|
||||
}
|
||||
|
||||
func (f *Frontend) ExecJS(js string) {
|
||||
f.mainWindow.ExecJS(js)
|
||||
}
|
||||
|
||||
//func (f *Frontend) processSystemEvent(message string) {
|
||||
// sl := strings.Split(message, ":")
|
||||
// if len(sl) != 2 {
|
||||
// f.logger.Error("Invalid system message: %s", message)
|
||||
// return
|
||||
// }
|
||||
// switch sl[1] {
|
||||
// case "fullscreen":
|
||||
// f.mainWindow.DisableSizeConstraints()
|
||||
// case "unfullscreen":
|
||||
// f.mainWindow.EnableSizeConstraints()
|
||||
// default:
|
||||
// f.logger.Error("Unknown system message: %s", message)
|
||||
// }
|
||||
//}
|
||||
|
||||
//export processMessage
|
||||
func processMessage(message *C.char) {
|
||||
goMessage := C.GoString(message)
|
||||
messageBuffer <- goMessage
|
||||
}
|
||||
|
||||
//export processBindingMessage
|
||||
func processBindingMessage(message *C.char, source *C.char, fromMainFrame bool) {
|
||||
goMessage := C.GoString(message)
|
||||
goSource := C.GoString(source)
|
||||
bindingsMessageBuffer <- &bindingsMessage{
|
||||
message: goMessage,
|
||||
source: goSource,
|
||||
isMainFrame: fromMainFrame,
|
||||
}
|
||||
}
|
||||
|
||||
//export processCallback
|
||||
func processCallback(callbackID uint) {
|
||||
callbackBuffer <- callbackID
|
||||
}
|
||||
|
||||
//export processURLRequest
|
||||
func processURLRequest(_ unsafe.Pointer, wkURLSchemeTask unsafe.Pointer) {
|
||||
requestBuffer <- webview.NewRequest(wkURLSchemeTask)
|
||||
}
|
||||
|
||||
//export HandleOpenFile
|
||||
func HandleOpenFile(filePath *C.char) {
|
||||
goFilepath := C.GoString(filePath)
|
||||
openFilepathBuffer <- goFilepath
|
||||
}
|
||||
|
||||
//export HandleOpenURL
|
||||
func HandleOpenURL(url *C.char) {
|
||||
goUrl := C.GoString(url)
|
||||
openUrlBuffer <- goUrl
|
||||
}
|
||||
10
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/inspector.go
generated
vendored
Normal file
10
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/inspector.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:build darwin && !(dev || debug || devtools)
|
||||
|
||||
package darwin
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func showInspector(_ unsafe.Pointer) {
|
||||
}
|
||||
78
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/inspector_dev.go
generated
vendored
Normal file
78
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/inspector_dev.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
//go:build darwin && (dev || debug || devtools)
|
||||
|
||||
package darwin
|
||||
|
||||
// We are using private APIs here, make sure this is only included in a dev/debug build and not in a production build.
|
||||
// Otherwise the binary might get rejected by the AppReview-Team when pushing it to the AppStore.
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "WailsContext.h"
|
||||
|
||||
extern void processMessage(const char *message);
|
||||
|
||||
@interface _WKInspector : NSObject
|
||||
- (void)show;
|
||||
- (void)detach;
|
||||
@end
|
||||
|
||||
@interface WKWebView ()
|
||||
- (_WKInspector *)_inspector;
|
||||
@end
|
||||
|
||||
void showInspector(void *inctx) {
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120000
|
||||
ON_MAIN_THREAD(
|
||||
if (@available(macOS 12.0, *)) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
|
||||
@try {
|
||||
[ctx.webview._inspector show];
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"Opening the inspector failed: %@", exception.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
|
||||
// Detach must be deferred a little bit and is ignored directly after a show.
|
||||
@try {
|
||||
[ctx.webview._inspector detach];
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"Detaching the inspector failed: %@", exception.reason);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NSLog(@"Opening the inspector needs at least MacOS 12");
|
||||
}
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void setupF12hotkey() {
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
|
||||
if (event.keyCode == 111 &&
|
||||
event.modifierFlags & NSEventModifierFlagFunction &&
|
||||
event.modifierFlags & NSEventModifierFlagCommand &&
|
||||
event.modifierFlags & NSEventModifierFlagShift) {
|
||||
processMessage("wails:openInspector");
|
||||
return nil;
|
||||
}
|
||||
return event;
|
||||
}];
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
C.setupF12hotkey()
|
||||
}
|
||||
|
||||
func showInspector(context unsafe.Pointer) {
|
||||
C.showInspector(context)
|
||||
}
|
||||
243
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/main.m
generated
vendored
Normal file
243
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/main.m
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
//go:build ignore
|
||||
// main.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
// ****** This file is used for testing purposes only ******
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
|
||||
void processMessage(const char*t) {
|
||||
NSLog(@"processMessage called");
|
||||
}
|
||||
|
||||
void processMessageDialogResponse(int t) {
|
||||
NSLog(@"processMessage called");
|
||||
}
|
||||
|
||||
void processOpenFileDialogResponse(const char *t) {
|
||||
NSLog(@"processMessage called %s", t);
|
||||
}
|
||||
void processSaveFileDialogResponse(const char *t) {
|
||||
NSLog(@"processMessage called %s", t);
|
||||
}
|
||||
|
||||
void processCallback(int callbackID) {
|
||||
NSLog(@"Process callback %d", callbackID);
|
||||
}
|
||||
|
||||
void processURLRequest(void *ctx, unsigned long long requestId, const char* url, const char *method, const char *headers, const void *body, int bodyLen) {
|
||||
NSLog(@"processURLRequest called");
|
||||
const char myByteArray[] = { 0x3c,0x68,0x31,0x3e,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21,0x3c,0x2f,0x68,0x31,0x3e };
|
||||
// void *inctx, const char *url, int statusCode, const char *headers, void* data, int datalength
|
||||
ProcessURLResponse(ctx, requestId, 200, "{\"Content-Type\": \"text/html\"}", (void*)myByteArray, 21);
|
||||
}
|
||||
|
||||
unsigned char _Users_username_Pictures_SaltBae_png[] = {
|
||||
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x8d, 0x89, 0x1d, 0x0d, 0x00, 0x00, 0x00,
|
||||
0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61,
|
||||
0x05, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, 0x52, 0x4d, 0x00, 0x00, 0x7a,
|
||||
0x26, 0x00, 0x00, 0x80, 0x84, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x80,
|
||||
0xe8, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0xea, 0x60, 0x00, 0x00, 0x3a,
|
||||
0x98, 0x00, 0x00, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x00, 0x00, 0x00,
|
||||
0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b,
|
||||
0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x01, 0xd5, 0x69, 0x54,
|
||||
0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64,
|
||||
0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78,
|
||||
0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62,
|
||||
0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20,
|
||||
0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, 0x4d, 0x50,
|
||||
0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22,
|
||||
0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44,
|
||||
0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d,
|
||||
0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
|
||||
0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f,
|
||||
0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79,
|
||||
0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x0a, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64,
|
||||
0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x0a, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78,
|
||||
0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68,
|
||||
0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f,
|
||||
0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f,
|
||||
0x31, 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x43, 0x6f,
|
||||
0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c,
|
||||
0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65,
|
||||
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c,
|
||||
0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x50, 0x68,
|
||||
0x6f, 0x74, 0x6f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x49, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e,
|
||||
0x32, 0x3c, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x50, 0x68, 0x6f, 0x74,
|
||||
0x6f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44,
|
||||
0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a,
|
||||
0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46,
|
||||
0x3e, 0x0a, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74,
|
||||
0x61, 0x3e, 0x0a, 0x02, 0xd8, 0x80, 0x05, 0x00, 0x00, 0x04, 0xdc, 0x49,
|
||||
0x44, 0x41, 0x54, 0x38, 0x11, 0x1d, 0x94, 0x49, 0x6c, 0x1b, 0x65, 0x18,
|
||||
0x86, 0x9f, 0x99, 0xf9, 0x67, 0xc6, 0x6b, 0xbc, 0x26, 0xce, 0xda, 0xa4,
|
||||
0x25, 0x69, 0x0b, 0x2d, 0x28, 0x34, 0x2c, 0x95, 0x00, 0x89, 0x45, 0x08,
|
||||
0x5a, 0x95, 0x03, 0x08, 0x09, 0x21, 0xe0, 0x80, 0x38, 0xc3, 0x85, 0x03,
|
||||
0xe2, 0x00, 0x47, 0xc4, 0x1d, 0x38, 0x70, 0xe3, 0xc6, 0x01, 0x01, 0x42,
|
||||
0x20, 0x54, 0x7a, 0x2a, 0x6b, 0x0b, 0x94, 0xd2, 0xd2, 0x25, 0x69, 0x9b,
|
||||
0xa4, 0x0d, 0x2d, 0xa9, 0xb3, 0x78, 0x89, 0x9d, 0xf1, 0x2c, 0x9e, 0x85,
|
||||
0x2f, 0xb5, 0x35, 0xb6, 0x35, 0x96, 0xde, 0x79, 0xdf, 0xef, 0x7f, 0x9f,
|
||||
0x4f, 0xfb, 0xe0, 0xad, 0x37, 0x12, 0xfd, 0xf0, 0xb3, 0x9c, 0xfb, 0xb7,
|
||||
0xc5, 0x8d, 0x46, 0x9b, 0x71, 0x5b, 0xf1, 0xd0, 0xf4, 0x18, 0xdb, 0xeb,
|
||||
0x4b, 0x1c, 0xff, 0xf1, 0x57, 0x98, 0xdc, 0x87, 0x72, 0x3a, 0x8c, 0x3a,
|
||||
0xcb, 0x8c, 0xea, 0x31, 0x35, 0xb7, 0xc3, 0x99, 0xba, 0xc3, 0xd7, 0xab,
|
||||
0x3e, 0x87, 0x2a, 0x8a, 0xb3, 0xff, 0xdc, 0xe0, 0x9b, 0x8f, 0x5f, 0xa2,
|
||||
0x1c, 0xc5, 0xfc, 0x72, 0xc9, 0x41, 0x99, 0x71, 0x48, 0xca, 0x84, 0x3c,
|
||||
0x3e, 0xda, 0xd2, 0x05, 0x9a, 0xb1, 0xc7, 0x35, 0x67, 0x1c, 0xdd, 0x4c,
|
||||
0x68, 0xeb, 0x26, 0xd9, 0x30, 0x26, 0x09, 0x23, 0x5c, 0x3f, 0xc2, 0xd3,
|
||||
0x43, 0xc2, 0x24, 0x21, 0x4e, 0x34, 0x40, 0x27, 0x89, 0x13, 0xf9, 0x1e,
|
||||
0x22, 0x6e, 0xd5, 0x45, 0x43, 0x63, 0xc6, 0xd2, 0x50, 0xa9, 0xc4, 0x67,
|
||||
0x24, 0x15, 0x72, 0xa9, 0x7e, 0x95, 0xfa, 0x4f, 0x27, 0x78, 0x64, 0x76,
|
||||
0x86, 0x23, 0x61, 0xc0, 0xf0, 0x58, 0x15, 0xc3, 0x29, 0x71, 0x06, 0x45,
|
||||
0x2e, 0xa5, 0x48, 0xbb, 0x0a, 0x3d, 0x89, 0xa0, 0x8f, 0x08, 0x8a, 0x8e,
|
||||
0x08, 0xbb, 0xc1, 0x8e, 0xb0, 0x8d, 0xdd, 0x0f, 0xc9, 0x84, 0x06, 0x65,
|
||||
0x34, 0xf4, 0xed, 0x8d, 0xff, 0x58, 0xbd, 0xfc, 0x27, 0x17, 0x2f, 0x9e,
|
||||
0xe3, 0xf0, 0x81, 0x49, 0x5e, 0xde, 0x5f, 0xe1, 0x9e, 0x82, 0xcd, 0xdc,
|
||||
0x78, 0x8d, 0xd9, 0xb2, 0xc9, 0x56, 0x12, 0x32, 0x94, 0x4f, 0x91, 0xcb,
|
||||
0x88, 0x68, 0xda, 0x42, 0x13, 0x77, 0x11, 0xa2, 0xa8, 0xc3, 0x5a, 0x5f,
|
||||
0x46, 0x30, 0x65, 0x52, 0x29, 0xe4, 0x24, 0x4d, 0x8e, 0xcc, 0x68, 0x19,
|
||||
0xe5, 0x76, 0xbb, 0xac, 0x5c, 0x98, 0xa7, 0xb3, 0xed, 0xd0, 0x37, 0x62,
|
||||
0xa2, 0xb0, 0xc7, 0x89, 0xe5, 0x2e, 0x03, 0x0d, 0x97, 0x95, 0x46, 0x8f,
|
||||
0x31, 0xd7, 0xa6, 0x63, 0x81, 0x65, 0x25, 0x84, 0xba, 0x45, 0x5f, 0x65,
|
||||
0x31, 0x2c, 0x71, 0x6b, 0x77, 0x69, 0xf5, 0x7a, 0xbc, 0xb0, 0x3b, 0xcd,
|
||||
0xf9, 0xa5, 0x90, 0xd1, 0xb0, 0xcd, 0xd4, 0xb0, 0xdc, 0xd7, 0xc4, 0xfa,
|
||||
0xf0, 0x78, 0x95, 0x7b, 0x27, 0xab, 0x5c, 0x5e, 0x6e, 0xd2, 0xee, 0x05,
|
||||
0xdc, 0xd8, 0xea, 0xf1, 0xf7, 0xe2, 0x1a, 0xc7, 0xee, 0x1a, 0x62, 0x2e,
|
||||
0x1f, 0xe3, 0xe8, 0xb6, 0xc4, 0x4c, 0xd3, 0x6d, 0x6e, 0xd0, 0x6b, 0xfc,
|
||||
0x4c, 0xe3, 0xd4, 0x1f, 0xc4, 0x4b, 0xf3, 0x1c, 0x2c, 0x65, 0x29, 0x67,
|
||||
0x4d, 0xbe, 0xfb, 0xad, 0x45, 0x65, 0x0c, 0xea, 0x7e, 0x1f, 0x15, 0x6b,
|
||||
0x09, 0x0b, 0x8b, 0xb7, 0x19, 0xc9, 0xa5, 0x78, 0x75, 0x6e, 0x18, 0xdf,
|
||||
0xf5, 0x79, 0x72, 0xd0, 0xa2, 0x2d, 0xb3, 0x3a, 0xbb, 0xb4, 0x41, 0x3e,
|
||||
0x53, 0xe6, 0xf4, 0xca, 0x3c, 0xa5, 0x7c, 0x86, 0xe9, 0xfd, 0x47, 0x18,
|
||||
0x2e, 0xbd, 0xce, 0xd1, 0x97, 0x26, 0x78, 0xbc, 0x7e, 0x1d, 0xff, 0xcc,
|
||||
0xa7, 0x5c, 0x71, 0x74, 0x16, 0xe3, 0x18, 0xd7, 0x1e, 0x23, 0xe8, 0xac,
|
||||
0xa3, 0x0c, 0xcd, 0x60, 0x22, 0x6f, 0x43, 0x36, 0x43, 0x3b, 0x19, 0xc6,
|
||||
0x08, 0x7a, 0xe0, 0x6c, 0xe3, 0x27, 0x8a, 0xdb, 0x4e, 0xc0, 0xd4, 0xa0,
|
||||
0xcd, 0x27, 0xaf, 0xbd, 0xcb, 0x86, 0x36, 0xc6, 0xcc, 0xfe, 0x59, 0xd2,
|
||||
0xca, 0x90, 0x93, 0x36, 0x70, 0xaf, 0x9c, 0xe4, 0xcb, 0x6f, 0x65, 0x54,
|
||||
0xd9, 0x47, 0x59, 0x70, 0xbb, 0x74, 0x1b, 0x0e, 0x89, 0xe7, 0xa3, 0xc7,
|
||||
0x12, 0x39, 0x63, 0xea, 0x68, 0x12, 0x6b, 0x53, 0x5c, 0x9e, 0xef, 0x76,
|
||||
0xf0, 0x55, 0x86, 0x0d, 0x17, 0x56, 0x9a, 0x4d, 0x94, 0x95, 0x65, 0xe6,
|
||||
0xbe, 0x67, 0x98, 0xbe, 0xfb, 0x21, 0x52, 0xd2, 0x43, 0xaf, 0x5d, 0x47,
|
||||
0x6b, 0x5c, 0xa3, 0x59, 0xbf, 0xc2, 0x62, 0xdd, 0x26, 0xa5, 0x12, 0x6a,
|
||||
0x41, 0x44, 0xdf, 0xbd, 0xcd, 0x92, 0x17, 0xa0, 0xb6, 0x03, 0x43, 0xba,
|
||||
0x66, 0x91, 0xe9, 0xdc, 0xc2, 0xce, 0xed, 0xa1, 0xfc, 0xc0, 0x2b, 0x14,
|
||||
0xff, 0xfd, 0x1e, 0x4b, 0xb3, 0xa9, 0x29, 0x87, 0x81, 0xd2, 0x04, 0x8e,
|
||||
0x66, 0x89, 0x58, 0x00, 0x7e, 0x07, 0xaf, 0xdb, 0xa4, 0xbb, 0xb5, 0x49,
|
||||
0xb9, 0xaa, 0x18, 0xb9, 0x77, 0x8e, 0xcd, 0xdb, 0x6d, 0x1e, 0x1c, 0xb5,
|
||||
0x38, 0x7d, 0xa5, 0xcf, 0xaa, 0x08, 0xeb, 0x77, 0x3f, 0x35, 0xc7, 0xda,
|
||||
0xfc, 0x02, 0xaa, 0xf6, 0x1c, 0xbb, 0x9f, 0x78, 0x9f, 0x89, 0x43, 0x47,
|
||||
0xa4, 0x6f, 0x3d, 0x06, 0xed, 0x90, 0x92, 0x79, 0x95, 0xd4, 0xe4, 0xfd,
|
||||
0x98, 0x66, 0x4a, 0x6a, 0xd7, 0xc7, 0x0b, 0x62, 0xa4, 0xe3, 0x8c, 0x4d,
|
||||
0xc4, 0xe8, 0x85, 0x98, 0xe5, 0x46, 0x44, 0x26, 0x97, 0x21, 0xe9, 0xf7,
|
||||
0xf9, 0x61, 0xc5, 0xe3, 0xd4, 0x66, 0x84, 0xd2, 0x70, 0xc9, 0xee, 0x79,
|
||||
0x98, 0x43, 0xc7, 0x5e, 0x27, 0xb6, 0x8a, 0xd2, 0x5a, 0x1f, 0xf3, 0xa9,
|
||||
0xf7, 0x88, 0xce, 0x7d, 0x85, 0x71, 0xe0, 0x79, 0x98, 0x7a, 0x90, 0x9e,
|
||||
0x1b, 0xd0, 0x13, 0x52, 0x4a, 0x66, 0x97, 0x7d, 0x33, 0x1e, 0xed, 0xae,
|
||||
0xc7, 0x87, 0x1f, 0x7d, 0xce, 0xc2, 0xd5, 0x3a, 0xe6, 0xde, 0x02, 0xcb,
|
||||
0xdb, 0x3e, 0xbe, 0xa6, 0x91, 0x95, 0x62, 0x6b, 0x2f, 0xce, 0x90, 0x3c,
|
||||
0xfd, 0xce, 0x71, 0x0e, 0xcc, 0x3e, 0x82, 0x13, 0xf4, 0x09, 0xd5, 0x00,
|
||||
0x16, 0x82, 0x98, 0xb3, 0x49, 0x24, 0xb1, 0x83, 0xc8, 0xc0, 0xd6, 0x3a,
|
||||
0x54, 0x33, 0xab, 0x14, 0x8c, 0x16, 0x4e, 0x38, 0xcc, 0xe5, 0xeb, 0x4d,
|
||||
0x5e, 0x7b, 0xfb, 0x4d, 0xaa, 0x79, 0xa1, 0x45, 0x1c, 0x9b, 0xd2, 0x94,
|
||||
0xcc, 0x0e, 0x8c, 0x52, 0x7a, 0x65, 0x17, 0xc7, 0xa9, 0x0c, 0x8e, 0xe2,
|
||||
0xf7, 0xba, 0xa8, 0xc8, 0x13, 0x87, 0x32, 0x87, 0x0b, 0x27, 0x30, 0x36,
|
||||
0x57, 0xe8, 0xea, 0x15, 0xce, 0x06, 0x65, 0x5e, 0x3d, 0x5a, 0x94, 0x53,
|
||||
0xb7, 0x59, 0x58, 0xdf, 0x25, 0xc4, 0xe4, 0xc9, 0x65, 0x3d, 0xb4, 0xb4,
|
||||
0x4e, 0x37, 0x0c, 0x29, 0x98, 0x4a, 0xe8, 0x11, 0xde, 0x85, 0x42, 0x43,
|
||||
0x1c, 0xaa, 0x38, 0x55, 0xc4, 0xb4, 0x2c, 0x22, 0x3d, 0xcd, 0xfa, 0xea,
|
||||
0x0d, 0xf4, 0x8d, 0x1f, 0xc9, 0x5f, 0xfa, 0x82, 0x6d, 0xc7, 0xe1, 0xa6,
|
||||
0x57, 0xe3, 0x56, 0x6e, 0x96, 0xbf, 0x16, 0x1f, 0xa3, 0x54, 0xaa, 0x91,
|
||||
0x16, 0x5a, 0xb2, 0xa9, 0x04, 0xaf, 0x67, 0xc9, 0xac, 0x6c, 0xfa, 0x32,
|
||||
0x9e, 0x48, 0xea, 0xa5, 0x0b, 0x89, 0x3b, 0x54, 0x47, 0xf2, 0xa1, 0xf2,
|
||||
0x2a, 0x4d, 0xeb, 0xf4, 0x17, 0xdc, 0xd4, 0x72, 0x6c, 0xb5, 0x36, 0x28,
|
||||
0xb6, 0x7e, 0x17, 0x04, 0xd3, 0xac, 0x7a, 0x42, 0xc1, 0xf4, 0x6e, 0x9e,
|
||||
0xbf, 0x6b, 0xb7, 0x3c, 0x3a, 0x21, 0x67, 0xcb, 0x41, 0x48, 0x07, 0x91,
|
||||
0xde, 0x1a, 0xe2, 0xaa, 0x9c, 0xb1, 0x59, 0xdb, 0x12, 0x25, 0xc1, 0x32,
|
||||
0x92, 0xea, 0xc9, 0xaf, 0x3b, 0x97, 0xca, 0xca, 0xfe, 0x5b, 0xfe, 0xe5,
|
||||
0x33, 0x29, 0xeb, 0x16, 0x95, 0xd2, 0x24, 0xeb, 0xda, 0x30, 0xeb, 0x95,
|
||||
0x1a, 0xd3, 0xf7, 0x0f, 0x51, 0x1c, 0xd9, 0x0b, 0x99, 0x12, 0x7a, 0x4a,
|
||||
0xd0, 0xd3, 0x25, 0x9a, 0x88, 0x45, 0xb1, 0x04, 0x33, 0x2c, 0x8a, 0x99,
|
||||
0x34, 0x6b, 0x75, 0x19, 0x91, 0x9d, 0x92, 0x29, 0x89, 0xa0, 0x2c, 0x8b,
|
||||
0x9d, 0xd8, 0x7a, 0x5e, 0x04, 0x07, 0x87, 0x66, 0x28, 0x56, 0x67, 0xb9,
|
||||
0xd6, 0xd2, 0x39, 0xd9, 0xec, 0x33, 0x30, 0xb2, 0x8b, 0xea, 0xae, 0x83,
|
||||
0x18, 0xb9, 0x31, 0x34, 0xbb, 0x42, 0x22, 0x0b, 0x21, 0x96, 0x3c, 0x61,
|
||||
0xac, 0xcb, 0x95, 0x60, 0x2a, 0xe9, 0x68, 0x79, 0x08, 0x36, 0x56, 0x65,
|
||||
0x27, 0x4a, 0xd9, 0x83, 0x00, 0xcf, 0x0b, 0xf1, 0xfc, 0x10, 0x15, 0x0a,
|
||||
0x6a, 0x75, 0x77, 0x8b, 0x86, 0xdc, 0x58, 0x57, 0x45, 0x52, 0xe9, 0x84,
|
||||
0x81, 0x7c, 0x91, 0x28, 0x55, 0x23, 0x96, 0x13, 0xd7, 0x24, 0xbe, 0xac,
|
||||
0x17, 0xfa, 0xf2, 0x78, 0x63, 0xc7, 0x82, 0x08, 0xda, 0xa6, 0xc5, 0x50,
|
||||
0x55, 0x04, 0xe5, 0x65, 0x5b, 0x06, 0xde, 0xce, 0xf0, 0x24, 0xf3, 0x4e,
|
||||
0x70, 0xb5, 0x15, 0x6a, 0x34, 0x7b, 0x11, 0x9d, 0xbe, 0x10, 0x53, 0xd0,
|
||||
0xa8, 0x86, 0x2e, 0x76, 0xb6, 0x2a, 0x9d, 0x2c, 0x48, 0x3c, 0x5b, 0xa2,
|
||||
0xc8, 0x3a, 0x37, 0xd4, 0x9d, 0xed, 0x6c, 0x4a, 0xab, 0x95, 0x6e, 0x08,
|
||||
0x66, 0x3d, 0x5a, 0xad, 0x4d, 0x18, 0xc8, 0xca, 0xfa, 0xd5, 0x85, 0x6f,
|
||||
0xf9, 0x5f, 0xde, 0x02, 0x30, 0xff, 0x03, 0x8c, 0x47, 0x35, 0xad, 0xbc,
|
||||
0xbf, 0x26, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
|
||||
0x42, 0x60, 0x82
|
||||
|
||||
};
|
||||
|
||||
unsigned int _Users_username_Pictures_SaltBae_png_len = 1863;
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
// insert code here...
|
||||
int frameless = 0;
|
||||
int resizable = 1;
|
||||
int zoomable = 0;
|
||||
int fullscreen = 1;
|
||||
int fullSizeContent = 1;
|
||||
int hideTitleBar = 0;
|
||||
int titlebarAppearsTransparent = 0;
|
||||
int hideTitle = 0;
|
||||
int useToolbar = 0;
|
||||
int hideToolbarSeparator = 0;
|
||||
int webviewIsTransparent = 1;
|
||||
int alwaysOnTop = 0;
|
||||
int hideWindowOnClose = 0;
|
||||
const char* appearance = "NSAppearanceNameDarkAqua";
|
||||
int windowIsTranslucent = 1;
|
||||
int devtoolsEnabled = 1;
|
||||
int defaultContextMenuEnabled = 1;
|
||||
int windowStartState = 0;
|
||||
int startsHidden = 0;
|
||||
WailsContext *result = Create("OI OI!",400,400, frameless, resizable, zoomable, fullscreen, fullSizeContent, hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent, alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, devtoolsEnabled, defaultContextMenuEnabled, windowStartState,
|
||||
startsHidden, 400, 400, 600, 600, false);
|
||||
SetBackgroundColour(result, 255, 0, 0, 255);
|
||||
void *m = NewMenu("");
|
||||
SetAbout(result, "Fake title", "I am a description", _Users_username_Pictures_SaltBae_png, _Users_username_Pictures_SaltBae_png_len);
|
||||
// AddMenuByRole(result, 1);
|
||||
|
||||
AppendRole(result, m, 1);
|
||||
AppendRole(result, m, 2);
|
||||
void* submenu = NewMenu("test");
|
||||
void* menuITem = AppendMenuItem(result, submenu, "Woohoo", "p", 0, 0, 0, 470);
|
||||
AppendSubmenu(m, submenu);
|
||||
UpdateMenuItem(menuITem, 1);
|
||||
SetAsApplicationMenu(result, m);
|
||||
// SetPosition(result, 100, 100);
|
||||
|
||||
|
||||
|
||||
Run((void*)CFBridgingRetain(result));
|
||||
return 0;
|
||||
}
|
||||
134
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/menu.go
generated
vendored
Normal file
134
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/menu.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
|
||||
type NSMenu struct {
|
||||
context unsafe.Pointer
|
||||
nsmenu unsafe.Pointer
|
||||
}
|
||||
|
||||
func NewNSMenu(context unsafe.Pointer, name string) *NSMenu {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
title := c.String(name)
|
||||
nsmenu := C.NewMenu(title)
|
||||
return &NSMenu{
|
||||
context: context,
|
||||
nsmenu: nsmenu,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *NSMenu) AddSubMenu(label string) *NSMenu {
|
||||
result := NewNSMenu(m.context, label)
|
||||
C.AppendSubmenu(m.nsmenu, result.nsmenu)
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *NSMenu) AppendRole(role menu.Role) {
|
||||
C.AppendRole(m.context, m.nsmenu, C.int(role))
|
||||
}
|
||||
|
||||
type MenuItem struct {
|
||||
id uint
|
||||
nsmenuitem unsafe.Pointer
|
||||
wailsMenuItem *menu.MenuItem
|
||||
radioGroupMembers []*MenuItem
|
||||
}
|
||||
|
||||
func (m *NSMenu) AddMenuItem(menuItem *menu.MenuItem) *MenuItem {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
var modifier C.int
|
||||
var key *C.char
|
||||
if menuItem.Accelerator != nil {
|
||||
modifier = C.int(keys.ToMacModifier(menuItem.Accelerator))
|
||||
key = c.String(menuItem.Accelerator.Key)
|
||||
}
|
||||
|
||||
result := &MenuItem{
|
||||
wailsMenuItem: menuItem,
|
||||
}
|
||||
|
||||
result.id = createMenuItemID(result)
|
||||
result.nsmenuitem = C.AppendMenuItem(m.context, m.nsmenu, c.String(menuItem.Label), key, modifier, bool2Cint(menuItem.Disabled), bool2Cint(menuItem.Checked), C.int(result.id))
|
||||
return result
|
||||
}
|
||||
|
||||
//func (w *Window) SetApplicationMenu(menu *menu.Menu) {
|
||||
//w.applicationMenu = menu
|
||||
//processMenu(w, menu)
|
||||
//}
|
||||
|
||||
func processMenu(parent *NSMenu, wailsMenu *menu.Menu) {
|
||||
var radioGroups []*MenuItem
|
||||
|
||||
for _, menuItem := range wailsMenu.Items {
|
||||
if menuItem.SubMenu != nil {
|
||||
if len(radioGroups) > 0 {
|
||||
processRadioGroups(radioGroups)
|
||||
radioGroups = []*MenuItem{}
|
||||
}
|
||||
submenu := parent.AddSubMenu(menuItem.Label)
|
||||
processMenu(submenu, menuItem.SubMenu)
|
||||
} else {
|
||||
lastMenuItem := processMenuItem(parent, menuItem)
|
||||
if menuItem.Type == menu.RadioType {
|
||||
radioGroups = append(radioGroups, lastMenuItem)
|
||||
} else {
|
||||
if len(radioGroups) > 0 {
|
||||
processRadioGroups(radioGroups)
|
||||
radioGroups = []*MenuItem{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processRadioGroups(groups []*MenuItem) {
|
||||
for _, item := range groups {
|
||||
item.radioGroupMembers = groups
|
||||
}
|
||||
}
|
||||
|
||||
func processMenuItem(parent *NSMenu, menuItem *menu.MenuItem) *MenuItem {
|
||||
if menuItem.Hidden {
|
||||
return nil
|
||||
}
|
||||
if menuItem.Role != 0 {
|
||||
parent.AppendRole(menuItem.Role)
|
||||
return nil
|
||||
}
|
||||
if menuItem.Type == menu.SeparatorType {
|
||||
C.AppendSeparator(parent.nsmenu)
|
||||
return nil
|
||||
}
|
||||
|
||||
return parent.AddMenuItem(menuItem)
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
|
||||
f.mainWindow.SetApplicationMenu(menu)
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuUpdateApplicationMenu() {
|
||||
f.mainWindow.UpdateApplicationMenu()
|
||||
}
|
||||
54
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/menuitem.go
generated
vendored
Normal file
54
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/menuitem.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
menuItemToID = make(map[*MenuItem]uint)
|
||||
idToMenuItem = make(map[uint]*MenuItem)
|
||||
menuItemLock sync.Mutex
|
||||
menuItemIDCounter uint = 0
|
||||
)
|
||||
|
||||
func createMenuItemID(item *MenuItem) uint {
|
||||
menuItemLock.Lock()
|
||||
defer menuItemLock.Unlock()
|
||||
counter := 0
|
||||
for {
|
||||
menuItemIDCounter++
|
||||
value := idToMenuItem[menuItemIDCounter]
|
||||
if value == nil {
|
||||
break
|
||||
}
|
||||
counter++
|
||||
if counter == math.MaxInt {
|
||||
log.Fatal("insane amounts of menuitems detected! Aborting before the collapse of the world!")
|
||||
}
|
||||
}
|
||||
idToMenuItem[menuItemIDCounter] = item
|
||||
menuItemToID[item] = menuItemIDCounter
|
||||
return menuItemIDCounter
|
||||
}
|
||||
|
||||
func getMenuItemForID(id uint) *MenuItem {
|
||||
menuItemLock.Lock()
|
||||
defer menuItemLock.Unlock()
|
||||
return idToMenuItem[id]
|
||||
}
|
||||
30
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/message.h
generated
vendored
Normal file
30
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/message.h
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// message.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 14/10/21.
|
||||
//
|
||||
|
||||
#ifndef export_h
|
||||
#define export_h
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
void processMessage(const char *);
|
||||
void processBindingMessage(const char *, const char *, bool);
|
||||
void processURLRequest(void *, void*);
|
||||
void processMessageDialogResponse(int);
|
||||
void processOpenFileDialogResponse(const char*);
|
||||
void processSaveFileDialogResponse(const char*);
|
||||
void processCallback(int);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* export_h */
|
||||
465
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/notifications.go
generated
vendored
Normal file
465
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/notifications.go
generated
vendored
Normal file
@@ -0,0 +1,465 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS:-x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa
|
||||
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
|
||||
#cgo LDFLAGS: -framework UserNotifications
|
||||
#endif
|
||||
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
// Package-scoped variable only accessible within this file
|
||||
var (
|
||||
currentFrontend *Frontend
|
||||
frontendMutex sync.RWMutex
|
||||
// Notification channels
|
||||
channels map[int]chan notificationChannel
|
||||
channelsLock sync.Mutex
|
||||
nextChannelID int
|
||||
|
||||
notificationResultCallback func(result frontend.NotificationResult)
|
||||
callbackLock sync.RWMutex
|
||||
)
|
||||
|
||||
const DefaultActionIdentifier = "DEFAULT_ACTION"
|
||||
const AppleDefaultActionIdentifier = "com.apple.UNNotificationDefaultActionIdentifier"
|
||||
|
||||
// setCurrentFrontend sets the current frontend instance
|
||||
// This is called when RequestNotificationAuthorization or CheckNotificationAuthorization is called
|
||||
func setCurrentFrontend(f *Frontend) {
|
||||
frontendMutex.Lock()
|
||||
defer frontendMutex.Unlock()
|
||||
currentFrontend = f
|
||||
}
|
||||
|
||||
// getCurrentFrontend gets the current frontend instance
|
||||
func getCurrentFrontend() *Frontend {
|
||||
frontendMutex.RLock()
|
||||
defer frontendMutex.RUnlock()
|
||||
return currentFrontend
|
||||
}
|
||||
|
||||
type notificationChannel struct {
|
||||
Success bool
|
||||
Error error
|
||||
}
|
||||
|
||||
func (f *Frontend) InitializeNotifications() error {
|
||||
if !f.IsNotificationAvailable() {
|
||||
return fmt.Errorf("notifications are not available on this system")
|
||||
}
|
||||
if !f.checkBundleIdentifier() {
|
||||
return fmt.Errorf("notifications require a valid bundle identifier")
|
||||
}
|
||||
if !bool(C.EnsureDelegateInitialized(f.mainWindow.context)) {
|
||||
return fmt.Errorf("failed to initialize notification center delegate")
|
||||
}
|
||||
|
||||
channels = make(map[int]chan notificationChannel)
|
||||
nextChannelID = 0
|
||||
|
||||
setCurrentFrontend(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupNotifications is a macOS stub that does nothing.
|
||||
// (Linux-specific cleanup)
|
||||
func (f *Frontend) CleanupNotifications() {
|
||||
// No cleanup needed on macOS
|
||||
}
|
||||
|
||||
func (f *Frontend) IsNotificationAvailable() bool {
|
||||
return bool(C.IsNotificationAvailable(f.mainWindow.context))
|
||||
}
|
||||
|
||||
func (f *Frontend) checkBundleIdentifier() bool {
|
||||
return bool(C.CheckBundleIdentifier(f.mainWindow.context))
|
||||
}
|
||||
|
||||
func (f *Frontend) RequestNotificationAuthorization() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
|
||||
C.RequestNotificationAuthorization(f.mainWindow.context, C.int(id))
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
return result.Success, result.Error
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return false, fmt.Errorf("notification authorization timed out after 3 minutes: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) CheckNotificationAuthorization() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
|
||||
C.CheckNotificationAuthorization(f.mainWindow.context, C.int(id))
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
return result.Success, result.Error
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return false, fmt.Errorf("notification authorization timed out after 15s: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
|
||||
func (f *Frontend) SendNotification(options frontend.NotificationOptions) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cIdentifier := C.CString(options.ID)
|
||||
cTitle := C.CString(options.Title)
|
||||
cSubtitle := C.CString(options.Subtitle)
|
||||
cBody := C.CString(options.Body)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
defer C.free(unsafe.Pointer(cTitle))
|
||||
defer C.free(unsafe.Pointer(cSubtitle))
|
||||
defer C.free(unsafe.Pointer(cBody))
|
||||
|
||||
var cDataJSON *C.char
|
||||
if options.Data != nil {
|
||||
jsonData, err := json.Marshal(options.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification data: %w", err)
|
||||
}
|
||||
cDataJSON = C.CString(string(jsonData))
|
||||
defer C.free(unsafe.Pointer(cDataJSON))
|
||||
}
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
C.SendNotification(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cDataJSON)
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
if !result.Success {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return fmt.Errorf("sending notification failed")
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return fmt.Errorf("sending notification timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// SendNotificationWithActions sends a notification with additional actions and inputs.
|
||||
// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
|
||||
// If a NotificationCategory is not registered a basic notification will be sent.
|
||||
func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cIdentifier := C.CString(options.ID)
|
||||
cTitle := C.CString(options.Title)
|
||||
cSubtitle := C.CString(options.Subtitle)
|
||||
cBody := C.CString(options.Body)
|
||||
cCategoryID := C.CString(options.CategoryID)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
defer C.free(unsafe.Pointer(cTitle))
|
||||
defer C.free(unsafe.Pointer(cSubtitle))
|
||||
defer C.free(unsafe.Pointer(cBody))
|
||||
defer C.free(unsafe.Pointer(cCategoryID))
|
||||
|
||||
var cDataJSON *C.char
|
||||
if options.Data != nil {
|
||||
jsonData, err := json.Marshal(options.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification data: %w", err)
|
||||
}
|
||||
cDataJSON = C.CString(string(jsonData))
|
||||
defer C.free(unsafe.Pointer(cDataJSON))
|
||||
}
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
C.SendNotificationWithActions(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cCategoryID, cDataJSON)
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
if !result.Success {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return fmt.Errorf("sending notification failed")
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return fmt.Errorf("sending notification timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
// Registering a category with the same name as a previously registered NotificationCategory will override it.
|
||||
func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cCategoryID := C.CString(category.ID)
|
||||
defer C.free(unsafe.Pointer(cCategoryID))
|
||||
|
||||
actionsJSON, err := json.Marshal(category.Actions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification category: %w", err)
|
||||
}
|
||||
cActionsJSON := C.CString(string(actionsJSON))
|
||||
defer C.free(unsafe.Pointer(cActionsJSON))
|
||||
|
||||
var cReplyPlaceholder, cReplyButtonTitle *C.char
|
||||
if category.HasReplyField {
|
||||
cReplyPlaceholder = C.CString(category.ReplyPlaceholder)
|
||||
cReplyButtonTitle = C.CString(category.ReplyButtonTitle)
|
||||
defer C.free(unsafe.Pointer(cReplyPlaceholder))
|
||||
defer C.free(unsafe.Pointer(cReplyButtonTitle))
|
||||
}
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
C.RegisterNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID, cActionsJSON, C.bool(category.HasReplyField),
|
||||
cReplyPlaceholder, cReplyButtonTitle)
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
if !result.Success {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return fmt.Errorf("category registration failed")
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return fmt.Errorf("category registration timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveNotificationCategory remove a previously registered NotificationCategory.
|
||||
func (f *Frontend) RemoveNotificationCategory(categoryId string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cCategoryID := C.CString(categoryId)
|
||||
defer C.free(unsafe.Pointer(cCategoryID))
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
C.RemoveNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID)
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
if !result.Success {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return fmt.Errorf("category removal failed")
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return fmt.Errorf("category removal timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAllPendingNotifications removes all pending notifications.
|
||||
func (f *Frontend) RemoveAllPendingNotifications() error {
|
||||
C.RemoveAllPendingNotifications(f.mainWindow.context)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePendingNotification removes a pending notification matching the unique identifier.
|
||||
func (f *Frontend) RemovePendingNotification(identifier string) error {
|
||||
cIdentifier := C.CString(identifier)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
C.RemovePendingNotification(f.mainWindow.context, cIdentifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllDeliveredNotifications removes all delivered notifications.
|
||||
func (f *Frontend) RemoveAllDeliveredNotifications() error {
|
||||
C.RemoveAllDeliveredNotifications(f.mainWindow.context)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDeliveredNotification removes a delivered notification matching the unique identifier.
|
||||
func (f *Frontend) RemoveDeliveredNotification(identifier string) error {
|
||||
cIdentifier := C.CString(identifier)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
C.RemoveDeliveredNotification(f.mainWindow.context, cIdentifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNotification is a macOS stub that always returns nil.
|
||||
// Use one of the following instead:
|
||||
// RemoveAllPendingNotifications
|
||||
// RemovePendingNotification
|
||||
// RemoveAllDeliveredNotifications
|
||||
// RemoveDeliveredNotification
|
||||
// (Linux-specific)
|
||||
func (f *Frontend) RemoveNotification(identifier string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) {
|
||||
callbackLock.Lock()
|
||||
notificationResultCallback = callback
|
||||
callbackLock.Unlock()
|
||||
}
|
||||
|
||||
//export captureResult
|
||||
func captureResult(channelID C.int, success C.bool, errorMsg *C.char) {
|
||||
f := getCurrentFrontend()
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resultCh, exists := f.GetChannel(int(channelID))
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if errorMsg != nil {
|
||||
err = fmt.Errorf("%s", C.GoString(errorMsg))
|
||||
}
|
||||
|
||||
resultCh <- notificationChannel{
|
||||
Success: bool(success),
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
//export didReceiveNotificationResponse
|
||||
func didReceiveNotificationResponse(jsonPayload *C.char, err *C.char) {
|
||||
result := frontend.NotificationResult{}
|
||||
|
||||
if err != nil {
|
||||
errMsg := C.GoString(err)
|
||||
result.Error = fmt.Errorf("notification response error: %s", errMsg)
|
||||
handleNotificationResult(result)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if jsonPayload == nil {
|
||||
result.Error = fmt.Errorf("received nil JSON payload in notification response")
|
||||
handleNotificationResult(result)
|
||||
return
|
||||
}
|
||||
|
||||
payload := C.GoString(jsonPayload)
|
||||
|
||||
var response frontend.NotificationResponse
|
||||
if err := json.Unmarshal([]byte(payload), &response); err != nil {
|
||||
result.Error = fmt.Errorf("failed to unmarshal notification response: %w", err)
|
||||
handleNotificationResult(result)
|
||||
return
|
||||
}
|
||||
|
||||
if response.ActionIdentifier == AppleDefaultActionIdentifier {
|
||||
response.ActionIdentifier = DefaultActionIdentifier
|
||||
}
|
||||
|
||||
result.Response = response
|
||||
handleNotificationResult(result)
|
||||
}
|
||||
|
||||
func handleNotificationResult(result frontend.NotificationResult) {
|
||||
callbackLock.Lock()
|
||||
callback := notificationResultCallback
|
||||
callbackLock.Unlock()
|
||||
|
||||
if callback != nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Log panic but don't crash the app
|
||||
fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
|
||||
}
|
||||
}()
|
||||
callback(result)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
func (f *Frontend) registerChannel() (int, chan notificationChannel) {
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
// Initialize channels map if it's nil
|
||||
if channels == nil {
|
||||
channels = make(map[int]chan notificationChannel)
|
||||
nextChannelID = 0
|
||||
}
|
||||
|
||||
id := nextChannelID
|
||||
nextChannelID++
|
||||
|
||||
resultCh := make(chan notificationChannel, 1)
|
||||
|
||||
channels[id] = resultCh
|
||||
return id, resultCh
|
||||
}
|
||||
|
||||
func (f *Frontend) GetChannel(id int) (chan notificationChannel, bool) {
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
if channels == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ch, exists := channels[id]
|
||||
if exists {
|
||||
delete(channels, id)
|
||||
}
|
||||
return ch, exists
|
||||
}
|
||||
|
||||
func (f *Frontend) cleanupChannel(id int) {
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
if channels == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ch, exists := channels[id]; exists {
|
||||
delete(channels, id)
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
118
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/screen.go
generated
vendored
Normal file
118
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/screen.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit -framework AppKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
typedef struct Screen {
|
||||
int isCurrent;
|
||||
int isPrimary;
|
||||
int height;
|
||||
int width;
|
||||
int pHeight;
|
||||
int pWidth;
|
||||
} Screen;
|
||||
|
||||
|
||||
int GetNumScreens(){
|
||||
return [[NSScreen screens] count];
|
||||
}
|
||||
|
||||
int screenUniqueID(NSScreen *screen){
|
||||
// adapted from https://stackoverflow.com/a/1237490/4188138
|
||||
NSDictionary* screenDictionary = [screen deviceDescription];
|
||||
NSNumber* screenID = [screenDictionary objectForKey:@"NSScreenNumber"];
|
||||
CGDirectDisplayID aID = [screenID unsignedIntValue];
|
||||
return aID;
|
||||
}
|
||||
|
||||
Screen GetNthScreen(int nth, void *inctx){
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSArray<NSScreen *> *screens = [NSScreen screens];
|
||||
NSScreen* nthScreen = [screens objectAtIndex:nth];
|
||||
NSScreen* currentScreen = [ctx getCurrentScreen];
|
||||
|
||||
Screen returnScreen;
|
||||
returnScreen.isCurrent = (int)(screenUniqueID(currentScreen)==screenUniqueID(nthScreen));
|
||||
// TODO properly handle screen mirroring
|
||||
// from apple documentation:
|
||||
// https://developer.apple.com/documentation/appkit/nsscreen/1388393-screens?language=objc
|
||||
// The screen at index 0 in the returned array corresponds to the primary screen of the user’s system. This is the screen that contains the menu bar and whose origin is at the point (0, 0). In the case of mirroring, the first screen is the largest drawable display; if all screens are the same size, it is the screen with the highest pixel depth. This primary screen may not be the same as the one returned by the mainScreen method, which returns the screen with the active window.
|
||||
returnScreen.isPrimary = nth==0;
|
||||
returnScreen.height = (int) nthScreen.frame.size.height;
|
||||
returnScreen.width = (int) nthScreen.frame.size.width;
|
||||
|
||||
returnScreen.pWidth = 0;
|
||||
returnScreen.pHeight = 0;
|
||||
|
||||
// https://stackoverflow.com/questions/13859109/how-to-programmatically-determine-native-pixel-resolution-of-retina-macbook-pro
|
||||
CGDirectDisplayID sid = ((NSNumber *)[nthScreen.deviceDescription
|
||||
objectForKey:@"NSScreenNumber"]).unsignedIntegerValue;
|
||||
|
||||
CFArrayRef ms = CGDisplayCopyAllDisplayModes(sid, NULL);
|
||||
CFIndex n = CFArrayGetCount(ms);
|
||||
for (int i = 0; i < n; i++) {
|
||||
CGDisplayModeRef m = (CGDisplayModeRef) CFArrayGetValueAtIndex(ms, i);
|
||||
if (CGDisplayModeGetIOFlags(m) & kDisplayModeNativeFlag) {
|
||||
// This corresponds with "System Settings" -> General -> About -> Displays
|
||||
returnScreen.pWidth = CGDisplayModeGetPixelWidth(m);
|
||||
returnScreen.pHeight = CGDisplayModeGetPixelHeight(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CFRelease(ms);
|
||||
|
||||
if (returnScreen.pWidth == 0 || returnScreen.pHeight == 0) {
|
||||
// If there was no native resolution take a best fit approach and use the backing pixel size.
|
||||
NSRect pSize = [nthScreen convertRectToBacking:nthScreen.frame];
|
||||
returnScreen.pHeight = (int) pSize.size.height;
|
||||
returnScreen.pWidth = (int) pSize.size.width;
|
||||
}
|
||||
return returnScreen;
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
func GetAllScreens(wailsContext unsafe.Pointer) ([]frontend.Screen, error) {
|
||||
err := error(nil)
|
||||
screens := []frontend.Screen{}
|
||||
numScreens := int(C.GetNumScreens())
|
||||
for screeNum := 0; screeNum < numScreens; screeNum++ {
|
||||
screenNumC := C.int(screeNum)
|
||||
cScreen := C.GetNthScreen(screenNumC, wailsContext)
|
||||
|
||||
screen := frontend.Screen{
|
||||
Height: int(cScreen.height),
|
||||
Width: int(cScreen.width),
|
||||
IsCurrent: cScreen.isCurrent == C.int(1),
|
||||
IsPrimary: cScreen.isPrimary == C.int(1),
|
||||
|
||||
Size: frontend.ScreenSize{
|
||||
Height: int(cScreen.height),
|
||||
Width: int(cScreen.width),
|
||||
},
|
||||
PhysicalSize: frontend.ScreenSize{
|
||||
Height: int(cScreen.pHeight),
|
||||
Width: int(cScreen.pWidth),
|
||||
},
|
||||
}
|
||||
screens = append(screens, screen)
|
||||
}
|
||||
return screens, err
|
||||
}
|
||||
95
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/single_instance.go
generated
vendored
Normal file
95
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/single_instance.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa
|
||||
#import "AppDelegate.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func SetupSingleInstance(uniqueID string) *os.File {
|
||||
lockFilePath := getTempDir()
|
||||
lockFileName := uniqueID + ".lock"
|
||||
file, err := createLockFile(lockFilePath + "/" + lockFileName)
|
||||
// if lockFile exist – send notification to second instance
|
||||
if err != nil {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
singleInstanceUniqueId := c.String(uniqueID)
|
||||
|
||||
data, err := options.NewSecondInstanceData()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
C.SendDataToFirstInstance(singleInstanceUniqueId, c.String(string(serialized)))
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
//export HandleSecondInstanceData
|
||||
func HandleSecondInstanceData(secondInstanceMessage *C.char) {
|
||||
message := C.GoString(secondInstanceMessage)
|
||||
|
||||
var secondInstanceData options.SecondInstanceData
|
||||
|
||||
err := json.Unmarshal([]byte(message), &secondInstanceData)
|
||||
if err == nil {
|
||||
secondInstanceBuffer <- secondInstanceData
|
||||
}
|
||||
}
|
||||
|
||||
// createLockFile tries to create a file with given name and acquire an
|
||||
// exclusive lock on it. If the file already exists AND is still locked, it will
|
||||
// fail.
|
||||
func createLockFile(filename string) (*os.File, error) {
|
||||
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0o600)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to open lockfile %s: %s", filename, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
// Flock failed for some other reason than other instance already lock it. Print it in logs for possible debugging.
|
||||
if !strings.Contains(err.Error(), "resource temporarily unavailable") {
|
||||
fmt.Printf("Failed to lock lockfile %s: %s", filename, err)
|
||||
}
|
||||
file.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// If app is sandboxed, golang os.TempDir() will return path that will not be accessible. So use native macOS temp dir function.
|
||||
func getTempDir() string {
|
||||
cstring := C.GetMacOsNativeTempDir()
|
||||
path := C.GoString(cstring)
|
||||
C.free(unsafe.Pointer(cstring))
|
||||
|
||||
return path
|
||||
}
|
||||
313
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/window.go
generated
vendored
Normal file
313
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/window.go
generated
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
type Window struct {
|
||||
context unsafe.Pointer
|
||||
|
||||
applicationMenu *menu.Menu
|
||||
}
|
||||
|
||||
func bool2Cint(value bool) C.int {
|
||||
if value {
|
||||
return C.int(1)
|
||||
}
|
||||
return C.int(0)
|
||||
}
|
||||
|
||||
func bool2CboolPtr(value bool) *C.bool {
|
||||
v := C.bool(value)
|
||||
return &v
|
||||
}
|
||||
|
||||
func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
|
||||
frameless := bool2Cint(frontendOptions.Frameless)
|
||||
resizable := bool2Cint(!frontendOptions.DisableResize)
|
||||
fullscreen := bool2Cint(frontendOptions.Fullscreen)
|
||||
alwaysOnTop := bool2Cint(frontendOptions.AlwaysOnTop)
|
||||
hideWindowOnClose := bool2Cint(frontendOptions.HideWindowOnClose)
|
||||
startsHidden := bool2Cint(frontendOptions.StartHidden)
|
||||
devtoolsEnabled := bool2Cint(devtools)
|
||||
defaultContextMenuEnabled := bool2Cint(debug || frontendOptions.EnableDefaultContextMenu)
|
||||
singleInstanceEnabled := bool2Cint(frontendOptions.SingleInstanceLock != nil)
|
||||
|
||||
var fullSizeContent, hideTitleBar, zoomable, hideTitle, useToolbar, webviewIsTransparent C.int
|
||||
var titlebarAppearsTransparent, hideToolbarSeparator, windowIsTranslucent, contentProtection C.int
|
||||
var appearance, title *C.char
|
||||
var preferences C.struct_Preferences
|
||||
|
||||
width := C.int(frontendOptions.Width)
|
||||
height := C.int(frontendOptions.Height)
|
||||
minWidth := C.int(frontendOptions.MinWidth)
|
||||
minHeight := C.int(frontendOptions.MinHeight)
|
||||
maxWidth := C.int(frontendOptions.MaxWidth)
|
||||
maxHeight := C.int(frontendOptions.MaxHeight)
|
||||
windowStartState := C.int(int(frontendOptions.WindowStartState))
|
||||
|
||||
title = c.String(frontendOptions.Title)
|
||||
|
||||
singleInstanceUniqueIdStr := ""
|
||||
if frontendOptions.SingleInstanceLock != nil {
|
||||
singleInstanceUniqueIdStr = frontendOptions.SingleInstanceLock.UniqueId
|
||||
}
|
||||
singleInstanceUniqueId := c.String(singleInstanceUniqueIdStr)
|
||||
|
||||
enableFraudulentWebsiteWarnings := C.bool(frontendOptions.EnableFraudulentWebsiteDetection)
|
||||
|
||||
enableDragAndDrop := C.bool(frontendOptions.DragAndDrop != nil && frontendOptions.DragAndDrop.EnableFileDrop)
|
||||
disableWebViewDragAndDrop := C.bool(frontendOptions.DragAndDrop != nil && frontendOptions.DragAndDrop.DisableWebViewDrop)
|
||||
|
||||
if frontendOptions.Mac != nil {
|
||||
mac := frontendOptions.Mac
|
||||
if mac.TitleBar != nil {
|
||||
fullSizeContent = bool2Cint(mac.TitleBar.FullSizeContent)
|
||||
hideTitleBar = bool2Cint(mac.TitleBar.HideTitleBar)
|
||||
hideTitle = bool2Cint(mac.TitleBar.HideTitle)
|
||||
useToolbar = bool2Cint(mac.TitleBar.UseToolbar)
|
||||
titlebarAppearsTransparent = bool2Cint(mac.TitleBar.TitlebarAppearsTransparent)
|
||||
hideToolbarSeparator = bool2Cint(mac.TitleBar.HideToolbarSeparator)
|
||||
}
|
||||
|
||||
if mac.Preferences != nil {
|
||||
if mac.Preferences.TabFocusesLinks.IsSet() {
|
||||
preferences.tabFocusesLinks = bool2CboolPtr(mac.Preferences.TabFocusesLinks.Get())
|
||||
}
|
||||
|
||||
if mac.Preferences.TextInteractionEnabled.IsSet() {
|
||||
preferences.textInteractionEnabled = bool2CboolPtr(mac.Preferences.TextInteractionEnabled.Get())
|
||||
}
|
||||
|
||||
if mac.Preferences.FullscreenEnabled.IsSet() {
|
||||
preferences.fullscreenEnabled = bool2CboolPtr(mac.Preferences.FullscreenEnabled.Get())
|
||||
}
|
||||
}
|
||||
|
||||
zoomable = bool2Cint(!frontendOptions.Mac.DisableZoom)
|
||||
|
||||
windowIsTranslucent = bool2Cint(mac.WindowIsTranslucent)
|
||||
webviewIsTransparent = bool2Cint(mac.WebviewIsTransparent)
|
||||
contentProtection = bool2Cint(mac.ContentProtection)
|
||||
|
||||
appearance = c.String(string(mac.Appearance))
|
||||
}
|
||||
var context *C.WailsContext = C.Create(title, width, height, frameless, resizable, zoomable, fullscreen, fullSizeContent,
|
||||
hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent,
|
||||
alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, contentProtection, devtoolsEnabled, defaultContextMenuEnabled,
|
||||
windowStartState, startsHidden, minWidth, minHeight, maxWidth, maxHeight, enableFraudulentWebsiteWarnings,
|
||||
preferences, singleInstanceEnabled, singleInstanceUniqueId, enableDragAndDrop, disableWebViewDragAndDrop,
|
||||
)
|
||||
|
||||
// Create menu
|
||||
result := &Window{
|
||||
context: unsafe.Pointer(context),
|
||||
}
|
||||
|
||||
if frontendOptions.BackgroundColour != nil {
|
||||
result.SetBackgroundColour(frontendOptions.BackgroundColour.R, frontendOptions.BackgroundColour.G, frontendOptions.BackgroundColour.B, frontendOptions.BackgroundColour.A)
|
||||
}
|
||||
|
||||
if frontendOptions.Mac != nil && frontendOptions.Mac.About != nil {
|
||||
title := c.String(frontendOptions.Mac.About.Title)
|
||||
description := c.String(frontendOptions.Mac.About.Message)
|
||||
var icon unsafe.Pointer
|
||||
var length C.int
|
||||
if frontendOptions.Mac.About.Icon != nil {
|
||||
icon = unsafe.Pointer(&frontendOptions.Mac.About.Icon[0])
|
||||
length = C.int(len(frontendOptions.Mac.About.Icon))
|
||||
}
|
||||
C.SetAbout(result.context, title, description, icon, length)
|
||||
}
|
||||
|
||||
if frontendOptions.Menu != nil {
|
||||
result.SetApplicationMenu(frontendOptions.Menu)
|
||||
}
|
||||
|
||||
if debug && frontendOptions.Debug.OpenInspectorOnStartup {
|
||||
showInspector(result.context)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (w *Window) Center() {
|
||||
C.Center(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) Run(url string) {
|
||||
_url := C.CString(url)
|
||||
C.Run(w.context, _url)
|
||||
C.free(unsafe.Pointer(_url))
|
||||
}
|
||||
|
||||
func (w *Window) Quit() {
|
||||
C.Quit(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) SetBackgroundColour(r uint8, g uint8, b uint8, a uint8) {
|
||||
C.SetBackgroundColour(w.context, C.int(r), C.int(g), C.int(b), C.int(a))
|
||||
}
|
||||
|
||||
func (w *Window) ExecJS(js string) {
|
||||
_js := C.CString(js)
|
||||
C.ExecJS(w.context, _js)
|
||||
C.free(unsafe.Pointer(_js))
|
||||
}
|
||||
|
||||
func (w *Window) SetPosition(x int, y int) {
|
||||
C.SetPosition(w.context, C.int(x), C.int(y))
|
||||
}
|
||||
|
||||
func (w *Window) SetSize(width int, height int) {
|
||||
C.SetSize(w.context, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (w *Window) SetAlwaysOnTop(onTop bool) {
|
||||
C.SetAlwaysOnTop(w.context, bool2Cint(onTop))
|
||||
}
|
||||
|
||||
func (w *Window) SetTitle(title string) {
|
||||
t := C.CString(title)
|
||||
C.SetTitle(w.context, t)
|
||||
C.free(unsafe.Pointer(t))
|
||||
}
|
||||
|
||||
func (w *Window) Maximise() {
|
||||
C.Maximise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) ToggleMaximise() {
|
||||
C.ToggleMaximise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) UnMaximise() {
|
||||
C.UnMaximise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) IsMaximised() bool {
|
||||
return (bool)(C.IsMaximised(w.context))
|
||||
}
|
||||
|
||||
func (w *Window) Minimise() {
|
||||
C.Minimise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) UnMinimise() {
|
||||
C.UnMinimise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) IsMinimised() bool {
|
||||
return (bool)(C.IsMinimised(w.context))
|
||||
}
|
||||
|
||||
func (w *Window) IsNormal() bool {
|
||||
return !w.IsMaximised() && !w.IsMinimised() && !w.IsFullScreen()
|
||||
}
|
||||
|
||||
func (w *Window) SetMinSize(width int, height int) {
|
||||
C.SetMinSize(w.context, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (w *Window) SetMaxSize(width int, height int) {
|
||||
C.SetMaxSize(w.context, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (w *Window) Fullscreen() {
|
||||
C.Fullscreen(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) UnFullscreen() {
|
||||
C.UnFullscreen(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) IsFullScreen() bool {
|
||||
return (bool)(C.IsFullScreen(w.context))
|
||||
}
|
||||
|
||||
func (w *Window) Show() {
|
||||
C.Show(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) Hide() {
|
||||
C.Hide(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) ShowApplication() {
|
||||
C.ShowApplication(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) HideApplication() {
|
||||
C.HideApplication(w.context)
|
||||
}
|
||||
|
||||
func parseIntDuo(temp string) (int, int) {
|
||||
split := strings.Split(temp, ",")
|
||||
x, err := strconv.Atoi(split[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
y, err := strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (w *Window) GetPosition() (int, int) {
|
||||
var _result *C.char = C.GetPosition(w.context)
|
||||
temp := C.GoString(_result)
|
||||
return parseIntDuo(temp)
|
||||
}
|
||||
|
||||
func (w *Window) Size() (int, int) {
|
||||
var _result *C.char = C.GetSize(w.context)
|
||||
temp := C.GoString(_result)
|
||||
return parseIntDuo(temp)
|
||||
}
|
||||
|
||||
func (w *Window) SetApplicationMenu(inMenu *menu.Menu) {
|
||||
w.applicationMenu = inMenu
|
||||
w.UpdateApplicationMenu()
|
||||
}
|
||||
|
||||
func (w *Window) UpdateApplicationMenu() {
|
||||
mainMenu := NewNSMenu(w.context, "")
|
||||
if w.applicationMenu != nil {
|
||||
processMenu(mainMenu, w.applicationMenu)
|
||||
}
|
||||
C.SetAsApplicationMenu(w.context, mainMenu.nsmenu)
|
||||
C.UpdateApplicationMenu(w.context)
|
||||
}
|
||||
|
||||
func (w Window) Print() {
|
||||
C.WindowPrint(w.context)
|
||||
}
|
||||
20
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_darwin.go
generated
vendored
Normal file
20
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
|
||||
return darwin.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
|
||||
}
|
||||
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_linux.go
generated
vendored
Normal file
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_linux.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/linux"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
|
||||
return linux.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
|
||||
}
|
||||
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_windows.go
generated
vendored
Normal file
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_windows.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
|
||||
return windows.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
|
||||
}
|
||||
23
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/browser.go
generated
vendored
Normal file
23
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/browser.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/utils"
|
||||
)
|
||||
|
||||
// BrowserOpenURL Use the default browser to open the url
|
||||
func (f *Frontend) BrowserOpenURL(rawURL string) {
|
||||
url, err := utils.ValidateAndSanitizeURL(rawURL)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Invalid URL %s", err.Error()))
|
||||
return
|
||||
}
|
||||
// Specific method implementation
|
||||
if err := browser.OpenURL(url); err != nil {
|
||||
f.logger.Error("Unable to open default system browser")
|
||||
}
|
||||
}
|
||||
35
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/calloc.go
generated
vendored
Normal file
35
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/calloc.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// Calloc handles alloc/dealloc of C data
|
||||
type Calloc struct {
|
||||
pool []unsafe.Pointer
|
||||
}
|
||||
|
||||
// NewCalloc creates a new allocator
|
||||
func NewCalloc() Calloc {
|
||||
return Calloc{}
|
||||
}
|
||||
|
||||
// String creates a new C string and retains a reference to it
|
||||
func (c Calloc) String(in string) *C.char {
|
||||
result := C.CString(in)
|
||||
c.pool = append(c.pool, unsafe.Pointer(result))
|
||||
return result
|
||||
}
|
||||
|
||||
// Free frees all allocated C memory
|
||||
func (c Calloc) Free() {
|
||||
for _, str := range c.pool {
|
||||
C.free(str)
|
||||
}
|
||||
c.pool = []unsafe.Pointer{}
|
||||
}
|
||||
51
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/clipboard.go
generated
vendored
Normal file
51
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/clipboard.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
|
||||
static gchar* GetClipboardText() {
|
||||
GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
|
||||
return gtk_clipboard_wait_for_text(clip);
|
||||
}
|
||||
|
||||
static void SetClipboardText(gchar* text) {
|
||||
GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
|
||||
gtk_clipboard_set_text(clip, text, -1);
|
||||
|
||||
clip = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
|
||||
gtk_clipboard_set_text(clip, text, -1);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import "sync"
|
||||
|
||||
func (f *Frontend) ClipboardGetText() (string, error) {
|
||||
var text string
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
invokeOnMainThread(func() {
|
||||
ctxt := C.GetClipboardText()
|
||||
defer C.g_free(C.gpointer(ctxt))
|
||||
text = C.GoString(ctxt)
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func (f *Frontend) ClipboardSetText(text string) error {
|
||||
invokeOnMainThread(func() {
|
||||
ctxt := (*C.gchar)(C.CString(text))
|
||||
defer C.g_free(C.gpointer(ctxt))
|
||||
C.SetClipboardText(ctxt)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
89
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/dialog.go
generated
vendored
Normal file
89
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/dialog.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include "gtk/gtk.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN C.GtkFileChooserAction = C.GTK_FILE_CHOOSER_ACTION_OPEN
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE C.GtkFileChooserAction = C.GTK_FILE_CHOOSER_ACTION_SAVE
|
||||
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER C.GtkFileChooserAction = C.GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
|
||||
)
|
||||
|
||||
var openFileResults = make(chan []string)
|
||||
var messageDialogResult = make(chan string)
|
||||
|
||||
func (f *Frontend) OpenFileDialog(dialogOptions frontend.OpenDialogOptions) (result string, err error) {
|
||||
f.mainWindow.OpenFileDialog(dialogOptions, 0, GTK_FILE_CHOOSER_ACTION_OPEN)
|
||||
results := <-openFileResults
|
||||
if len(results) == 1 {
|
||||
return results[0], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *Frontend) OpenMultipleFilesDialog(dialogOptions frontend.OpenDialogOptions) ([]string, error) {
|
||||
f.mainWindow.OpenFileDialog(dialogOptions, 1, GTK_FILE_CHOOSER_ACTION_OPEN)
|
||||
result := <-openFileResults
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f *Frontend) OpenDirectoryDialog(dialogOptions frontend.OpenDialogOptions) (string, error) {
|
||||
f.mainWindow.OpenFileDialog(dialogOptions, 0, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
||||
result := <-openFileResults
|
||||
if len(result) == 1 {
|
||||
return result[0], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *Frontend) SaveFileDialog(dialogOptions frontend.SaveDialogOptions) (string, error) {
|
||||
options := frontend.OpenDialogOptions{
|
||||
DefaultDirectory: dialogOptions.DefaultDirectory,
|
||||
DefaultFilename: dialogOptions.DefaultFilename,
|
||||
Title: dialogOptions.Title,
|
||||
Filters: dialogOptions.Filters,
|
||||
ShowHiddenFiles: dialogOptions.ShowHiddenFiles,
|
||||
CanCreateDirectories: dialogOptions.CanCreateDirectories,
|
||||
}
|
||||
f.mainWindow.OpenFileDialog(options, 0, GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
results := <-openFileResults
|
||||
if len(results) == 1 {
|
||||
return results[0], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *Frontend) MessageDialog(dialogOptions frontend.MessageDialogOptions) (string, error) {
|
||||
f.mainWindow.MessageDialog(dialogOptions)
|
||||
return <-messageDialogResult, nil
|
||||
}
|
||||
|
||||
//export processOpenFileResult
|
||||
func processOpenFileResult(carray **C.char) {
|
||||
// Create a Go slice from the C array
|
||||
var result []string
|
||||
goArray := (*[1024]*C.char)(unsafe.Pointer(carray))[:1024:1024]
|
||||
for _, s := range goArray {
|
||||
if s == nil {
|
||||
break
|
||||
}
|
||||
result = append(result, C.GoString(s))
|
||||
}
|
||||
openFileResults <- result
|
||||
}
|
||||
|
||||
//export processMessageDialogResult
|
||||
func processMessageDialogResult(result *C.char) {
|
||||
messageDialogResult <- C.GoString(result)
|
||||
}
|
||||
589
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/frontend.go
generated
vendored
Normal file
589
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/frontend.go
generated
vendored
Normal file
@@ -0,0 +1,589 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
|
||||
// CREDIT: https://github.com/rainycape/magick
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void fix_signal(int signum)
|
||||
{
|
||||
struct sigaction st;
|
||||
|
||||
if (sigaction(signum, NULL, &st) < 0) {
|
||||
goto fix_signal_error;
|
||||
}
|
||||
st.sa_flags |= SA_ONSTACK;
|
||||
if (sigaction(signum, &st, NULL) < 0) {
|
||||
goto fix_signal_error;
|
||||
}
|
||||
return;
|
||||
fix_signal_error:
|
||||
fprintf(stderr, "error fixing handler for signal %d, please "
|
||||
"report this issue to "
|
||||
"https://github.com/wailsapp/wails: %s\n",
|
||||
signum, strerror(errno));
|
||||
}
|
||||
|
||||
static void install_signal_handlers()
|
||||
{
|
||||
#if defined(SIGCHLD)
|
||||
fix_signal(SIGCHLD);
|
||||
#endif
|
||||
#if defined(SIGHUP)
|
||||
fix_signal(SIGHUP);
|
||||
#endif
|
||||
#if defined(SIGINT)
|
||||
fix_signal(SIGINT);
|
||||
#endif
|
||||
#if defined(SIGQUIT)
|
||||
fix_signal(SIGQUIT);
|
||||
#endif
|
||||
#if defined(SIGABRT)
|
||||
fix_signal(SIGABRT);
|
||||
#endif
|
||||
#if defined(SIGFPE)
|
||||
fix_signal(SIGFPE);
|
||||
#endif
|
||||
#if defined(SIGTERM)
|
||||
fix_signal(SIGTERM);
|
||||
#endif
|
||||
#if defined(SIGBUS)
|
||||
fix_signal(SIGBUS);
|
||||
#endif
|
||||
#if defined(SIGSEGV)
|
||||
fix_signal(SIGSEGV);
|
||||
#endif
|
||||
#if defined(SIGXCPU)
|
||||
fix_signal(SIGXCPU);
|
||||
#endif
|
||||
#if defined(SIGXFSZ)
|
||||
fix_signal(SIGXFSZ);
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean install_signal_handlers_idle(gpointer data) {
|
||||
(void)data;
|
||||
install_signal_handlers();
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void fix_signal_handlers_after_gtk_init() {
|
||||
g_idle_add(install_signal_handlers_idle, NULL);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/originvalidator"
|
||||
wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
var initOnce = sync.Once{}
|
||||
|
||||
const startURL = "wails://wails/"
|
||||
|
||||
var secondInstanceBuffer = make(chan options.SecondInstanceData, 1)
|
||||
|
||||
type Frontend struct {
|
||||
|
||||
// Context
|
||||
ctx context.Context
|
||||
|
||||
frontendOptions *options.App
|
||||
logger *logger.Logger
|
||||
debug bool
|
||||
devtoolsEnabled bool
|
||||
|
||||
// Assets
|
||||
assets *assetserver.AssetServer
|
||||
startURL *url.URL
|
||||
|
||||
// main window handle
|
||||
mainWindow *Window
|
||||
bindings *binding.Bindings
|
||||
dispatcher frontend.Dispatcher
|
||||
|
||||
originValidator *originvalidator.OriginValidator
|
||||
}
|
||||
|
||||
func (f *Frontend) RunMainLoop() {
|
||||
C.gtk_main()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowClose() {
|
||||
f.mainWindow.Destroy()
|
||||
}
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
|
||||
initOnce.Do(func() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
// Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings
|
||||
if os.Getenv("GDK_BACKEND") == "" && (os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "unspecified" || os.Getenv("XDG_SESSION_TYPE") == "x11") {
|
||||
_ = os.Setenv("GDK_BACKEND", "x11")
|
||||
}
|
||||
|
||||
if ok := C.gtk_init_check(nil, nil); ok != 1 {
|
||||
panic(errors.New("failed to init GTK"))
|
||||
}
|
||||
})
|
||||
|
||||
result := &Frontend{
|
||||
frontendOptions: appoptions,
|
||||
logger: myLogger,
|
||||
bindings: appBindings,
|
||||
dispatcher: dispatcher,
|
||||
ctx: ctx,
|
||||
}
|
||||
result.startURL, _ = url.Parse(startURL)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
|
||||
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
|
||||
result.startURL = _starturl
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
} else {
|
||||
if port, _ := ctx.Value("assetserverport").(string); port != "" {
|
||||
result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
}
|
||||
|
||||
var bindings string
|
||||
var err error
|
||||
if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
|
||||
bindings, err = appBindings.ToJSON()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
appBindings.DB().UpdateObfuscatedCallMap()
|
||||
}
|
||||
assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
result.assets = assets
|
||||
|
||||
go result.startRequestProcessor()
|
||||
}
|
||||
|
||||
go result.startMessageProcessor()
|
||||
go result.startBindingsMessageProcessor()
|
||||
|
||||
var _debug = ctx.Value("debug")
|
||||
var _devtoolsEnabled = ctx.Value("devtoolsEnabled")
|
||||
|
||||
if _debug != nil {
|
||||
result.debug = _debug.(bool)
|
||||
}
|
||||
if _devtoolsEnabled != nil {
|
||||
result.devtoolsEnabled = _devtoolsEnabled.(bool)
|
||||
}
|
||||
|
||||
result.mainWindow = NewWindow(appoptions, result.debug, result.devtoolsEnabled)
|
||||
|
||||
C.fix_signal_handlers_after_gtk_init()
|
||||
|
||||
if appoptions.Linux != nil && appoptions.Linux.ProgramName != "" {
|
||||
prgname := C.CString(appoptions.Linux.ProgramName)
|
||||
C.g_set_prgname(prgname)
|
||||
C.free(unsafe.Pointer(prgname))
|
||||
}
|
||||
|
||||
go result.startSecondInstanceProcessor()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *Frontend) startMessageProcessor() {
|
||||
for message := range messageBuffer {
|
||||
f.processMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startBindingsMessageProcessor() {
|
||||
for msg := range bindingsMessageBuffer {
|
||||
origin, err := f.originValidator.GetOriginFromURL(msg.source)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("failed to get origin for URL %q: %v", msg.source, err))
|
||||
continue
|
||||
}
|
||||
|
||||
allowed := f.originValidator.IsOriginAllowed(origin)
|
||||
if !allowed {
|
||||
f.logger.Error("Blocked request from unauthorized origin: %s", origin)
|
||||
continue
|
||||
}
|
||||
|
||||
f.processMessage(msg.message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReload() {
|
||||
f.ExecJS("runtime.WindowReload();")
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetSystemDefaultTheme() {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetLightTheme() {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetDarkTheme() {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Frontend) Run(ctx context.Context) error {
|
||||
f.ctx = ctx
|
||||
|
||||
go func() {
|
||||
if f.frontendOptions.OnStartup != nil {
|
||||
f.frontendOptions.OnStartup(f.ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
if f.frontendOptions.SingleInstanceLock != nil {
|
||||
SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId)
|
||||
}
|
||||
|
||||
f.mainWindow.Run(f.startURL.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowCenter() {
|
||||
f.mainWindow.Center()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetAlwaysOnTop(b bool) {
|
||||
f.mainWindow.SetKeepAbove(b)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetPosition(x, y int) {
|
||||
f.mainWindow.SetPosition(x, y)
|
||||
}
|
||||
func (f *Frontend) WindowGetPosition() (int, int) {
|
||||
return f.mainWindow.GetPosition()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetSize(width, height int) {
|
||||
f.mainWindow.SetSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowGetSize() (int, int) {
|
||||
return f.mainWindow.Size()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetTitle(title string) {
|
||||
f.mainWindow.SetTitle(title)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowFullscreen() {
|
||||
if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
|
||||
f.ExecJS("window.wails.flags.enableResize = false;")
|
||||
}
|
||||
f.mainWindow.Fullscreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowUnfullscreen() {
|
||||
if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
|
||||
f.ExecJS("window.wails.flags.enableResize = true;")
|
||||
}
|
||||
f.mainWindow.UnFullscreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReloadApp() {
|
||||
f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowShow() {
|
||||
f.mainWindow.Show()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowHide() {
|
||||
f.mainWindow.Hide()
|
||||
}
|
||||
|
||||
func (f *Frontend) Show() {
|
||||
f.mainWindow.Show()
|
||||
}
|
||||
|
||||
func (f *Frontend) Hide() {
|
||||
f.mainWindow.Hide()
|
||||
}
|
||||
func (f *Frontend) WindowMaximise() {
|
||||
f.mainWindow.Maximise()
|
||||
}
|
||||
func (f *Frontend) WindowToggleMaximise() {
|
||||
f.mainWindow.ToggleMaximise()
|
||||
}
|
||||
func (f *Frontend) WindowUnmaximise() {
|
||||
f.mainWindow.UnMaximise()
|
||||
}
|
||||
func (f *Frontend) WindowMinimise() {
|
||||
f.mainWindow.Minimise()
|
||||
}
|
||||
func (f *Frontend) WindowUnminimise() {
|
||||
f.mainWindow.UnMinimise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetMinSize(width int, height int) {
|
||||
f.mainWindow.SetMinSize(width, height)
|
||||
}
|
||||
func (f *Frontend) WindowSetMaxSize(width int, height int) {
|
||||
f.mainWindow.SetMaxSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
|
||||
if col == nil {
|
||||
return
|
||||
}
|
||||
f.mainWindow.SetBackgroundColour(col.R, col.G, col.B, col.A)
|
||||
}
|
||||
|
||||
func (f *Frontend) ScreenGetAll() ([]Screen, error) {
|
||||
return GetAllScreens(f.mainWindow.asGTKWindow())
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsMaximised() bool {
|
||||
return f.mainWindow.IsMaximised()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsMinimised() bool {
|
||||
return f.mainWindow.IsMinimised()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsNormal() bool {
|
||||
return f.mainWindow.IsNormal()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsFullscreen() bool {
|
||||
return f.mainWindow.IsFullScreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) Quit() {
|
||||
if f.frontendOptions.OnBeforeClose != nil {
|
||||
go func() {
|
||||
if !f.frontendOptions.OnBeforeClose(f.ctx) {
|
||||
f.mainWindow.Quit()
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
f.mainWindow.Quit()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowPrint() {
|
||||
f.ExecJS("window.print();")
|
||||
}
|
||||
|
||||
type EventNotify struct {
|
||||
Name string `json:"name"`
|
||||
Data []interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func (f *Frontend) Notify(name string, data ...interface{}) {
|
||||
notification := EventNotify{
|
||||
Name: name,
|
||||
Data: data,
|
||||
}
|
||||
payload, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
f.mainWindow.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
|
||||
}
|
||||
|
||||
var edgeMap = map[string]uintptr{
|
||||
"n-resize": C.GDK_WINDOW_EDGE_NORTH,
|
||||
"ne-resize": C.GDK_WINDOW_EDGE_NORTH_EAST,
|
||||
"e-resize": C.GDK_WINDOW_EDGE_EAST,
|
||||
"se-resize": C.GDK_WINDOW_EDGE_SOUTH_EAST,
|
||||
"s-resize": C.GDK_WINDOW_EDGE_SOUTH,
|
||||
"sw-resize": C.GDK_WINDOW_EDGE_SOUTH_WEST,
|
||||
"w-resize": C.GDK_WINDOW_EDGE_WEST,
|
||||
"nw-resize": C.GDK_WINDOW_EDGE_NORTH_WEST,
|
||||
}
|
||||
|
||||
func (f *Frontend) processMessage(message string) {
|
||||
if message == "DomReady" {
|
||||
if f.frontendOptions.OnDomReady != nil {
|
||||
f.frontendOptions.OnDomReady(f.ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if message == "drag" {
|
||||
if !f.mainWindow.IsFullScreen() {
|
||||
f.startDrag()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if message == "wails:showInspector" {
|
||||
f.mainWindow.ShowInspector()
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "resize:") {
|
||||
if !f.mainWindow.IsFullScreen() {
|
||||
sl := strings.Split(message, ":")
|
||||
if len(sl) != 2 {
|
||||
f.logger.Info("Unknown message returned from dispatcher: %+v", message)
|
||||
return
|
||||
}
|
||||
edge := edgeMap[sl[1]]
|
||||
err := f.startResize(edge)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if message == "runtime:ready" {
|
||||
cmd := fmt.Sprintf(
|
||||
"window.wails.setCSSDragProperties('%s', '%s');\n"+
|
||||
"window.wails.setCSSDropProperties('%s', '%s');\n"+
|
||||
"window.wails.flags.deferDragToMouseMove = true;",
|
||||
f.frontendOptions.CSSDragProperty,
|
||||
f.frontendOptions.CSSDragValue,
|
||||
f.frontendOptions.DragAndDrop.CSSDropProperty,
|
||||
f.frontendOptions.DragAndDrop.CSSDropValue,
|
||||
)
|
||||
|
||||
f.ExecJS(cmd)
|
||||
|
||||
if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
|
||||
f.ExecJS("window.wails.flags.enableResize = true;")
|
||||
}
|
||||
|
||||
if f.frontendOptions.DragAndDrop.EnableFileDrop {
|
||||
f.ExecJS("window.wails.flags.enableWailsDragAndDrop = true;")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
result, err := f.dispatcher.ProcessMessage(message, f)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
f.Callback(result)
|
||||
return
|
||||
}
|
||||
if result == "" {
|
||||
return
|
||||
}
|
||||
|
||||
switch result[0] {
|
||||
case 'c':
|
||||
// Callback from a method call
|
||||
f.Callback(result[1:])
|
||||
default:
|
||||
f.logger.Info("Unknown message returned from dispatcher: %+v", result)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (f *Frontend) Callback(message string) {
|
||||
escaped, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`)
|
||||
}
|
||||
|
||||
func (f *Frontend) startDrag() {
|
||||
f.mainWindow.StartDrag()
|
||||
}
|
||||
|
||||
func (f *Frontend) startResize(edge uintptr) error {
|
||||
f.mainWindow.StartResize(edge)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) ExecJS(js string) {
|
||||
f.mainWindow.ExecJS(js)
|
||||
}
|
||||
|
||||
type bindingsMessage struct {
|
||||
message string
|
||||
source string
|
||||
}
|
||||
|
||||
var messageBuffer = make(chan string, 100)
|
||||
var bindingsMessageBuffer = make(chan *bindingsMessage, 100)
|
||||
|
||||
//export processMessage
|
||||
func processMessage(message *C.char) {
|
||||
goMessage := C.GoString(message)
|
||||
messageBuffer <- goMessage
|
||||
}
|
||||
|
||||
//export processBindingMessage
|
||||
func processBindingMessage(message *C.char, source *C.char) {
|
||||
goMessage := C.GoString(message)
|
||||
goSource := C.GoString(source)
|
||||
bindingsMessageBuffer <- &bindingsMessage{
|
||||
message: goMessage,
|
||||
source: goSource,
|
||||
}
|
||||
}
|
||||
|
||||
var requestBuffer = make(chan webview.Request, 100)
|
||||
|
||||
func (f *Frontend) startRequestProcessor() {
|
||||
for request := range requestBuffer {
|
||||
f.assets.ServeWebViewRequest(request)
|
||||
}
|
||||
}
|
||||
|
||||
//export processURLRequest
|
||||
func processURLRequest(request unsafe.Pointer) {
|
||||
requestBuffer <- webview.NewRequest(request)
|
||||
}
|
||||
|
||||
func (f *Frontend) startSecondInstanceProcessor() {
|
||||
for secondInstanceData := range secondInstanceBuffer {
|
||||
if f.frontendOptions.SingleInstanceLock != nil &&
|
||||
f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil {
|
||||
f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
85
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/gtk.go
generated
vendored
Normal file
85
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/gtk.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
static GtkCheckMenuItem *toGtkCheckMenuItem(void *pointer) { return (GTK_CHECK_MENU_ITEM(pointer)); }
|
||||
|
||||
extern void blockClick(GtkWidget* menuItem, gulong handler_id);
|
||||
extern void unblockClick(GtkWidget* menuItem, gulong handler_id);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
func GtkMenuItemWithLabel(label string) *C.GtkWidget {
|
||||
cLabel := C.CString(label)
|
||||
result := C.gtk_menu_item_new_with_label(cLabel)
|
||||
C.free(unsafe.Pointer(cLabel))
|
||||
return result
|
||||
}
|
||||
|
||||
func GtkCheckMenuItemWithLabel(label string) *C.GtkWidget {
|
||||
cLabel := C.CString(label)
|
||||
result := C.gtk_check_menu_item_new_with_label(cLabel)
|
||||
C.free(unsafe.Pointer(cLabel))
|
||||
return result
|
||||
}
|
||||
|
||||
func GtkRadioMenuItemWithLabel(label string, group *C.GSList) *C.GtkWidget {
|
||||
cLabel := C.CString(label)
|
||||
result := C.gtk_radio_menu_item_new_with_label(group, cLabel)
|
||||
C.free(unsafe.Pointer(cLabel))
|
||||
return result
|
||||
}
|
||||
|
||||
//export handleMenuItemClick
|
||||
func handleMenuItemClick(gtkWidget unsafe.Pointer) {
|
||||
// Make sure to execute the final callback on a new goroutine otherwise if the callback e.g. tries to open a dialog, the
|
||||
// main thread will get blocked and so the message loop blocks. As a result the app will block and shows a
|
||||
// "not responding" dialog.
|
||||
|
||||
item := gtkSignalToMenuItem[(*C.GtkWidget)(gtkWidget)]
|
||||
switch item.Type {
|
||||
case menu.CheckboxType:
|
||||
item.Checked = !item.Checked
|
||||
checked := C.int(0)
|
||||
if item.Checked {
|
||||
checked = C.int(1)
|
||||
}
|
||||
for _, gtkCheckbox := range gtkCheckboxCache[item] {
|
||||
handler := gtkSignalHandlers[gtkCheckbox]
|
||||
C.blockClick(gtkCheckbox, handler)
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkCheckbox)), checked)
|
||||
C.unblockClick(gtkCheckbox, handler)
|
||||
}
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
case menu.RadioType:
|
||||
gtkRadioItems := gtkRadioMenuCache[item]
|
||||
active := C.gtk_check_menu_item_get_active(C.toGtkCheckMenuItem(gtkWidget))
|
||||
if int(active) == 1 {
|
||||
for _, gtkRadioItem := range gtkRadioItems {
|
||||
handler := gtkSignalHandlers[gtkRadioItem]
|
||||
C.blockClick(gtkRadioItem, handler)
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkRadioItem)), 1)
|
||||
C.unblockClick(gtkRadioItem, handler)
|
||||
}
|
||||
item.Checked = true
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
} else {
|
||||
item.Checked = false
|
||||
}
|
||||
default:
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
}
|
||||
}
|
||||
78
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/invoke.go
generated
vendored
Normal file
78
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/invoke.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
|
||||
#include <stdio.h>
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
extern gboolean invokeCallbacks(void *);
|
||||
|
||||
static inline void triggerInvokesOnMainThread() {
|
||||
g_idle_add((GSourceFunc)invokeCallbacks, NULL);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
m sync.Mutex
|
||||
mainTid int
|
||||
dispatchq []func()
|
||||
)
|
||||
|
||||
func invokeOnMainThread(f func()) {
|
||||
if tryInvokeOnCurrentGoRoutine(f) {
|
||||
return
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
dispatchq = append(dispatchq, f)
|
||||
m.Unlock()
|
||||
|
||||
C.triggerInvokesOnMainThread()
|
||||
}
|
||||
|
||||
func tryInvokeOnCurrentGoRoutine(f func()) bool {
|
||||
m.Lock()
|
||||
mainThreadID := mainTid
|
||||
m.Unlock()
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if mainThreadID != unix.Gettid() {
|
||||
return false
|
||||
}
|
||||
f()
|
||||
return true
|
||||
}
|
||||
|
||||
//export invokeCallbacks
|
||||
func invokeCallbacks(_ unsafe.Pointer) C.gboolean {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
m.Lock()
|
||||
if mainTid == 0 {
|
||||
mainTid = unix.Gettid()
|
||||
}
|
||||
|
||||
q := append([]func(){}, dispatchq...)
|
||||
dispatchq = []func(){}
|
||||
m.Unlock()
|
||||
|
||||
for _, v := range q {
|
||||
v()
|
||||
}
|
||||
return C.G_SOURCE_REMOVE
|
||||
}
|
||||
110
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/keys.go
generated
vendored
Normal file
110
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/keys.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
|
||||
var namedKeysToGTK = map[string]C.guint{
|
||||
"backspace": C.guint(0xff08),
|
||||
"tab": C.guint(0xff09),
|
||||
"return": C.guint(0xff0d),
|
||||
"enter": C.guint(0xff0d),
|
||||
"escape": C.guint(0xff1b),
|
||||
"left": C.guint(0xff51),
|
||||
"right": C.guint(0xff53),
|
||||
"up": C.guint(0xff52),
|
||||
"down": C.guint(0xff54),
|
||||
"space": C.guint(0xff80),
|
||||
"delete": C.guint(0xff9f),
|
||||
"home": C.guint(0xff95),
|
||||
"end": C.guint(0xff9c),
|
||||
"page up": C.guint(0xff9a),
|
||||
"page down": C.guint(0xff9b),
|
||||
"f1": C.guint(0xffbe),
|
||||
"f2": C.guint(0xffbf),
|
||||
"f3": C.guint(0xffc0),
|
||||
"f4": C.guint(0xffc1),
|
||||
"f5": C.guint(0xffc2),
|
||||
"f6": C.guint(0xffc3),
|
||||
"f7": C.guint(0xffc4),
|
||||
"f8": C.guint(0xffc5),
|
||||
"f9": C.guint(0xffc6),
|
||||
"f10": C.guint(0xffc7),
|
||||
"f11": C.guint(0xffc8),
|
||||
"f12": C.guint(0xffc9),
|
||||
"f13": C.guint(0xffca),
|
||||
"f14": C.guint(0xffcb),
|
||||
"f15": C.guint(0xffcc),
|
||||
"f16": C.guint(0xffcd),
|
||||
"f17": C.guint(0xffce),
|
||||
"f18": C.guint(0xffcf),
|
||||
"f19": C.guint(0xffd0),
|
||||
"f20": C.guint(0xffd1),
|
||||
"f21": C.guint(0xffd2),
|
||||
"f22": C.guint(0xffd3),
|
||||
"f23": C.guint(0xffd4),
|
||||
"f24": C.guint(0xffd5),
|
||||
"f25": C.guint(0xffd6),
|
||||
"f26": C.guint(0xffd7),
|
||||
"f27": C.guint(0xffd8),
|
||||
"f28": C.guint(0xffd9),
|
||||
"f29": C.guint(0xffda),
|
||||
"f30": C.guint(0xffdb),
|
||||
"f31": C.guint(0xffdc),
|
||||
"f32": C.guint(0xffdd),
|
||||
"f33": C.guint(0xffde),
|
||||
"f34": C.guint(0xffdf),
|
||||
"f35": C.guint(0xffe0),
|
||||
"numlock": C.guint(0xff7f),
|
||||
}
|
||||
|
||||
func acceleratorToGTK(accelerator *keys.Accelerator) (C.guint, C.GdkModifierType) {
|
||||
key := parseKey(accelerator.Key)
|
||||
mods := parseModifiers(accelerator.Modifiers)
|
||||
return key, mods
|
||||
}
|
||||
|
||||
func parseKey(key string) C.guint {
|
||||
var result C.guint
|
||||
result, found := namedKeysToGTK[key]
|
||||
if found {
|
||||
return result
|
||||
}
|
||||
// Check for unknown namedkeys
|
||||
// Check if we only have a single character
|
||||
if len(key) != 1 {
|
||||
return C.guint(0)
|
||||
}
|
||||
keyval := rune(key[0])
|
||||
return C.gdk_unicode_to_keyval(C.guint(keyval))
|
||||
}
|
||||
|
||||
func parseModifiers(modifiers []keys.Modifier) C.GdkModifierType {
|
||||
|
||||
var result C.GdkModifierType
|
||||
|
||||
for _, modifier := range modifiers {
|
||||
switch modifier {
|
||||
case keys.ShiftKey:
|
||||
result |= C.GDK_SHIFT_MASK
|
||||
case keys.ControlKey, keys.CmdOrCtrlKey:
|
||||
result |= C.GDK_CONTROL_MASK
|
||||
case keys.OptionOrAltKey:
|
||||
result |= C.GDK_MOD1_MASK
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
169
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/menu.go
generated
vendored
Normal file
169
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/menu.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
static GtkMenuItem *toGtkMenuItem(void *pointer) { return (GTK_MENU_ITEM(pointer)); }
|
||||
static GtkMenuShell *toGtkMenuShell(void *pointer) { return (GTK_MENU_SHELL(pointer)); }
|
||||
static GtkCheckMenuItem *toGtkCheckMenuItem(void *pointer) { return (GTK_CHECK_MENU_ITEM(pointer)); }
|
||||
static GtkRadioMenuItem *toGtkRadioMenuItem(void *pointer) { return (GTK_RADIO_MENU_ITEM(pointer)); }
|
||||
|
||||
extern void handleMenuItemClick(void*);
|
||||
|
||||
void blockClick(GtkWidget* menuItem, gulong handler_id) {
|
||||
g_signal_handler_block (menuItem, handler_id);
|
||||
}
|
||||
|
||||
void unblockClick(GtkWidget* menuItem, gulong handler_id) {
|
||||
g_signal_handler_unblock (menuItem, handler_id);
|
||||
}
|
||||
|
||||
gulong connectClick(GtkWidget* menuItem) {
|
||||
return g_signal_connect(menuItem, "activate", G_CALLBACK(handleMenuItemClick), (void*)menuItem);
|
||||
}
|
||||
|
||||
void addAccelerator(GtkWidget* menuItem, GtkAccelGroup* group, guint key, GdkModifierType mods) {
|
||||
gtk_widget_add_accelerator(menuItem, "activate", group, key, mods, GTK_ACCEL_VISIBLE);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
var menuIdCounter int
|
||||
var menuItemToId map[*menu.MenuItem]int
|
||||
var menuIdToItem map[int]*menu.MenuItem
|
||||
var gtkCheckboxCache map[*menu.MenuItem][]*C.GtkWidget
|
||||
var gtkMenuCache map[*menu.MenuItem]*C.GtkWidget
|
||||
var gtkRadioMenuCache map[*menu.MenuItem][]*C.GtkWidget
|
||||
var gtkSignalHandlers map[*C.GtkWidget]C.gulong
|
||||
var gtkSignalToMenuItem map[*C.GtkWidget]*menu.MenuItem
|
||||
|
||||
func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
|
||||
f.mainWindow.SetApplicationMenu(menu)
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuUpdateApplicationMenu() {
|
||||
f.mainWindow.SetApplicationMenu(f.mainWindow.applicationMenu)
|
||||
}
|
||||
|
||||
func (w *Window) SetApplicationMenu(inmenu *menu.Menu) {
|
||||
if inmenu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Setup accelerator group
|
||||
w.accels = C.gtk_accel_group_new()
|
||||
C.gtk_window_add_accel_group(w.asGTKWindow(), w.accels)
|
||||
|
||||
menuItemToId = make(map[*menu.MenuItem]int)
|
||||
menuIdToItem = make(map[int]*menu.MenuItem)
|
||||
gtkCheckboxCache = make(map[*menu.MenuItem][]*C.GtkWidget)
|
||||
gtkMenuCache = make(map[*menu.MenuItem]*C.GtkWidget)
|
||||
gtkRadioMenuCache = make(map[*menu.MenuItem][]*C.GtkWidget)
|
||||
gtkSignalHandlers = make(map[*C.GtkWidget]C.gulong)
|
||||
gtkSignalToMenuItem = make(map[*C.GtkWidget]*menu.MenuItem)
|
||||
|
||||
// Increase ref count?
|
||||
w.menubar = C.gtk_menu_bar_new()
|
||||
|
||||
processMenu(w, inmenu)
|
||||
|
||||
C.gtk_widget_show(w.menubar)
|
||||
}
|
||||
|
||||
func processMenu(window *Window, menu *menu.Menu) {
|
||||
for _, menuItem := range menu.Items {
|
||||
if menuItem.SubMenu != nil {
|
||||
submenu := processSubmenu(menuItem, window.accels)
|
||||
C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(window.menubar)), submenu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processSubmenu(menuItem *menu.MenuItem, group *C.GtkAccelGroup) *C.GtkWidget {
|
||||
existingMenu := gtkMenuCache[menuItem]
|
||||
if existingMenu != nil {
|
||||
return existingMenu
|
||||
}
|
||||
gtkMenu := C.gtk_menu_new()
|
||||
submenu := GtkMenuItemWithLabel(menuItem.Label)
|
||||
for _, menuItem := range menuItem.SubMenu.Items {
|
||||
menuID := menuIdCounter
|
||||
menuIdToItem[menuID] = menuItem
|
||||
menuItemToId[menuItem] = menuID
|
||||
menuIdCounter++
|
||||
processMenuItem(gtkMenu, menuItem, group)
|
||||
}
|
||||
C.gtk_menu_item_set_submenu(C.toGtkMenuItem(unsafe.Pointer(submenu)), gtkMenu)
|
||||
gtkMenuCache[menuItem] = existingMenu
|
||||
return submenu
|
||||
}
|
||||
|
||||
var currentRadioGroup *C.GSList
|
||||
|
||||
func processMenuItem(parent *C.GtkWidget, menuItem *menu.MenuItem, group *C.GtkAccelGroup) {
|
||||
if menuItem.Hidden {
|
||||
return
|
||||
}
|
||||
|
||||
if menuItem.Type != menu.RadioType {
|
||||
currentRadioGroup = nil
|
||||
}
|
||||
|
||||
if menuItem.Type == menu.SeparatorType {
|
||||
result := C.gtk_separator_menu_item_new()
|
||||
C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(parent)), result)
|
||||
return
|
||||
}
|
||||
|
||||
var result *C.GtkWidget
|
||||
|
||||
switch menuItem.Type {
|
||||
case menu.TextType:
|
||||
result = GtkMenuItemWithLabel(menuItem.Label)
|
||||
case menu.CheckboxType:
|
||||
result = GtkCheckMenuItemWithLabel(menuItem.Label)
|
||||
if menuItem.Checked {
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1)
|
||||
}
|
||||
gtkCheckboxCache[menuItem] = append(gtkCheckboxCache[menuItem], result)
|
||||
|
||||
case menu.RadioType:
|
||||
result = GtkRadioMenuItemWithLabel(menuItem.Label, currentRadioGroup)
|
||||
currentRadioGroup = C.gtk_radio_menu_item_get_group(C.toGtkRadioMenuItem(unsafe.Pointer(result)))
|
||||
if menuItem.Checked {
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1)
|
||||
}
|
||||
gtkRadioMenuCache[menuItem] = append(gtkRadioMenuCache[menuItem], result)
|
||||
case menu.SubmenuType:
|
||||
result = processSubmenu(menuItem, group)
|
||||
}
|
||||
C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(parent)), result)
|
||||
C.gtk_widget_show(result)
|
||||
|
||||
if menuItem.Click != nil {
|
||||
handler := C.connectClick(result)
|
||||
gtkSignalHandlers[result] = handler
|
||||
gtkSignalToMenuItem[result] = menuItem
|
||||
}
|
||||
|
||||
if menuItem.Disabled {
|
||||
C.gtk_widget_set_sensitive(result, 0)
|
||||
}
|
||||
|
||||
if menuItem.Accelerator != nil {
|
||||
key, mods := acceleratorToGTK(menuItem.Accelerator)
|
||||
C.addAccelerator(result, group, key, mods)
|
||||
}
|
||||
}
|
||||
594
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/notifications.go
generated
vendored
Normal file
594
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/notifications.go
generated
vendored
Normal file
@@ -0,0 +1,594 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
var (
|
||||
conn *dbus.Conn
|
||||
categories map[string]frontend.NotificationCategory = make(map[string]frontend.NotificationCategory)
|
||||
categoriesLock sync.RWMutex
|
||||
notifications map[uint32]*notificationData = make(map[uint32]*notificationData)
|
||||
notificationsLock sync.RWMutex
|
||||
notificationResultCallback func(result frontend.NotificationResult)
|
||||
callbackLock sync.RWMutex
|
||||
appName string
|
||||
cancel context.CancelFunc
|
||||
)
|
||||
|
||||
type notificationData struct {
|
||||
ID string
|
||||
Title string
|
||||
Subtitle string
|
||||
Body string
|
||||
CategoryID string
|
||||
Data map[string]interface{}
|
||||
DBusID uint32
|
||||
ActionMap map[string]string
|
||||
}
|
||||
|
||||
const (
|
||||
dbusNotificationInterface = "org.freedesktop.Notifications"
|
||||
dbusNotificationPath = "/org/freedesktop/Notifications"
|
||||
DefaultActionIdentifier = "DEFAULT_ACTION"
|
||||
)
|
||||
|
||||
// Creates a new Notifications Service.
|
||||
func (f *Frontend) InitializeNotifications() error {
|
||||
// Clean up any previous initialization
|
||||
f.CleanupNotifications()
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable: %w", err)
|
||||
}
|
||||
appName = filepath.Base(exe)
|
||||
|
||||
_conn, err := dbus.ConnectSessionBus()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to session bus: %w", err)
|
||||
}
|
||||
conn = _conn
|
||||
|
||||
if err := f.loadCategories(); err != nil {
|
||||
f.logger.Warning("Failed to load notification categories: %v", err)
|
||||
}
|
||||
|
||||
var signalCtx context.Context
|
||||
signalCtx, cancel = context.WithCancel(context.Background())
|
||||
|
||||
if err := f.setupSignalHandling(signalCtx); err != nil {
|
||||
return fmt.Errorf("failed to set up notification signal handling: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupNotifications cleans up notification resources
|
||||
func (f *Frontend) CleanupNotifications() {
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
cancel = nil
|
||||
}
|
||||
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) IsNotificationAvailable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RequestNotificationAuthorization is a Linux stub that always returns true, nil.
|
||||
// (authorization is macOS-specific)
|
||||
func (f *Frontend) RequestNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckNotificationAuthorization is a Linux stub that always returns true.
|
||||
// (authorization is macOS-specific)
|
||||
func (f *Frontend) CheckNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
|
||||
func (f *Frontend) SendNotification(options frontend.NotificationOptions) error {
|
||||
if conn == nil {
|
||||
return fmt.Errorf("notifications not initialized")
|
||||
}
|
||||
|
||||
hints := map[string]dbus.Variant{}
|
||||
|
||||
body := options.Body
|
||||
if options.Subtitle != "" {
|
||||
body = options.Subtitle + "\n" + body
|
||||
}
|
||||
|
||||
defaultActionID := "default"
|
||||
actions := []string{defaultActionID, "Default"}
|
||||
|
||||
actionMap := map[string]string{
|
||||
defaultActionID: DefaultActionIdentifier,
|
||||
}
|
||||
|
||||
hints["x-notification-id"] = dbus.MakeVariant(options.ID)
|
||||
|
||||
if options.Data != nil {
|
||||
userData, err := json.Marshal(options.Data)
|
||||
if err == nil {
|
||||
hints["x-user-data"] = dbus.MakeVariant(string(userData))
|
||||
}
|
||||
}
|
||||
|
||||
// Call the Notify method on the D-Bus interface
|
||||
obj := conn.Object(dbusNotificationInterface, dbusNotificationPath)
|
||||
call := obj.Call(
|
||||
dbusNotificationInterface+".Notify",
|
||||
0,
|
||||
appName,
|
||||
uint32(0),
|
||||
"", // Icon
|
||||
options.Title,
|
||||
body,
|
||||
actions,
|
||||
hints,
|
||||
int32(-1),
|
||||
)
|
||||
|
||||
if call.Err != nil {
|
||||
return fmt.Errorf("failed to send notification: %w", call.Err)
|
||||
}
|
||||
|
||||
var dbusID uint32
|
||||
if err := call.Store(&dbusID); err != nil {
|
||||
return fmt.Errorf("failed to store notification ID: %w", err)
|
||||
}
|
||||
|
||||
notification := ¬ificationData{
|
||||
ID: options.ID,
|
||||
Title: options.Title,
|
||||
Subtitle: options.Subtitle,
|
||||
Body: options.Body,
|
||||
Data: options.Data,
|
||||
DBusID: dbusID,
|
||||
ActionMap: actionMap,
|
||||
}
|
||||
|
||||
notificationsLock.Lock()
|
||||
notifications[dbusID] = notification
|
||||
notificationsLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendNotificationWithActions sends a notification with additional actions.
|
||||
func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error {
|
||||
if conn == nil {
|
||||
return fmt.Errorf("notifications not initialized")
|
||||
}
|
||||
|
||||
categoriesLock.RLock()
|
||||
category, exists := categories[options.CategoryID]
|
||||
categoriesLock.RUnlock()
|
||||
|
||||
if options.CategoryID == "" || !exists {
|
||||
// Fall back to basic notification
|
||||
return f.SendNotification(options)
|
||||
}
|
||||
|
||||
body := options.Body
|
||||
if options.Subtitle != "" {
|
||||
body = options.Subtitle + "\n" + body
|
||||
}
|
||||
|
||||
var actions []string
|
||||
actionMap := make(map[string]string)
|
||||
|
||||
defaultActionID := "default"
|
||||
actions = append(actions, defaultActionID, "Default")
|
||||
actionMap[defaultActionID] = DefaultActionIdentifier
|
||||
|
||||
for _, action := range category.Actions {
|
||||
actions = append(actions, action.ID, action.Title)
|
||||
actionMap[action.ID] = action.ID
|
||||
}
|
||||
|
||||
hints := map[string]dbus.Variant{}
|
||||
|
||||
hints["x-notification-id"] = dbus.MakeVariant(options.ID)
|
||||
|
||||
hints["x-category-id"] = dbus.MakeVariant(options.CategoryID)
|
||||
|
||||
if options.Data != nil {
|
||||
userData, err := json.Marshal(options.Data)
|
||||
if err == nil {
|
||||
hints["x-user-data"] = dbus.MakeVariant(string(userData))
|
||||
}
|
||||
}
|
||||
|
||||
obj := conn.Object(dbusNotificationInterface, dbusNotificationPath)
|
||||
call := obj.Call(
|
||||
dbusNotificationInterface+".Notify",
|
||||
0,
|
||||
appName,
|
||||
uint32(0),
|
||||
"", // Icon
|
||||
options.Title,
|
||||
body,
|
||||
actions,
|
||||
hints,
|
||||
int32(-1),
|
||||
)
|
||||
|
||||
if call.Err != nil {
|
||||
return fmt.Errorf("failed to send notification: %w", call.Err)
|
||||
}
|
||||
|
||||
var dbusID uint32
|
||||
if err := call.Store(&dbusID); err != nil {
|
||||
return fmt.Errorf("failed to store notification ID: %w", err)
|
||||
}
|
||||
|
||||
notification := ¬ificationData{
|
||||
ID: options.ID,
|
||||
Title: options.Title,
|
||||
Subtitle: options.Subtitle,
|
||||
Body: options.Body,
|
||||
CategoryID: options.CategoryID,
|
||||
Data: options.Data,
|
||||
DBusID: dbusID,
|
||||
ActionMap: actionMap,
|
||||
}
|
||||
|
||||
notificationsLock.Lock()
|
||||
notifications[dbusID] = notification
|
||||
notificationsLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error {
|
||||
categoriesLock.Lock()
|
||||
categories[category.ID] = category
|
||||
categoriesLock.Unlock()
|
||||
|
||||
if err := f.saveCategories(); err != nil {
|
||||
f.logger.Warning("Failed to save notification categories: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNotificationCategory removes a previously registered NotificationCategory.
|
||||
func (f *Frontend) RemoveNotificationCategory(categoryId string) error {
|
||||
categoriesLock.Lock()
|
||||
delete(categories, categoryId)
|
||||
categoriesLock.Unlock()
|
||||
|
||||
if err := f.saveCategories(); err != nil {
|
||||
f.logger.Warning("Failed to save notification categories: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllPendingNotifications attempts to remove all active notifications.
|
||||
func (f *Frontend) RemoveAllPendingNotifications() error {
|
||||
notificationsLock.Lock()
|
||||
dbusIDs := make([]uint32, 0, len(notifications))
|
||||
for id := range notifications {
|
||||
dbusIDs = append(dbusIDs, id)
|
||||
}
|
||||
notificationsLock.Unlock()
|
||||
|
||||
for _, id := range dbusIDs {
|
||||
f.closeNotification(id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePendingNotification removes a pending notification.
|
||||
func (f *Frontend) RemovePendingNotification(identifier string) error {
|
||||
var dbusID uint32
|
||||
found := false
|
||||
|
||||
notificationsLock.Lock()
|
||||
for id, notif := range notifications {
|
||||
if notif.ID == identifier {
|
||||
dbusID = id
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
notificationsLock.Unlock()
|
||||
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f.closeNotification(dbusID)
|
||||
}
|
||||
|
||||
// RemoveAllDeliveredNotifications functionally equivalent to RemoveAllPendingNotification on Linux.
|
||||
func (f *Frontend) RemoveAllDeliveredNotifications() error {
|
||||
return f.RemoveAllPendingNotifications()
|
||||
}
|
||||
|
||||
// RemoveDeliveredNotification functionally equivalent RemovePendingNotification on Linux.
|
||||
func (f *Frontend) RemoveDeliveredNotification(identifier string) error {
|
||||
return f.RemovePendingNotification(identifier)
|
||||
}
|
||||
|
||||
// RemoveNotification removes a notification by identifier.
|
||||
func (f *Frontend) RemoveNotification(identifier string) error {
|
||||
return f.RemovePendingNotification(identifier)
|
||||
}
|
||||
|
||||
func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) {
|
||||
callbackLock.Lock()
|
||||
defer callbackLock.Unlock()
|
||||
|
||||
notificationResultCallback = callback
|
||||
}
|
||||
|
||||
// Helper method to close a notification.
|
||||
func (f *Frontend) closeNotification(id uint32) error {
|
||||
if conn == nil {
|
||||
return fmt.Errorf("notifications not initialized")
|
||||
}
|
||||
|
||||
obj := conn.Object(dbusNotificationInterface, dbusNotificationPath)
|
||||
call := obj.Call(dbusNotificationInterface+".CloseNotification", 0, id)
|
||||
|
||||
if call.Err != nil {
|
||||
return fmt.Errorf("failed to close notification: %w", call.Err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) getConfigDir() (string, error) {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get user config directory: %w", err)
|
||||
}
|
||||
|
||||
appConfigDir := filepath.Join(configDir, appName)
|
||||
if err := os.MkdirAll(appConfigDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("failed to create app config directory: %w", err)
|
||||
}
|
||||
|
||||
return appConfigDir, nil
|
||||
}
|
||||
|
||||
// Save notification categories.
|
||||
func (f *Frontend) saveCategories() error {
|
||||
configDir, err := f.getConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
categoriesFile := filepath.Join(configDir, "notification-categories.json")
|
||||
|
||||
categoriesLock.RLock()
|
||||
categoriesData, err := json.MarshalIndent(categories, "", " ")
|
||||
categoriesLock.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification categories: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(categoriesFile, categoriesData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write notification categories to disk: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load notification categories.
|
||||
func (f *Frontend) loadCategories() error {
|
||||
configDir, err := f.getConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
categoriesFile := filepath.Join(configDir, "notification-categories.json")
|
||||
|
||||
if _, err := os.Stat(categoriesFile); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
categoriesData, err := os.ReadFile(categoriesFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read notification categories from disk: %w", err)
|
||||
}
|
||||
|
||||
_categories := make(map[string]frontend.NotificationCategory)
|
||||
if err := json.Unmarshal(categoriesData, &_categories); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal notification categories: %w", err)
|
||||
}
|
||||
|
||||
categoriesLock.Lock()
|
||||
categories = _categories
|
||||
categoriesLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setup signal handling for notification actions.
|
||||
func (f *Frontend) setupSignalHandling(ctx context.Context) error {
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchInterface(dbusNotificationInterface),
|
||||
dbus.WithMatchMember("ActionInvoked"),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchInterface(dbusNotificationInterface),
|
||||
dbus.WithMatchMember("NotificationClosed"),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c := make(chan *dbus.Signal, 10)
|
||||
conn.Signal(c)
|
||||
|
||||
go f.handleSignals(ctx, c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle incoming D-Bus signals.
|
||||
func (f *Frontend) handleSignals(ctx context.Context, c chan *dbus.Signal) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case signal, ok := <-c:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch signal.Name {
|
||||
case dbusNotificationInterface + ".ActionInvoked":
|
||||
f.handleActionInvoked(signal)
|
||||
case dbusNotificationInterface + ".NotificationClosed":
|
||||
f.handleNotificationClosed(signal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle ActionInvoked signal.
|
||||
func (f *Frontend) handleActionInvoked(signal *dbus.Signal) {
|
||||
if len(signal.Body) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
dbusID, ok := signal.Body[0].(uint32)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
actionID, ok := signal.Body[1].(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
notificationsLock.Lock()
|
||||
notification, exists := notifications[dbusID]
|
||||
if exists {
|
||||
delete(notifications, dbusID)
|
||||
}
|
||||
notificationsLock.Unlock()
|
||||
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
appActionID, ok := notification.ActionMap[actionID]
|
||||
if !ok {
|
||||
appActionID = actionID
|
||||
}
|
||||
|
||||
response := frontend.NotificationResponse{
|
||||
ID: notification.ID,
|
||||
ActionIdentifier: appActionID,
|
||||
Title: notification.Title,
|
||||
Subtitle: notification.Subtitle,
|
||||
Body: notification.Body,
|
||||
CategoryID: notification.CategoryID,
|
||||
UserInfo: notification.Data,
|
||||
}
|
||||
|
||||
result := frontend.NotificationResult{
|
||||
Response: response,
|
||||
}
|
||||
|
||||
handleNotificationResult(result)
|
||||
}
|
||||
|
||||
func handleNotificationResult(result frontend.NotificationResult) {
|
||||
callbackLock.Lock()
|
||||
callback := notificationResultCallback
|
||||
callbackLock.Unlock()
|
||||
|
||||
if callback != nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Log panic but don't crash the app
|
||||
fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
|
||||
}
|
||||
}()
|
||||
callback(result)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle NotificationClosed signal.
|
||||
// Reason codes:
|
||||
// 1 - expired timeout
|
||||
// 2 - dismissed by user (click on X)
|
||||
// 3 - closed by CloseNotification call
|
||||
// 4 - undefined/reserved
|
||||
func (f *Frontend) handleNotificationClosed(signal *dbus.Signal) {
|
||||
if len(signal.Body) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
dbusID, ok := signal.Body[0].(uint32)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
reason, ok := signal.Body[1].(uint32)
|
||||
if !ok {
|
||||
reason = 0 // Unknown reason
|
||||
}
|
||||
|
||||
notificationsLock.Lock()
|
||||
notification, exists := notifications[dbusID]
|
||||
if exists {
|
||||
delete(notifications, dbusID)
|
||||
}
|
||||
notificationsLock.Unlock()
|
||||
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
if reason == 2 {
|
||||
response := frontend.NotificationResponse{
|
||||
ID: notification.ID,
|
||||
ActionIdentifier: DefaultActionIdentifier,
|
||||
Title: notification.Title,
|
||||
Subtitle: notification.Subtitle,
|
||||
Body: notification.Body,
|
||||
CategoryID: notification.CategoryID,
|
||||
UserInfo: notification.Data,
|
||||
}
|
||||
|
||||
result := frontend.NotificationResult{
|
||||
Response: response,
|
||||
}
|
||||
|
||||
handleNotificationResult(result)
|
||||
}
|
||||
}
|
||||
91
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/screen.go
generated
vendored
Normal file
91
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/screen.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#cgo CFLAGS: -w
|
||||
#include <stdio.h>
|
||||
#include "webkit2/webkit2.h"
|
||||
#include "gtk/gtk.h"
|
||||
#include "gdk/gdk.h"
|
||||
|
||||
typedef struct Screen {
|
||||
int isCurrent;
|
||||
int isPrimary;
|
||||
int height;
|
||||
int width;
|
||||
int scale;
|
||||
} Screen;
|
||||
|
||||
int GetNMonitors(GtkWindow *window){
|
||||
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||
GdkDisplay *display = gdk_window_get_display(gdk_window);
|
||||
return gdk_display_get_n_monitors(display);
|
||||
}
|
||||
|
||||
Screen GetNThMonitor(int monitor_num, GtkWindow *window){
|
||||
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||
GdkDisplay *display = gdk_window_get_display(gdk_window);
|
||||
GdkMonitor *monitor = gdk_display_get_monitor(display,monitor_num);
|
||||
GdkMonitor *currentMonitor = gdk_display_get_monitor_at_window(display,gdk_window);
|
||||
Screen screen;
|
||||
GdkRectangle geometry;
|
||||
gdk_monitor_get_geometry(monitor,&geometry);
|
||||
screen.isCurrent = currentMonitor==monitor;
|
||||
screen.isPrimary = gdk_monitor_is_primary(monitor);
|
||||
screen.height = geometry.height;
|
||||
screen.width = geometry.width;
|
||||
screen.scale = gdk_monitor_get_scale_factor(monitor);
|
||||
return screen;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
type Screen = frontend.Screen
|
||||
|
||||
func GetAllScreens(window *C.GtkWindow) ([]Screen, error) {
|
||||
if window == nil {
|
||||
return nil, errors.New("window is nil, cannot perform screen operations")
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
var screens []Screen
|
||||
wg.Add(1)
|
||||
invokeOnMainThread(func() {
|
||||
numMonitors := C.GetNMonitors(window)
|
||||
for i := 0; i < int(numMonitors); i++ {
|
||||
cMonitor := C.GetNThMonitor(C.int(i), window)
|
||||
|
||||
screen := Screen{
|
||||
IsCurrent: cMonitor.isCurrent == 1,
|
||||
IsPrimary: cMonitor.isPrimary == 1,
|
||||
Width: int(cMonitor.width),
|
||||
Height: int(cMonitor.height),
|
||||
|
||||
Size: frontend.ScreenSize{
|
||||
Width: int(cMonitor.width),
|
||||
Height: int(cMonitor.height),
|
||||
},
|
||||
PhysicalSize: frontend.ScreenSize{
|
||||
Width: int(cMonitor.width * cMonitor.scale),
|
||||
Height: int(cMonitor.height * cMonitor.scale),
|
||||
},
|
||||
}
|
||||
screens = append(screens, screen)
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return screens, nil
|
||||
}
|
||||
77
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/single_instance.go
generated
vendored
Normal file
77
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/single_instance.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dbusHandler func(string)
|
||||
|
||||
func (f dbusHandler) SendMessage(message string) *dbus.Error {
|
||||
f(message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetupSingleInstance(uniqueID string) {
|
||||
id := "wails_app_" + strings.ReplaceAll(strings.ReplaceAll(uniqueID, "-", "_"), ".", "_")
|
||||
|
||||
dbusName := "org." + id + ".SingleInstance"
|
||||
dbusPath := "/org/" + id + "/SingleInstance"
|
||||
|
||||
conn, err := dbus.ConnectSessionBus()
|
||||
// if we will reach any error during establishing connection or sending message we will just continue.
|
||||
// It should not be the case that such thing will happen actually, but just in case.
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f := dbusHandler(func(message string) {
|
||||
var secondInstanceData options.SecondInstanceData
|
||||
|
||||
err := json.Unmarshal([]byte(message), &secondInstanceData)
|
||||
if err == nil {
|
||||
secondInstanceBuffer <- secondInstanceData
|
||||
}
|
||||
})
|
||||
|
||||
err = conn.Export(f, dbus.ObjectPath(dbusPath), dbusName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reply, err := conn.RequestName(dbusName, dbus.NameFlagDoNotQueue)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if name already taken, try to send args to existing instance, if no success just launch new instance
|
||||
if reply == dbus.RequestNameReplyExists {
|
||||
data := options.SecondInstanceData{
|
||||
Args: os.Args[1:],
|
||||
}
|
||||
data.WorkingDirectory, err = os.Getwd()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get working directory: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Printf("Failed to marshal data: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = conn.Object(dbusName, dbus.ObjectPath(dbusPath)).Call(dbusName+".SendMessage", 0, string(serialized)).Store()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
32
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/webkit2.go
generated
vendored
Normal file
32
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/webkit2.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
//go:build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
#include "webkit2/webkit2.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
)
|
||||
|
||||
func validateWebKit2Version(options *options.App) {
|
||||
if C.webkit_get_major_version() == 2 && C.webkit_get_minor_version() >= webview.Webkit2MinMinorVersion {
|
||||
return
|
||||
}
|
||||
|
||||
msg := linux.DefaultMessages()
|
||||
if options.Linux != nil && options.Linux.Messages != nil {
|
||||
msg = options.Linux.Messages
|
||||
}
|
||||
|
||||
v := fmt.Sprintf("2.%d.0", webview.Webkit2MinMinorVersion)
|
||||
showModalDialogAndExit("WebKit2GTK", fmt.Sprintf(msg.WebKit2GTKMinRequired, v))
|
||||
}
|
||||
891
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.c
generated
vendored
Normal file
891
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.c
generated
vendored
Normal file
@@ -0,0 +1,891 @@
|
||||
#include <JavaScriptCore/JavaScript.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <locale.h>
|
||||
#include "window.h"
|
||||
|
||||
// These are the x,y,time & button of the last mouse down event
|
||||
// It's used for window dragging
|
||||
static float xroot = 0.0f;
|
||||
static float yroot = 0.0f;
|
||||
static int dragTime = -1;
|
||||
static uint mouseButton = 0;
|
||||
static int wmIsWayland = -1;
|
||||
static int decoratorWidth = -1;
|
||||
static int decoratorHeight = -1;
|
||||
|
||||
// casts
|
||||
void ExecuteOnMainThread(void *f, gpointer jscallback)
|
||||
{
|
||||
g_idle_add((GSourceFunc)f, (gpointer)jscallback);
|
||||
}
|
||||
|
||||
GtkWidget *GTKWIDGET(void *pointer)
|
||||
{
|
||||
return GTK_WIDGET(pointer);
|
||||
}
|
||||
|
||||
GtkWindow *GTKWINDOW(void *pointer)
|
||||
{
|
||||
return GTK_WINDOW(pointer);
|
||||
}
|
||||
|
||||
GtkContainer *GTKCONTAINER(void *pointer)
|
||||
{
|
||||
return GTK_CONTAINER(pointer);
|
||||
}
|
||||
|
||||
GtkBox *GTKBOX(void *pointer)
|
||||
{
|
||||
return GTK_BOX(pointer);
|
||||
}
|
||||
|
||||
extern void processMessage(char *);
|
||||
extern void processBindingMessage(char *, char *);
|
||||
|
||||
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
|
||||
WebKitJavascriptResult *result,
|
||||
void *data)
|
||||
{
|
||||
// Retrieve webview from content manager
|
||||
WebKitWebView *webview = WEBKIT_WEB_VIEW(g_object_get_data(G_OBJECT(contentManager), "webview"));
|
||||
const char *current_uri = webview ? webkit_web_view_get_uri(webview) : NULL;
|
||||
char *uri = current_uri ? g_strdup(current_uri) : NULL;
|
||||
|
||||
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
|
||||
JSCValue *value = webkit_javascript_result_get_js_value(result);
|
||||
char *message = jsc_value_to_string(value);
|
||||
#else
|
||||
JSGlobalContextRef context = webkit_javascript_result_get_global_context(result);
|
||||
JSValueRef value = webkit_javascript_result_get_value(result);
|
||||
JSStringRef js = JSValueToStringCopy(context, value, NULL);
|
||||
size_t messageSize = JSStringGetMaximumUTF8CStringSize(js);
|
||||
char *message = g_new(char, messageSize);
|
||||
JSStringGetUTF8CString(js, message, messageSize);
|
||||
JSStringRelease(js);
|
||||
#endif
|
||||
processBindingMessage(message, uri);
|
||||
g_free(message);
|
||||
if (uri) {
|
||||
g_free(uri);
|
||||
}
|
||||
}
|
||||
|
||||
static bool isNULLRectangle(GdkRectangle input)
|
||||
{
|
||||
return input.x == -1 && input.y == -1 && input.width == -1 && input.height == -1;
|
||||
}
|
||||
|
||||
static gboolean onWayland()
|
||||
{
|
||||
switch (wmIsWayland)
|
||||
{
|
||||
case -1:
|
||||
{
|
||||
char *gdkBackend = getenv("XDG_SESSION_TYPE");
|
||||
if(gdkBackend != NULL && strcmp(gdkBackend, "wayland") == 0)
|
||||
{
|
||||
wmIsWayland = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
wmIsWayland = 0;
|
||||
return FALSE;
|
||||
}
|
||||
case 1:
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static GdkMonitor *getCurrentMonitor(GtkWindow *window)
|
||||
{
|
||||
// Get the monitor that the window is currently on
|
||||
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
|
||||
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||
if (gdk_window == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
GdkMonitor *monitor = gdk_display_get_monitor_at_window(display, gdk_window);
|
||||
|
||||
return GDK_MONITOR(monitor);
|
||||
}
|
||||
|
||||
static GdkRectangle getCurrentMonitorGeometry(GtkWindow *window)
|
||||
{
|
||||
GdkMonitor *monitor = getCurrentMonitor(window);
|
||||
GdkRectangle result;
|
||||
if (monitor == NULL)
|
||||
{
|
||||
result.x = result.y = result.height = result.width = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get the geometry of the monitor
|
||||
gdk_monitor_get_geometry(monitor, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int getCurrentMonitorScaleFactor(GtkWindow *window)
|
||||
{
|
||||
GdkMonitor *monitor = getCurrentMonitor(window);
|
||||
|
||||
return gdk_monitor_get_scale_factor(monitor);
|
||||
}
|
||||
|
||||
// window
|
||||
|
||||
ulong SetupInvokeSignal(void *contentManager)
|
||||
{
|
||||
return g_signal_connect((WebKitUserContentManager *)contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), NULL);
|
||||
}
|
||||
|
||||
void SetWindowIcon(GtkWindow *window, const guchar *buf, gsize len)
|
||||
{
|
||||
GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
|
||||
if (!loader)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (gdk_pixbuf_loader_write(loader, buf, len, NULL) && gdk_pixbuf_loader_close(loader, NULL))
|
||||
{
|
||||
GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
|
||||
if (pixbuf)
|
||||
{
|
||||
gtk_window_set_icon(window, pixbuf);
|
||||
}
|
||||
}
|
||||
g_object_unref(loader);
|
||||
}
|
||||
|
||||
void SetWindowTransparency(GtkWidget *widget)
|
||||
{
|
||||
GdkScreen *screen = gtk_widget_get_screen(widget);
|
||||
GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
|
||||
|
||||
if (visual != NULL && gdk_screen_is_composited(screen))
|
||||
{
|
||||
gtk_widget_set_app_paintable(widget, true);
|
||||
gtk_widget_set_visual(widget, visual);
|
||||
}
|
||||
}
|
||||
|
||||
static GtkCssProvider *windowCssProvider = NULL;
|
||||
|
||||
void SetBackgroundColour(void *data)
|
||||
{
|
||||
// set webview's background color
|
||||
RGBAOptions *options = (RGBAOptions *)data;
|
||||
|
||||
GdkRGBA colour = {options->r / 255.0, options->g / 255.0, options->b / 255.0, options->a / 255.0};
|
||||
if (options->windowIsTranslucent != NULL && options->windowIsTranslucent == TRUE)
|
||||
{
|
||||
colour.alpha = 0.0;
|
||||
}
|
||||
webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(options->webview), &colour);
|
||||
|
||||
// set window's background color
|
||||
// Get the name of the current locale
|
||||
char *old_locale, *saved_locale;
|
||||
old_locale = setlocale(LC_ALL, NULL);
|
||||
|
||||
// Copy the name so it won’t be clobbered by setlocale.
|
||||
saved_locale = strdup(old_locale);
|
||||
if (saved_locale == NULL)
|
||||
return;
|
||||
|
||||
//Now change the locale to english for so printf always converts floats with a dot decimal separator
|
||||
setlocale(LC_ALL, "en_US.UTF-8");
|
||||
gchar *str = g_strdup_printf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", options->r, options->g, options->b, options->a / 255.0);
|
||||
|
||||
//Restore the original locale.
|
||||
setlocale(LC_ALL, saved_locale);
|
||||
free(saved_locale);
|
||||
|
||||
if (windowCssProvider == NULL)
|
||||
{
|
||||
windowCssProvider = gtk_css_provider_new();
|
||||
gtk_style_context_add_provider(
|
||||
gtk_widget_get_style_context(GTK_WIDGET(options->webviewBox)),
|
||||
GTK_STYLE_PROVIDER(windowCssProvider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
g_object_unref(windowCssProvider);
|
||||
}
|
||||
|
||||
gtk_css_provider_load_from_data(windowCssProvider, str, -1, NULL);
|
||||
g_free(str);
|
||||
}
|
||||
|
||||
static gboolean setTitle(gpointer data)
|
||||
{
|
||||
SetTitleArgs *args = (SetTitleArgs *)data;
|
||||
gtk_window_set_title(args->window, args->title);
|
||||
free((void *)args->title);
|
||||
free((void *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void SetTitle(GtkWindow *window, char *title)
|
||||
{
|
||||
SetTitleArgs *args = malloc(sizeof(SetTitleArgs));
|
||||
args->window = window;
|
||||
args->title = title;
|
||||
ExecuteOnMainThread(setTitle, (gpointer)args);
|
||||
}
|
||||
|
||||
static gboolean setPosition(gpointer data)
|
||||
{
|
||||
SetPositionArgs *args = (SetPositionArgs *)data;
|
||||
gtk_window_move((GtkWindow *)args->window, args->x, args->y);
|
||||
free(args);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void SetPosition(void *window, int x, int y)
|
||||
{
|
||||
GdkRectangle monitorDimensions = getCurrentMonitorGeometry(window);
|
||||
if (isNULLRectangle(monitorDimensions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
SetPositionArgs *args = malloc(sizeof(SetPositionArgs));
|
||||
args->window = window;
|
||||
args->x = monitorDimensions.x + x;
|
||||
args->y = monitorDimensions.y + y;
|
||||
ExecuteOnMainThread(setPosition, (gpointer)args);
|
||||
}
|
||||
|
||||
void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_width, int max_height)
|
||||
{
|
||||
GdkGeometry size;
|
||||
size.min_width = size.min_height = size.max_width = size.max_height = 0;
|
||||
|
||||
GdkRectangle monitorSize = getCurrentMonitorGeometry(window);
|
||||
if (isNULLRectangle(monitorSize))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int flags = GDK_HINT_MAX_SIZE | GDK_HINT_MIN_SIZE;
|
||||
|
||||
size.max_height = (max_height == 0 ? monitorSize.height : max_height);
|
||||
size.max_width = (max_width == 0 ? monitorSize.width : max_width);
|
||||
size.min_height = min_height;
|
||||
size.min_width = min_width;
|
||||
|
||||
// On Wayland window manager get the decorators and calculate the differences from the windows' size.
|
||||
if(onWayland())
|
||||
{
|
||||
if(decoratorWidth == -1 && decoratorHeight == -1)
|
||||
{
|
||||
int windowWidth, windowHeight;
|
||||
gtk_window_get_size(window, &windowWidth, &windowHeight);
|
||||
|
||||
GtkAllocation windowAllocation;
|
||||
gtk_widget_get_allocation(GTK_WIDGET(window), &windowAllocation);
|
||||
|
||||
decoratorWidth = (windowAllocation.width-windowWidth);
|
||||
decoratorHeight = (windowAllocation.height-windowHeight);
|
||||
}
|
||||
|
||||
// Add the decorator difference to the window so fullscreen and maximise can fill the window.
|
||||
size.max_height = decoratorHeight+size.max_height;
|
||||
size.max_width = decoratorWidth+size.max_width;
|
||||
}
|
||||
|
||||
gtk_window_set_geometry_hints(window, NULL, &size, flags);
|
||||
}
|
||||
|
||||
// function to disable the context menu but propagate the event
|
||||
static gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context_menu, GdkEvent *event, WebKitHitTestResult *hit_test_result, gpointer data)
|
||||
{
|
||||
// return true to disable the context menu
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void DisableContextMenu(void *webview)
|
||||
{
|
||||
// Disable the context menu but propagate the event
|
||||
g_signal_connect(WEBKIT_WEB_VIEW(webview), "context-menu", G_CALLBACK(disableContextMenu), NULL);
|
||||
}
|
||||
|
||||
static gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, void *dummy)
|
||||
{
|
||||
if (event == NULL)
|
||||
{
|
||||
xroot = yroot = 0.0f;
|
||||
dragTime = -1;
|
||||
return FALSE;
|
||||
}
|
||||
mouseButton = event->button;
|
||||
if (event->button == 3)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == 1)
|
||||
{
|
||||
xroot = event->x_root;
|
||||
yroot = event->y_root;
|
||||
dragTime = event->time;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, void *dummy)
|
||||
{
|
||||
if (event == NULL || (event->type == GDK_BUTTON_RELEASE && event->button == 1))
|
||||
{
|
||||
xroot = yroot = 0.0f;
|
||||
dragTime = -1;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void ConnectButtons(void *webview)
|
||||
{
|
||||
g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-press-event", G_CALLBACK(buttonPress), NULL);
|
||||
g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-release-event", G_CALLBACK(buttonRelease), NULL);
|
||||
}
|
||||
|
||||
int IsFullscreen(GtkWidget *widget)
|
||||
{
|
||||
GdkWindow *gdkwindow = gtk_widget_get_window(widget);
|
||||
GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
|
||||
return state & GDK_WINDOW_STATE_FULLSCREEN;
|
||||
}
|
||||
|
||||
int IsMaximised(GtkWidget *widget)
|
||||
{
|
||||
GdkWindow *gdkwindow = gtk_widget_get_window(widget);
|
||||
GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
|
||||
return state & GDK_WINDOW_STATE_MAXIMIZED && !(state & GDK_WINDOW_STATE_FULLSCREEN);
|
||||
}
|
||||
|
||||
int IsMinimised(GtkWidget *widget)
|
||||
{
|
||||
GdkWindow *gdkwindow = gtk_widget_get_window(widget);
|
||||
GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
|
||||
return state & GDK_WINDOW_STATE_ICONIFIED;
|
||||
}
|
||||
|
||||
gboolean Center(gpointer data)
|
||||
{
|
||||
GtkWindow *window = (GtkWindow *)data;
|
||||
|
||||
// Get the geometry of the monitor
|
||||
GdkRectangle m = getCurrentMonitorGeometry(window);
|
||||
if (isNULLRectangle(m))
|
||||
{
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// Get the window width/height
|
||||
int windowWidth, windowHeight;
|
||||
gtk_window_get_size(window, &windowWidth, &windowHeight);
|
||||
|
||||
int newX = ((m.width - windowWidth) / 2) + m.x;
|
||||
int newY = ((m.height - windowHeight) / 2) + m.y;
|
||||
|
||||
// Place the window at the center of the monitor
|
||||
gtk_window_move(window, newX, newY);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Show(gpointer data)
|
||||
{
|
||||
gtk_widget_show((GtkWidget *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Hide(gpointer data)
|
||||
{
|
||||
gtk_widget_hide((GtkWidget *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Maximise(gpointer data)
|
||||
{
|
||||
gtk_window_maximize((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean UnMaximise(gpointer data)
|
||||
{
|
||||
gtk_window_unmaximize((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Minimise(gpointer data)
|
||||
{
|
||||
gtk_window_iconify((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean UnMinimise(gpointer data)
|
||||
{
|
||||
gtk_window_present((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Fullscreen(gpointer data)
|
||||
{
|
||||
GtkWindow *window = (GtkWindow *)data;
|
||||
|
||||
// Get the geometry of the monitor.
|
||||
GdkRectangle m = getCurrentMonitorGeometry(window);
|
||||
if (isNULLRectangle(m))
|
||||
{
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
int scale = getCurrentMonitorScaleFactor(window);
|
||||
SetMinMaxSize(window, 0, 0, m.width * scale, m.height * scale);
|
||||
|
||||
gtk_window_fullscreen(window);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean UnFullscreen(gpointer data)
|
||||
{
|
||||
gtk_window_unfullscreen((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void webviewLoadChanged(WebKitWebView *web_view, WebKitLoadEvent load_event, gpointer data)
|
||||
{
|
||||
if (load_event == WEBKIT_LOAD_FINISHED)
|
||||
{
|
||||
processMessage("DomReady");
|
||||
}
|
||||
}
|
||||
|
||||
extern void processURLRequest(void *request);
|
||||
|
||||
// This is called when the close button on the window is pressed
|
||||
gboolean close_button_pressed(GtkWidget *widget, GdkEvent *event, void *data)
|
||||
{
|
||||
processMessage("Q");
|
||||
// since we handle the close in processMessage tell GTK to not invoke additional handlers - see:
|
||||
// https://docs.gtk.org/gtk3/signal.Widget.delete-event.html
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
char *droppedFiles = NULL;
|
||||
|
||||
static void onDragDataReceived(GtkWidget *self, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint target_type, guint time, gpointer data)
|
||||
{
|
||||
if(selection_data == NULL || (gtk_selection_data_get_length(selection_data) <= 0) || target_type != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(droppedFiles != NULL) {
|
||||
free(droppedFiles);
|
||||
droppedFiles = NULL;
|
||||
}
|
||||
|
||||
gchar **filenames = NULL;
|
||||
filenames = g_uri_list_extract_uris((const gchar *)gtk_selection_data_get_data(selection_data));
|
||||
if (filenames == NULL) // If unable to retrieve filenames:
|
||||
{
|
||||
g_strfreev(filenames);
|
||||
return;
|
||||
}
|
||||
|
||||
droppedFiles = calloc((size_t)gtk_selection_data_get_length(selection_data), 1);
|
||||
|
||||
int iter = 0;
|
||||
while(filenames[iter] != NULL) // The last URI list element is NULL.
|
||||
{
|
||||
if(iter != 0)
|
||||
{
|
||||
strncat(droppedFiles, "\n", 1);
|
||||
}
|
||||
char *filename = g_filename_from_uri(filenames[iter], NULL, NULL);
|
||||
if (filename == NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
strncat(droppedFiles, filename, strlen(filename));
|
||||
|
||||
free(filename);
|
||||
iter++;
|
||||
}
|
||||
|
||||
g_strfreev(filenames);
|
||||
}
|
||||
|
||||
static gboolean onDragDrop(GtkWidget* self, GdkDragContext* context, gint x, gint y, guint time, gpointer user_data)
|
||||
{
|
||||
if(droppedFiles == NULL)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
size_t resLen = strlen(droppedFiles)+(sizeof(gint)*2)+6;
|
||||
char *res = calloc(resLen, 1);
|
||||
|
||||
snprintf(res, resLen, "DD:%d:%d:%s", x, y, droppedFiles);
|
||||
|
||||
if(droppedFiles != NULL) {
|
||||
free(droppedFiles);
|
||||
droppedFiles = NULL;
|
||||
}
|
||||
|
||||
processMessage(res);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// WebView
|
||||
GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy, int disableWebViewDragAndDrop, int enableDragAndDrop)
|
||||
{
|
||||
GtkWidget *webview = webkit_web_view_new_with_user_content_manager((WebKitUserContentManager *)contentManager);
|
||||
|
||||
// Store webview reference in the content manager
|
||||
g_object_set_data(G_OBJECT((WebKitUserContentManager *)contentManager), "webview", webview);
|
||||
// gtk_container_add(GTK_CONTAINER(window), webview);
|
||||
WebKitWebContext *context = webkit_web_context_get_default();
|
||||
webkit_web_context_register_uri_scheme(context, "wails", (WebKitURISchemeRequestCallback)processURLRequest, NULL, NULL);
|
||||
g_signal_connect(G_OBJECT(webview), "load-changed", G_CALLBACK(webviewLoadChanged), NULL);
|
||||
|
||||
if(disableWebViewDragAndDrop)
|
||||
{
|
||||
gtk_drag_dest_unset(webview);
|
||||
}
|
||||
|
||||
if(enableDragAndDrop)
|
||||
{
|
||||
g_signal_connect(G_OBJECT(webview), "drag-data-received", G_CALLBACK(onDragDataReceived), NULL);
|
||||
g_signal_connect(G_OBJECT(webview), "drag-drop", G_CALLBACK(onDragDrop), NULL);
|
||||
}
|
||||
|
||||
if (hideWindowOnClose)
|
||||
{
|
||||
g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(close_button_pressed), NULL);
|
||||
}
|
||||
|
||||
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
|
||||
webkit_settings_set_user_agent_with_application_details(settings, "wails.io", "");
|
||||
|
||||
switch (gpuPolicy)
|
||||
{
|
||||
case 0:
|
||||
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS);
|
||||
break;
|
||||
case 1:
|
||||
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND);
|
||||
break;
|
||||
case 2:
|
||||
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
|
||||
break;
|
||||
default:
|
||||
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND);
|
||||
}
|
||||
return webview;
|
||||
}
|
||||
|
||||
void DevtoolsEnabled(void *webview, int enabled, bool showInspector)
|
||||
{
|
||||
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
|
||||
gboolean genabled = enabled == 1 ? true : false;
|
||||
webkit_settings_set_enable_developer_extras(settings, genabled);
|
||||
|
||||
if (genabled && showInspector)
|
||||
{
|
||||
ShowInspector(webview);
|
||||
}
|
||||
}
|
||||
|
||||
void LoadIndex(void *webview, char *url)
|
||||
{
|
||||
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url);
|
||||
}
|
||||
|
||||
static gboolean startDrag(gpointer data)
|
||||
{
|
||||
DragOptions *options = (DragOptions *)data;
|
||||
|
||||
// Ignore non-toplevel widgets
|
||||
GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(options->webview));
|
||||
if (!GTK_IS_WINDOW(window))
|
||||
{
|
||||
free(data);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gtk_window_begin_move_drag(options->mainwindow, mouseButton, xroot, yroot, dragTime);
|
||||
free(data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void StartDrag(void *webview, GtkWindow *mainwindow)
|
||||
{
|
||||
DragOptions *data = malloc(sizeof(DragOptions));
|
||||
data->webview = webview;
|
||||
data->mainwindow = mainwindow;
|
||||
ExecuteOnMainThread(startDrag, (gpointer)data);
|
||||
}
|
||||
|
||||
static gboolean startResize(gpointer data)
|
||||
{
|
||||
ResizeOptions *options = (ResizeOptions *)data;
|
||||
|
||||
// Ignore non-toplevel widgets
|
||||
GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(options->webview));
|
||||
if (!GTK_IS_WINDOW(window))
|
||||
{
|
||||
free(data);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gtk_window_begin_resize_drag(options->mainwindow, options->edge, mouseButton, xroot, yroot, dragTime);
|
||||
free(data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void StartResize(void *webview, GtkWindow *mainwindow, GdkWindowEdge edge)
|
||||
{
|
||||
ResizeOptions *data = malloc(sizeof(ResizeOptions));
|
||||
data->webview = webview;
|
||||
data->mainwindow = mainwindow;
|
||||
data->edge = edge;
|
||||
ExecuteOnMainThread(startResize, (gpointer)data);
|
||||
}
|
||||
|
||||
void ExecuteJS(void *data)
|
||||
{
|
||||
struct JSCallback *js = data;
|
||||
webkit_web_view_run_javascript(js->webview, js->script, NULL, NULL, NULL);
|
||||
free(js->script);
|
||||
}
|
||||
|
||||
void extern processMessageDialogResult(char *);
|
||||
|
||||
void MessageDialog(void *data)
|
||||
{
|
||||
GtkDialogFlags flags;
|
||||
GtkMessageType messageType;
|
||||
MessageDialogOptions *options = (MessageDialogOptions *)data;
|
||||
if (options->messageType == 0)
|
||||
{
|
||||
messageType = GTK_MESSAGE_INFO;
|
||||
flags = GTK_BUTTONS_OK;
|
||||
}
|
||||
else if (options->messageType == 1)
|
||||
{
|
||||
messageType = GTK_MESSAGE_ERROR;
|
||||
flags = GTK_BUTTONS_OK;
|
||||
}
|
||||
else if (options->messageType == 2)
|
||||
{
|
||||
messageType = GTK_MESSAGE_QUESTION;
|
||||
flags = GTK_BUTTONS_YES_NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
messageType = GTK_MESSAGE_WARNING;
|
||||
flags = GTK_BUTTONS_OK;
|
||||
}
|
||||
|
||||
GtkWidget *dialog;
|
||||
dialog = gtk_message_dialog_new(GTK_WINDOW(options->window),
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
messageType,
|
||||
flags,
|
||||
options->message, NULL);
|
||||
gtk_window_set_title(GTK_WINDOW(dialog), options->title);
|
||||
GtkResponseType result = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
if (result == GTK_RESPONSE_YES)
|
||||
{
|
||||
processMessageDialogResult("Yes");
|
||||
}
|
||||
else if (result == GTK_RESPONSE_NO)
|
||||
{
|
||||
processMessageDialogResult("No");
|
||||
}
|
||||
else if (result == GTK_RESPONSE_OK)
|
||||
{
|
||||
processMessageDialogResult("OK");
|
||||
}
|
||||
else if (result == GTK_RESPONSE_CANCEL)
|
||||
{
|
||||
processMessageDialogResult("Cancel");
|
||||
}
|
||||
else
|
||||
{
|
||||
processMessageDialogResult("");
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
free(options->title);
|
||||
free(options->message);
|
||||
}
|
||||
|
||||
void extern processOpenFileResult(void *);
|
||||
|
||||
GtkFileFilter **AllocFileFilterArray(size_t ln)
|
||||
{
|
||||
return (GtkFileFilter **)malloc(ln * sizeof(GtkFileFilter *));
|
||||
}
|
||||
|
||||
void freeFileFilterArray(GtkFileFilter **filters)
|
||||
{
|
||||
free(filters);
|
||||
}
|
||||
|
||||
void Opendialog(void *data)
|
||||
{
|
||||
struct OpenFileDialogOptions *options = data;
|
||||
char *label = "_Open";
|
||||
if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
{
|
||||
label = "_Save";
|
||||
}
|
||||
GtkWidget *dlgWidget = gtk_file_chooser_dialog_new(options->title, options->window, options->action,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
label, GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
GtkFileChooser *fc = GTK_FILE_CHOOSER(dlgWidget);
|
||||
// filters
|
||||
if (options->filters != 0)
|
||||
{
|
||||
int index = 0;
|
||||
GtkFileFilter *thisFilter;
|
||||
while (options->filters[index] != NULL)
|
||||
{
|
||||
thisFilter = options->filters[index];
|
||||
gtk_file_chooser_add_filter(fc, thisFilter);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_file_chooser_set_local_only(fc, FALSE);
|
||||
|
||||
if (options->multipleFiles == 1)
|
||||
{
|
||||
gtk_file_chooser_set_select_multiple(fc, TRUE);
|
||||
}
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(fc, TRUE);
|
||||
if (options->createDirectories == 1)
|
||||
{
|
||||
gtk_file_chooser_set_create_folders(fc, TRUE);
|
||||
}
|
||||
if (options->showHiddenFiles == 1)
|
||||
{
|
||||
gtk_file_chooser_set_show_hidden(fc, TRUE);
|
||||
}
|
||||
|
||||
if (options->defaultDirectory != NULL)
|
||||
{
|
||||
gtk_file_chooser_set_current_folder(fc, options->defaultDirectory);
|
||||
free(options->defaultDirectory);
|
||||
}
|
||||
|
||||
if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
{
|
||||
if (options->defaultFilename != NULL)
|
||||
{
|
||||
gtk_file_chooser_set_current_name(fc, options->defaultFilename);
|
||||
free(options->defaultFilename);
|
||||
}
|
||||
}
|
||||
|
||||
gint response = gtk_dialog_run(GTK_DIALOG(dlgWidget));
|
||||
|
||||
// Max 1024 files to select
|
||||
char **result = calloc(1024, sizeof(char *));
|
||||
int resultIndex = 0;
|
||||
|
||||
if (response == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
GSList *filenames = gtk_file_chooser_get_filenames(fc);
|
||||
GSList *iter = filenames;
|
||||
while (iter)
|
||||
{
|
||||
result[resultIndex++] = (char *)iter->data;
|
||||
iter = g_slist_next(iter);
|
||||
if (resultIndex == 1024)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
processOpenFileResult(result);
|
||||
iter = filenames;
|
||||
while (iter)
|
||||
{
|
||||
g_free(iter->data);
|
||||
iter = g_slist_next(iter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
processOpenFileResult(result);
|
||||
}
|
||||
free(result);
|
||||
|
||||
// Release filters
|
||||
if (options->filters != NULL)
|
||||
{
|
||||
int index = 0;
|
||||
GtkFileFilter *thisFilter;
|
||||
while (options->filters[index] != 0)
|
||||
{
|
||||
thisFilter = options->filters[index];
|
||||
g_object_unref(thisFilter);
|
||||
index++;
|
||||
}
|
||||
freeFileFilterArray(options->filters);
|
||||
}
|
||||
gtk_widget_destroy(dlgWidget);
|
||||
free(options->title);
|
||||
}
|
||||
|
||||
GtkFileFilter *newFileFilter()
|
||||
{
|
||||
GtkFileFilter *result = gtk_file_filter_new();
|
||||
g_object_ref(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ShowInspector(void *webview) {
|
||||
WebKitWebInspector *inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
|
||||
webkit_web_inspector_show(WEBKIT_WEB_INSPECTOR(inspector));
|
||||
}
|
||||
|
||||
void sendShowInspectorMessage() {
|
||||
processMessage("wails:showInspector");
|
||||
}
|
||||
|
||||
void InstallF12Hotkey(void *window)
|
||||
{
|
||||
// When the user presses Ctrl+Shift+F12, call ShowInspector
|
||||
GtkAccelGroup *accel_group = gtk_accel_group_new();
|
||||
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
|
||||
GClosure *closure = g_cclosure_new(G_CALLBACK(sendShowInspectorMessage), window, NULL);
|
||||
gtk_accel_group_connect(accel_group, GDK_KEY_F12, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE, closure);
|
||||
}
|
||||
479
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.go
generated
vendored
Normal file
479
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.go
generated
vendored
Normal file
@@ -0,0 +1,479 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include <JavaScriptCore/JavaScript.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include "window.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
||||
)
|
||||
|
||||
func gtkBool(input bool) C.gboolean {
|
||||
if input {
|
||||
return C.gboolean(1)
|
||||
}
|
||||
return C.gboolean(0)
|
||||
}
|
||||
|
||||
type Window struct {
|
||||
appoptions *options.App
|
||||
debug bool
|
||||
devtoolsEnabled bool
|
||||
gtkWindow unsafe.Pointer
|
||||
contentManager unsafe.Pointer
|
||||
webview unsafe.Pointer
|
||||
applicationMenu *menu.Menu
|
||||
menubar *C.GtkWidget
|
||||
webviewBox *C.GtkWidget
|
||||
vbox *C.GtkWidget
|
||||
accels *C.GtkAccelGroup
|
||||
minWidth, minHeight, maxWidth, maxHeight int
|
||||
}
|
||||
|
||||
func bool2Cint(value bool) C.int {
|
||||
if value {
|
||||
return C.int(1)
|
||||
}
|
||||
return C.int(0)
|
||||
}
|
||||
|
||||
func NewWindow(appoptions *options.App, debug bool, devtoolsEnabled bool) *Window {
|
||||
validateWebKit2Version(appoptions)
|
||||
|
||||
result := &Window{
|
||||
appoptions: appoptions,
|
||||
debug: debug,
|
||||
devtoolsEnabled: devtoolsEnabled,
|
||||
minHeight: appoptions.MinHeight,
|
||||
minWidth: appoptions.MinWidth,
|
||||
maxHeight: appoptions.MaxHeight,
|
||||
maxWidth: appoptions.MaxWidth,
|
||||
}
|
||||
|
||||
gtkWindow := C.gtk_window_new(C.GTK_WINDOW_TOPLEVEL)
|
||||
C.g_object_ref_sink(C.gpointer(gtkWindow))
|
||||
result.gtkWindow = unsafe.Pointer(gtkWindow)
|
||||
|
||||
webviewName := C.CString("webview-box")
|
||||
defer C.free(unsafe.Pointer(webviewName))
|
||||
result.webviewBox = C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0)
|
||||
C.gtk_widget_set_name(result.webviewBox, webviewName)
|
||||
|
||||
result.vbox = C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0)
|
||||
C.gtk_container_add(result.asGTKContainer(), result.vbox)
|
||||
|
||||
result.contentManager = unsafe.Pointer(C.webkit_user_content_manager_new())
|
||||
external := C.CString("external")
|
||||
defer C.free(unsafe.Pointer(external))
|
||||
C.webkit_user_content_manager_register_script_message_handler(result.cWebKitUserContentManager(), external)
|
||||
C.SetupInvokeSignal(result.contentManager)
|
||||
|
||||
var webviewGpuPolicy int
|
||||
if appoptions.Linux != nil {
|
||||
webviewGpuPolicy = int(appoptions.Linux.WebviewGpuPolicy)
|
||||
} else {
|
||||
// workaround for https://github.com/wailsapp/wails/issues/2977
|
||||
webviewGpuPolicy = int(linux.WebviewGpuPolicyNever)
|
||||
}
|
||||
|
||||
webview := C.SetupWebview(
|
||||
result.contentManager,
|
||||
result.asGTKWindow(),
|
||||
bool2Cint(appoptions.HideWindowOnClose),
|
||||
C.int(webviewGpuPolicy),
|
||||
bool2Cint(appoptions.DragAndDrop != nil && appoptions.DragAndDrop.DisableWebViewDrop),
|
||||
bool2Cint(appoptions.DragAndDrop != nil && appoptions.DragAndDrop.EnableFileDrop),
|
||||
)
|
||||
result.webview = unsafe.Pointer(webview)
|
||||
buttonPressedName := C.CString("button-press-event")
|
||||
defer C.free(unsafe.Pointer(buttonPressedName))
|
||||
C.ConnectButtons(unsafe.Pointer(webview))
|
||||
|
||||
if devtoolsEnabled {
|
||||
C.DevtoolsEnabled(unsafe.Pointer(webview), C.int(1), C.bool(debug && appoptions.Debug.OpenInspectorOnStartup))
|
||||
// Install Ctrl-Shift-F12 hotkey to call ShowInspector
|
||||
C.InstallF12Hotkey(unsafe.Pointer(gtkWindow))
|
||||
}
|
||||
|
||||
if !(debug || appoptions.EnableDefaultContextMenu) {
|
||||
C.DisableContextMenu(unsafe.Pointer(webview))
|
||||
}
|
||||
|
||||
// Set background colour
|
||||
RGBA := appoptions.BackgroundColour
|
||||
result.SetBackgroundColour(RGBA.R, RGBA.G, RGBA.B, RGBA.A)
|
||||
|
||||
// Setup window
|
||||
result.SetKeepAbove(appoptions.AlwaysOnTop)
|
||||
result.SetResizable(!appoptions.DisableResize)
|
||||
result.SetDefaultSize(appoptions.Width, appoptions.Height)
|
||||
result.SetDecorated(!appoptions.Frameless)
|
||||
result.SetTitle(appoptions.Title)
|
||||
result.SetMinSize(appoptions.MinWidth, appoptions.MinHeight)
|
||||
result.SetMaxSize(appoptions.MaxWidth, appoptions.MaxHeight)
|
||||
if appoptions.Linux != nil {
|
||||
if appoptions.Linux.Icon != nil {
|
||||
result.SetWindowIcon(appoptions.Linux.Icon)
|
||||
}
|
||||
if appoptions.Linux.WindowIsTranslucent {
|
||||
C.SetWindowTransparency(gtkWindow)
|
||||
}
|
||||
}
|
||||
|
||||
// Menu
|
||||
result.SetApplicationMenu(appoptions.Menu)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (w *Window) asGTKWidget() *C.GtkWidget {
|
||||
return C.GTKWIDGET(w.gtkWindow)
|
||||
}
|
||||
|
||||
func (w *Window) asGTKWindow() *C.GtkWindow {
|
||||
return C.GTKWINDOW(w.gtkWindow)
|
||||
}
|
||||
|
||||
func (w *Window) asGTKContainer() *C.GtkContainer {
|
||||
return C.GTKCONTAINER(w.gtkWindow)
|
||||
}
|
||||
|
||||
func (w *Window) cWebKitUserContentManager() *C.WebKitUserContentManager {
|
||||
return (*C.WebKitUserContentManager)(w.contentManager)
|
||||
}
|
||||
|
||||
func (w *Window) Fullscreen() {
|
||||
C.ExecuteOnMainThread(C.Fullscreen, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) UnFullscreen() {
|
||||
if !w.IsFullScreen() {
|
||||
return
|
||||
}
|
||||
C.ExecuteOnMainThread(C.UnFullscreen, C.gpointer(w.asGTKWindow()))
|
||||
w.SetMinSize(w.minWidth, w.minHeight)
|
||||
w.SetMaxSize(w.maxWidth, w.maxHeight)
|
||||
}
|
||||
|
||||
func (w *Window) Destroy() {
|
||||
C.gtk_widget_destroy(w.asGTKWidget())
|
||||
C.g_object_unref(C.gpointer(w.gtkWindow))
|
||||
}
|
||||
|
||||
func (w *Window) Close() {
|
||||
C.gtk_window_close(w.asGTKWindow())
|
||||
}
|
||||
|
||||
func (w *Window) Center() {
|
||||
C.ExecuteOnMainThread(C.Center, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) SetPosition(x int, y int) {
|
||||
invokeOnMainThread(func() {
|
||||
C.SetPosition(unsafe.Pointer(w.asGTKWindow()), C.int(x), C.int(y))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Window) Size() (int, int) {
|
||||
var width, height C.int
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
invokeOnMainThread(func() {
|
||||
C.gtk_window_get_size(w.asGTKWindow(), &width, &height)
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return int(width), int(height)
|
||||
}
|
||||
|
||||
func (w *Window) GetPosition() (int, int) {
|
||||
var width, height C.int
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
invokeOnMainThread(func() {
|
||||
C.gtk_window_get_position(w.asGTKWindow(), &width, &height)
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return int(width), int(height)
|
||||
}
|
||||
|
||||
func (w *Window) SetMaxSize(maxWidth int, maxHeight int) {
|
||||
w.maxHeight = maxHeight
|
||||
w.maxWidth = maxWidth
|
||||
invokeOnMainThread(func() {
|
||||
C.SetMinMaxSize(w.asGTKWindow(), C.int(w.minWidth), C.int(w.minHeight), C.int(w.maxWidth), C.int(w.maxHeight))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Window) SetMinSize(minWidth int, minHeight int) {
|
||||
w.minHeight = minHeight
|
||||
w.minWidth = minWidth
|
||||
invokeOnMainThread(func() {
|
||||
C.SetMinMaxSize(w.asGTKWindow(), C.int(w.minWidth), C.int(w.minHeight), C.int(w.maxWidth), C.int(w.maxHeight))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Window) Show() {
|
||||
C.ExecuteOnMainThread(C.Show, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) Hide() {
|
||||
C.ExecuteOnMainThread(C.Hide, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) Maximise() {
|
||||
C.ExecuteOnMainThread(C.Maximise, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) UnMaximise() {
|
||||
C.ExecuteOnMainThread(C.UnMaximise, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) Minimise() {
|
||||
C.ExecuteOnMainThread(C.Minimise, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) UnMinimise() {
|
||||
C.ExecuteOnMainThread(C.UnMinimise, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) IsFullScreen() bool {
|
||||
result := C.IsFullscreen(w.asGTKWidget())
|
||||
if result != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Window) IsMaximised() bool {
|
||||
result := C.IsMaximised(w.asGTKWidget())
|
||||
return result > 0
|
||||
}
|
||||
|
||||
func (w *Window) IsMinimised() bool {
|
||||
result := C.IsMinimised(w.asGTKWidget())
|
||||
return result > 0
|
||||
}
|
||||
|
||||
func (w *Window) IsNormal() bool {
|
||||
return !w.IsMaximised() && !w.IsMinimised() && !w.IsFullScreen()
|
||||
}
|
||||
|
||||
func (w *Window) SetBackgroundColour(r uint8, g uint8, b uint8, a uint8) {
|
||||
windowIsTranslucent := false
|
||||
if w.appoptions.Linux != nil && w.appoptions.Linux.WindowIsTranslucent {
|
||||
windowIsTranslucent = true
|
||||
}
|
||||
data := C.RGBAOptions{
|
||||
r: C.uchar(r),
|
||||
g: C.uchar(g),
|
||||
b: C.uchar(b),
|
||||
a: C.uchar(a),
|
||||
webview: w.webview,
|
||||
webviewBox: unsafe.Pointer(w.webviewBox),
|
||||
windowIsTranslucent: gtkBool(windowIsTranslucent),
|
||||
}
|
||||
invokeOnMainThread(func() { C.SetBackgroundColour(unsafe.Pointer(&data)) })
|
||||
|
||||
}
|
||||
|
||||
func (w *Window) SetWindowIcon(icon []byte) {
|
||||
if len(icon) == 0 {
|
||||
return
|
||||
}
|
||||
C.SetWindowIcon(w.asGTKWindow(), (*C.guchar)(&icon[0]), (C.gsize)(len(icon)))
|
||||
}
|
||||
|
||||
func (w *Window) Run(url string) {
|
||||
if w.menubar != nil {
|
||||
C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), w.menubar, 0, 0, 0)
|
||||
}
|
||||
|
||||
C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.webviewBox)), C.GTKWIDGET(w.webview), 1, 1, 0)
|
||||
C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), w.webviewBox, 1, 1, 0)
|
||||
_url := C.CString(url)
|
||||
C.LoadIndex(w.webview, _url)
|
||||
defer C.free(unsafe.Pointer(_url))
|
||||
if w.appoptions.StartHidden {
|
||||
w.Hide()
|
||||
}
|
||||
C.gtk_widget_show_all(w.asGTKWidget())
|
||||
w.Center()
|
||||
switch w.appoptions.WindowStartState {
|
||||
case options.Fullscreen:
|
||||
w.Fullscreen()
|
||||
case options.Minimised:
|
||||
w.Minimise()
|
||||
case options.Maximised:
|
||||
w.Maximise()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) SetKeepAbove(top bool) {
|
||||
C.gtk_window_set_keep_above(w.asGTKWindow(), gtkBool(top))
|
||||
}
|
||||
|
||||
func (w *Window) SetResizable(resizable bool) {
|
||||
C.gtk_window_set_resizable(w.asGTKWindow(), gtkBool(resizable))
|
||||
}
|
||||
|
||||
func (w *Window) SetDefaultSize(width int, height int) {
|
||||
C.gtk_window_set_default_size(w.asGTKWindow(), C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (w *Window) SetSize(width int, height int) {
|
||||
C.gtk_window_resize(w.asGTKWindow(), C.gint(width), C.gint(height))
|
||||
}
|
||||
|
||||
func (w *Window) SetDecorated(frameless bool) {
|
||||
C.gtk_window_set_decorated(w.asGTKWindow(), gtkBool(frameless))
|
||||
}
|
||||
|
||||
func (w *Window) SetTitle(title string) {
|
||||
C.SetTitle(w.asGTKWindow(), C.CString(title))
|
||||
}
|
||||
|
||||
func (w *Window) ExecJS(js string) {
|
||||
jscallback := C.JSCallback{
|
||||
webview: w.webview,
|
||||
script: C.CString(js),
|
||||
}
|
||||
invokeOnMainThread(func() { C.ExecuteJS(unsafe.Pointer(&jscallback)) })
|
||||
}
|
||||
|
||||
func (w *Window) StartDrag() {
|
||||
C.StartDrag(w.webview, w.asGTKWindow())
|
||||
}
|
||||
|
||||
func (w *Window) StartResize(edge uintptr) {
|
||||
C.StartResize(w.webview, w.asGTKWindow(), C.GdkWindowEdge(edge))
|
||||
}
|
||||
|
||||
func (w *Window) Quit() {
|
||||
C.gtk_main_quit()
|
||||
}
|
||||
|
||||
func (w *Window) OpenFileDialog(dialogOptions frontend.OpenDialogOptions, multipleFiles int, action C.GtkFileChooserAction) {
|
||||
|
||||
data := C.OpenFileDialogOptions{
|
||||
window: w.asGTKWindow(),
|
||||
title: C.CString(dialogOptions.Title),
|
||||
multipleFiles: C.int(multipleFiles),
|
||||
action: action,
|
||||
}
|
||||
|
||||
if len(dialogOptions.Filters) > 0 {
|
||||
// Create filter array
|
||||
mem := NewCalloc()
|
||||
arraySize := len(dialogOptions.Filters) + 1
|
||||
data.filters = C.AllocFileFilterArray((C.size_t)(arraySize))
|
||||
filters := unsafe.Slice((**C.struct__GtkFileFilter)(unsafe.Pointer(data.filters)), arraySize)
|
||||
for index, filter := range dialogOptions.Filters {
|
||||
thisFilter := C.gtk_file_filter_new()
|
||||
C.g_object_ref(C.gpointer(thisFilter))
|
||||
if filter.DisplayName != "" {
|
||||
cName := mem.String(filter.DisplayName)
|
||||
C.gtk_file_filter_set_name(thisFilter, cName)
|
||||
}
|
||||
if filter.Pattern != "" {
|
||||
for _, thisPattern := range strings.Split(filter.Pattern, ";") {
|
||||
cThisPattern := mem.String(thisPattern)
|
||||
C.gtk_file_filter_add_pattern(thisFilter, cThisPattern)
|
||||
}
|
||||
}
|
||||
// Add filter to array
|
||||
filters[index] = thisFilter
|
||||
}
|
||||
mem.Free()
|
||||
filters[arraySize-1] = nil
|
||||
}
|
||||
|
||||
if dialogOptions.CanCreateDirectories {
|
||||
data.createDirectories = C.int(1)
|
||||
}
|
||||
|
||||
if dialogOptions.ShowHiddenFiles {
|
||||
data.showHiddenFiles = C.int(1)
|
||||
}
|
||||
|
||||
if dialogOptions.DefaultFilename != "" {
|
||||
data.defaultFilename = C.CString(dialogOptions.DefaultFilename)
|
||||
}
|
||||
|
||||
if dialogOptions.DefaultDirectory != "" {
|
||||
data.defaultDirectory = C.CString(dialogOptions.DefaultDirectory)
|
||||
}
|
||||
|
||||
invokeOnMainThread(func() { C.Opendialog(unsafe.Pointer(&data)) })
|
||||
}
|
||||
|
||||
func (w *Window) MessageDialog(dialogOptions frontend.MessageDialogOptions) {
|
||||
|
||||
data := C.MessageDialogOptions{
|
||||
window: w.gtkWindow,
|
||||
title: C.CString(dialogOptions.Title),
|
||||
message: C.CString(dialogOptions.Message),
|
||||
}
|
||||
switch dialogOptions.Type {
|
||||
case frontend.InfoDialog:
|
||||
data.messageType = C.int(0)
|
||||
case frontend.ErrorDialog:
|
||||
data.messageType = C.int(1)
|
||||
case frontend.QuestionDialog:
|
||||
data.messageType = C.int(2)
|
||||
case frontend.WarningDialog:
|
||||
data.messageType = C.int(3)
|
||||
}
|
||||
invokeOnMainThread(func() { C.MessageDialog(unsafe.Pointer(&data)) })
|
||||
}
|
||||
|
||||
func (w *Window) ToggleMaximise() {
|
||||
if w.IsMaximised() {
|
||||
w.UnMaximise()
|
||||
} else {
|
||||
w.Maximise()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) ShowInspector() {
|
||||
invokeOnMainThread(func() { C.ShowInspector(w.webview) })
|
||||
}
|
||||
|
||||
// showModalDialogAndExit shows a modal dialog and exits the app.
|
||||
func showModalDialogAndExit(title, message string) {
|
||||
go func() {
|
||||
data := C.MessageDialogOptions{
|
||||
title: C.CString(title),
|
||||
message: C.CString(message),
|
||||
messageType: C.int(1),
|
||||
}
|
||||
|
||||
C.MessageDialog(unsafe.Pointer(&data))
|
||||
}()
|
||||
|
||||
<-messageDialogResult
|
||||
log.Fatal(message)
|
||||
}
|
||||
128
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.h
generated
vendored
Normal file
128
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.h
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
#ifndef window_h
|
||||
#define window_h
|
||||
|
||||
#include <JavaScriptCore/JavaScript.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct DragOptions
|
||||
{
|
||||
void *webview;
|
||||
GtkWindow *mainwindow;
|
||||
} DragOptions;
|
||||
|
||||
typedef struct ResizeOptions
|
||||
{
|
||||
void *webview;
|
||||
GtkWindow *mainwindow;
|
||||
GdkWindowEdge edge;
|
||||
} ResizeOptions;
|
||||
|
||||
typedef struct JSCallback
|
||||
{
|
||||
void *webview;
|
||||
char *script;
|
||||
} JSCallback;
|
||||
|
||||
typedef struct MessageDialogOptions
|
||||
{
|
||||
void *window;
|
||||
char *title;
|
||||
char *message;
|
||||
int messageType;
|
||||
} MessageDialogOptions;
|
||||
|
||||
typedef struct OpenFileDialogOptions
|
||||
{
|
||||
GtkWindow *window;
|
||||
char *title;
|
||||
char *defaultFilename;
|
||||
char *defaultDirectory;
|
||||
int createDirectories;
|
||||
int multipleFiles;
|
||||
int showHiddenFiles;
|
||||
GtkFileChooserAction action;
|
||||
GtkFileFilter **filters;
|
||||
} OpenFileDialogOptions;
|
||||
|
||||
typedef struct RGBAOptions
|
||||
{
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
void *webview;
|
||||
void *webviewBox;
|
||||
gboolean windowIsTranslucent;
|
||||
} RGBAOptions;
|
||||
|
||||
typedef struct SetTitleArgs
|
||||
{
|
||||
GtkWindow *window;
|
||||
char *title;
|
||||
} SetTitleArgs;
|
||||
|
||||
typedef struct SetPositionArgs
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
void *window;
|
||||
} SetPositionArgs;
|
||||
|
||||
void ExecuteOnMainThread(void *f, gpointer jscallback);
|
||||
|
||||
GtkWidget *GTKWIDGET(void *pointer);
|
||||
GtkWindow *GTKWINDOW(void *pointer);
|
||||
GtkContainer *GTKCONTAINER(void *pointer);
|
||||
GtkBox *GTKBOX(void *pointer);
|
||||
|
||||
// window
|
||||
ulong SetupInvokeSignal(void *contentManager);
|
||||
|
||||
void SetWindowIcon(GtkWindow *window, const guchar *buf, gsize len);
|
||||
void SetWindowTransparency(GtkWidget *widget);
|
||||
void SetBackgroundColour(void *data);
|
||||
void SetTitle(GtkWindow *window, char *title);
|
||||
void SetPosition(void *window, int x, int y);
|
||||
void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_width, int max_height);
|
||||
void DisableContextMenu(void *webview);
|
||||
void ConnectButtons(void *webview);
|
||||
|
||||
int IsFullscreen(GtkWidget *widget);
|
||||
int IsMaximised(GtkWidget *widget);
|
||||
int IsMinimised(GtkWidget *widget);
|
||||
|
||||
gboolean Center(gpointer data);
|
||||
gboolean Show(gpointer data);
|
||||
gboolean Hide(gpointer data);
|
||||
gboolean Maximise(gpointer data);
|
||||
gboolean UnMaximise(gpointer data);
|
||||
gboolean Minimise(gpointer data);
|
||||
gboolean UnMinimise(gpointer data);
|
||||
gboolean Fullscreen(gpointer data);
|
||||
gboolean UnFullscreen(gpointer data);
|
||||
|
||||
// WebView
|
||||
GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy, int disableWebViewDragAndDrop, int enableDragAndDrop);
|
||||
void LoadIndex(void *webview, char *url);
|
||||
void DevtoolsEnabled(void *webview, int enabled, bool showInspector);
|
||||
void ExecuteJS(void *data);
|
||||
|
||||
// Drag
|
||||
void StartDrag(void *webview, GtkWindow *mainwindow);
|
||||
void StartResize(void *webview, GtkWindow *mainwindow, GdkWindowEdge edge);
|
||||
|
||||
// Dialog
|
||||
void MessageDialog(void *data);
|
||||
GtkFileFilter **AllocFileFilterArray(size_t ln);
|
||||
void Opendialog(void *data);
|
||||
|
||||
// Inspector
|
||||
void sendShowInspectorMessage();
|
||||
void ShowInspector(void *webview);
|
||||
void InstallF12Hotkey(void *window);
|
||||
|
||||
#endif /* window_h */
|
||||
43
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/browser.go
generated
vendored
Normal file
43
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/browser.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/utils"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var fallbackBrowserPaths = []string{
|
||||
`\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`,
|
||||
`\Program Files\Google\Chrome\Application\chrome.exe`,
|
||||
`\Program Files (x86)\Google\Chrome\Application\chrome.exe`,
|
||||
`\Program Files\Mozilla Firefox\firefox.exe`,
|
||||
}
|
||||
|
||||
// BrowserOpenURL Use the default browser to open the url
|
||||
func (f *Frontend) BrowserOpenURL(rawURL string) {
|
||||
url, err := utils.ValidateAndSanitizeURL(rawURL)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Invalid URL %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Specific method implementation
|
||||
err = browser.OpenURL(url)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
for _, fallback := range fallbackBrowserPaths {
|
||||
if err := openBrowser(fallback, url); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
f.logger.Error("Unable to open default system browser")
|
||||
}
|
||||
|
||||
func openBrowser(path, url string) error {
|
||||
return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(path), windows.StringToUTF16Ptr(url), nil, windows.SW_SHOWNORMAL)
|
||||
}
|
||||
16
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/clipboard.go
generated
vendored
Normal file
16
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/clipboard.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
||||
)
|
||||
|
||||
func (f *Frontend) ClipboardGetText() (string, error) {
|
||||
return win32.GetClipboardText()
|
||||
}
|
||||
|
||||
func (f *Frontend) ClipboardSetText(text string) error {
|
||||
return win32.SetClipboardText(text)
|
||||
}
|
||||
210
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/dialog.go
generated
vendored
Normal file
210
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/dialog.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func (f *Frontend) getHandleForDialog() w32.HWND {
|
||||
if f.mainWindow.IsVisible() {
|
||||
return f.mainWindow.Handle()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getDefaultFolder(folder string) (string, error) {
|
||||
if folder == "" {
|
||||
return "", nil
|
||||
}
|
||||
return filepath.Abs(folder)
|
||||
}
|
||||
|
||||
// OpenDirectoryDialog prompts the user to select a directory
|
||||
func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) {
|
||||
|
||||
defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config := cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "PickFolder",
|
||||
Folder: defaultFolder,
|
||||
}
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewSelectFolderDialog(config)
|
||||
}, false)
|
||||
|
||||
if err != nil && err != cfd.ErrCancelled {
|
||||
return "", err
|
||||
}
|
||||
return result.(string), nil
|
||||
}
|
||||
|
||||
// OpenFileDialog prompts the user to select a file
|
||||
func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
|
||||
defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config := cfd.DialogConfig{
|
||||
Folder: defaultFolder,
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
FileName: options.DefaultFilename,
|
||||
Title: options.Title,
|
||||
}
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewOpenFileDialog(config)
|
||||
}, false)
|
||||
|
||||
if err != nil && err != cfd.ErrCancelled {
|
||||
return "", err
|
||||
}
|
||||
return result.(string), nil
|
||||
}
|
||||
|
||||
// OpenMultipleFilesDialog prompts the user to select a file
|
||||
func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) {
|
||||
|
||||
defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "OpenMultipleFiles",
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
FileName: options.DefaultFilename,
|
||||
Folder: defaultFolder,
|
||||
}
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewOpenMultipleFilesDialog(config)
|
||||
}, true)
|
||||
|
||||
if err != nil && err != cfd.ErrCancelled {
|
||||
return nil, err
|
||||
}
|
||||
return result.([]string), nil
|
||||
}
|
||||
|
||||
// SaveFileDialog prompts the user to select a file
|
||||
func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) {
|
||||
|
||||
defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config := cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "SaveFile",
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
FileName: options.DefaultFilename,
|
||||
Folder: defaultFolder,
|
||||
}
|
||||
|
||||
if len(options.Filters) > 0 {
|
||||
config.DefaultExtension = strings.TrimPrefix(strings.Split(options.Filters[0].Pattern, ";")[0], "*")
|
||||
}
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewSaveFileDialog(config)
|
||||
}, false)
|
||||
|
||||
if err != nil && err != cfd.ErrCancelled {
|
||||
return "", err
|
||||
}
|
||||
return result.(string), nil
|
||||
}
|
||||
|
||||
func (f *Frontend) showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) {
|
||||
return invokeSync(f.mainWindow, func() (any, error) {
|
||||
dlg, err := newDlg()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err := dlg.Release()
|
||||
if err != nil {
|
||||
println("ERROR: Unable to release dialog:", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
dlg.SetParentWindowHandle(f.getHandleForDialog())
|
||||
if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect {
|
||||
return multi.ShowAndGetResults()
|
||||
}
|
||||
return dlg.ShowAndGetResult()
|
||||
})
|
||||
}
|
||||
|
||||
func calculateMessageDialogFlags(options frontend.MessageDialogOptions) uint32 {
|
||||
var flags uint32
|
||||
|
||||
switch options.Type {
|
||||
case frontend.InfoDialog:
|
||||
flags = windows.MB_OK | windows.MB_ICONINFORMATION
|
||||
case frontend.ErrorDialog:
|
||||
flags = windows.MB_ICONERROR | windows.MB_OK
|
||||
case frontend.QuestionDialog:
|
||||
flags = windows.MB_YESNO
|
||||
if strings.TrimSpace(strings.ToLower(options.DefaultButton)) == "no" {
|
||||
flags |= windows.MB_DEFBUTTON2
|
||||
}
|
||||
case frontend.WarningDialog:
|
||||
flags = windows.MB_OK | windows.MB_ICONWARNING
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
// MessageDialog show a message dialog to the user
|
||||
func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
|
||||
|
||||
title, err := syscall.UTF16PtrFromString(options.Title)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
message, err := syscall.UTF16PtrFromString(options.Message)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
flags := calculateMessageDialogFlags(options)
|
||||
|
||||
button, _ := windows.MessageBox(windows.HWND(f.getHandleForDialog()), message, title, flags|windows.MB_SYSTEMMODAL)
|
||||
// This maps MessageBox return values to strings
|
||||
responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"}
|
||||
result := "Error"
|
||||
if int(button) < len(responses) {
|
||||
result = responses[button]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func convertFilters(filters []frontend.FileFilter) []cfd.FileFilter {
|
||||
var result []cfd.FileFilter
|
||||
for _, filter := range filters {
|
||||
result = append(result, cfd.FileFilter(filter))
|
||||
}
|
||||
return result
|
||||
}
|
||||
1004
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/frontend.go
generated
vendored
Normal file
1004
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/frontend.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
203
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/keys.go
generated
vendored
Normal file
203
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/keys.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ModifierMap = map[keys.Modifier]winc.Modifiers{
|
||||
keys.ShiftKey: winc.ModShift,
|
||||
keys.ControlKey: winc.ModControl,
|
||||
keys.OptionOrAltKey: winc.ModAlt,
|
||||
keys.CmdOrCtrlKey: winc.ModControl,
|
||||
}
|
||||
|
||||
func acceleratorToWincShortcut(accelerator *keys.Accelerator) winc.Shortcut {
|
||||
|
||||
if accelerator == nil {
|
||||
return winc.NoShortcut
|
||||
}
|
||||
inKey := strings.ToUpper(accelerator.Key)
|
||||
key, exists := keyMap[inKey]
|
||||
if !exists {
|
||||
return winc.NoShortcut
|
||||
}
|
||||
var modifiers winc.Modifiers
|
||||
if _, exists := shiftMap[inKey]; exists {
|
||||
modifiers = winc.ModShift
|
||||
}
|
||||
for _, mod := range accelerator.Modifiers {
|
||||
modifiers |= ModifierMap[mod]
|
||||
}
|
||||
return winc.Shortcut{
|
||||
Modifiers: modifiers,
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
var shiftMap = map[string]struct{}{
|
||||
"~": {},
|
||||
")": {},
|
||||
"!": {},
|
||||
"@": {},
|
||||
"#": {},
|
||||
"$": {},
|
||||
"%": {},
|
||||
"^": {},
|
||||
"&": {},
|
||||
"*": {},
|
||||
"(": {},
|
||||
"_": {},
|
||||
"PLUS": {},
|
||||
"<": {},
|
||||
">": {},
|
||||
"?": {},
|
||||
":": {},
|
||||
`"`: {},
|
||||
"{": {},
|
||||
"}": {},
|
||||
"|": {},
|
||||
}
|
||||
|
||||
var keyMap = map[string]winc.Key{
|
||||
"0": winc.Key0,
|
||||
"1": winc.Key1,
|
||||
"2": winc.Key2,
|
||||
"3": winc.Key3,
|
||||
"4": winc.Key4,
|
||||
"5": winc.Key5,
|
||||
"6": winc.Key6,
|
||||
"7": winc.Key7,
|
||||
"8": winc.Key8,
|
||||
"9": winc.Key9,
|
||||
"A": winc.KeyA,
|
||||
"B": winc.KeyB,
|
||||
"C": winc.KeyC,
|
||||
"D": winc.KeyD,
|
||||
"E": winc.KeyE,
|
||||
"F": winc.KeyF,
|
||||
"G": winc.KeyG,
|
||||
"H": winc.KeyH,
|
||||
"I": winc.KeyI,
|
||||
"J": winc.KeyJ,
|
||||
"K": winc.KeyK,
|
||||
"L": winc.KeyL,
|
||||
"M": winc.KeyM,
|
||||
"N": winc.KeyN,
|
||||
"O": winc.KeyO,
|
||||
"P": winc.KeyP,
|
||||
"Q": winc.KeyQ,
|
||||
"R": winc.KeyR,
|
||||
"S": winc.KeyS,
|
||||
"T": winc.KeyT,
|
||||
"U": winc.KeyU,
|
||||
"V": winc.KeyV,
|
||||
"W": winc.KeyW,
|
||||
"X": winc.KeyX,
|
||||
"Y": winc.KeyY,
|
||||
"Z": winc.KeyZ,
|
||||
"F1": winc.KeyF1,
|
||||
"F2": winc.KeyF2,
|
||||
"F3": winc.KeyF3,
|
||||
"F4": winc.KeyF4,
|
||||
"F5": winc.KeyF5,
|
||||
"F6": winc.KeyF6,
|
||||
"F7": winc.KeyF7,
|
||||
"F8": winc.KeyF8,
|
||||
"F9": winc.KeyF9,
|
||||
"F10": winc.KeyF10,
|
||||
"F11": winc.KeyF11,
|
||||
"F12": winc.KeyF12,
|
||||
"F13": winc.KeyF13,
|
||||
"F14": winc.KeyF14,
|
||||
"F15": winc.KeyF15,
|
||||
"F16": winc.KeyF16,
|
||||
"F17": winc.KeyF17,
|
||||
"F18": winc.KeyF18,
|
||||
"F19": winc.KeyF19,
|
||||
"F20": winc.KeyF20,
|
||||
"F21": winc.KeyF21,
|
||||
"F22": winc.KeyF22,
|
||||
"F23": winc.KeyF23,
|
||||
"F24": winc.KeyF24,
|
||||
|
||||
"`": winc.KeyOEM3,
|
||||
",": winc.KeyOEMComma,
|
||||
".": winc.KeyOEMPeriod,
|
||||
"/": winc.KeyOEM2,
|
||||
";": winc.KeyOEM1,
|
||||
"'": winc.KeyOEM7,
|
||||
"[": winc.KeyOEM4,
|
||||
"]": winc.KeyOEM6,
|
||||
`\`: winc.KeyOEM5,
|
||||
|
||||
"~": winc.KeyOEM3, //
|
||||
")": winc.Key0,
|
||||
"!": winc.Key1,
|
||||
"@": winc.Key2,
|
||||
"#": winc.Key3,
|
||||
"$": winc.Key4,
|
||||
"%": winc.Key5,
|
||||
"^": winc.Key6,
|
||||
"&": winc.Key7,
|
||||
"*": winc.Key8,
|
||||
"(": winc.Key9,
|
||||
"_": winc.KeyOEMMinus,
|
||||
"PLUS": winc.KeyOEMPlus,
|
||||
"<": winc.KeyOEMComma,
|
||||
">": winc.KeyOEMPeriod,
|
||||
"?": winc.KeyOEM2,
|
||||
":": winc.KeyOEM1,
|
||||
`"`: winc.KeyOEM7,
|
||||
"{": winc.KeyOEM4,
|
||||
"}": winc.KeyOEM6,
|
||||
"|": winc.KeyOEM5,
|
||||
|
||||
"SPACE": winc.KeySpace,
|
||||
"TAB": winc.KeyTab,
|
||||
"CAPSLOCK": winc.KeyCapital,
|
||||
"NUMLOCK": winc.KeyNumlock,
|
||||
"SCROLLLOCK": winc.KeyScroll,
|
||||
"BACKSPACE": winc.KeyBack,
|
||||
"DELETE": winc.KeyDelete,
|
||||
"INSERT": winc.KeyInsert,
|
||||
"RETURN": winc.KeyReturn,
|
||||
"ENTER": winc.KeyReturn,
|
||||
"UP": winc.KeyUp,
|
||||
"DOWN": winc.KeyDown,
|
||||
"LEFT": winc.KeyLeft,
|
||||
"RIGHT": winc.KeyRight,
|
||||
"HOME": winc.KeyHome,
|
||||
"END": winc.KeyEnd,
|
||||
"PAGEUP": winc.KeyPrior,
|
||||
"PAGEDOWN": winc.KeyNext,
|
||||
"ESCAPE": winc.KeyEscape,
|
||||
"ESC": winc.KeyEscape,
|
||||
"VOLUMEUP": winc.KeyVolumeUp,
|
||||
"VOLUMEDOWN": winc.KeyVolumeDown,
|
||||
"VOLUMEMUTE": winc.KeyVolumeMute,
|
||||
"MEDIANEXTTRACK": winc.KeyMediaNextTrack,
|
||||
"MEDIAPREVIOUSTRACK": winc.KeyMediaPrevTrack,
|
||||
"MEDIASTOP": winc.KeyMediaStop,
|
||||
"MEDIAPLAYPAUSE": winc.KeyMediaPlayPause,
|
||||
"PRINTSCREEN": winc.KeyPrint,
|
||||
"NUM0": winc.KeyNumpad0,
|
||||
"NUM1": winc.KeyNumpad1,
|
||||
"NUM2": winc.KeyNumpad2,
|
||||
"NUM3": winc.KeyNumpad3,
|
||||
"NUM4": winc.KeyNumpad4,
|
||||
"NUM5": winc.KeyNumpad5,
|
||||
"NUM6": winc.KeyNumpad6,
|
||||
"NUM7": winc.KeyNumpad7,
|
||||
"NUM8": winc.KeyNumpad8,
|
||||
"NUM9": winc.KeyNumpad9,
|
||||
"nummult": winc.KeyMultiply,
|
||||
"numadd": winc.KeyAdd,
|
||||
"numsub": winc.KeySubtract,
|
||||
"numdec": winc.KeyDecimal,
|
||||
"numdiv": winc.KeyDivide,
|
||||
}
|
||||
132
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/menu.go
generated
vendored
Normal file
132
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/menu.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
var checkboxMap = map[*menu.MenuItem][]*winc.MenuItem{}
|
||||
var radioGroupMap = map[*menu.MenuItem][]*winc.MenuItem{}
|
||||
|
||||
func toggleCheckBox(menuItem *menu.MenuItem) {
|
||||
menuItem.Checked = !menuItem.Checked
|
||||
for _, wincMenu := range checkboxMap[menuItem] {
|
||||
wincMenu.SetChecked(menuItem.Checked)
|
||||
}
|
||||
}
|
||||
|
||||
func addCheckBoxToMap(menuItem *menu.MenuItem, wincMenuItem *winc.MenuItem) {
|
||||
if checkboxMap[menuItem] == nil {
|
||||
checkboxMap[menuItem] = []*winc.MenuItem{}
|
||||
}
|
||||
checkboxMap[menuItem] = append(checkboxMap[menuItem], wincMenuItem)
|
||||
}
|
||||
|
||||
func toggleRadioItem(menuItem *menu.MenuItem) {
|
||||
menuItem.Checked = !menuItem.Checked
|
||||
for _, wincMenu := range radioGroupMap[menuItem] {
|
||||
wincMenu.SetChecked(menuItem.Checked)
|
||||
}
|
||||
}
|
||||
|
||||
func addRadioItemToMap(menuItem *menu.MenuItem, wincMenuItem *winc.MenuItem) {
|
||||
if radioGroupMap[menuItem] == nil {
|
||||
radioGroupMap[menuItem] = []*winc.MenuItem{}
|
||||
}
|
||||
radioGroupMap[menuItem] = append(radioGroupMap[menuItem], wincMenuItem)
|
||||
}
|
||||
|
||||
func (w *Window) SetApplicationMenu(menu *menu.Menu) {
|
||||
w.applicationMenu = menu
|
||||
processMenu(w, menu)
|
||||
}
|
||||
|
||||
func processMenu(window *Window, menu *menu.Menu) {
|
||||
mainMenu := window.NewMenu()
|
||||
for _, menuItem := range menu.Items {
|
||||
submenu := mainMenu.AddSubMenu(menuItem.Label)
|
||||
if menuItem.SubMenu != nil {
|
||||
for _, menuItem := range menuItem.SubMenu.Items {
|
||||
processMenuItem(submenu, menuItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
mainMenu.Show()
|
||||
}
|
||||
|
||||
func processMenuItem(parent *winc.MenuItem, menuItem *menu.MenuItem) {
|
||||
if menuItem.Hidden {
|
||||
return
|
||||
}
|
||||
switch menuItem.Type {
|
||||
case menu.SeparatorType:
|
||||
parent.AddSeparator()
|
||||
case menu.TextType:
|
||||
shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
|
||||
newItem := parent.AddItem(menuItem.Label, shortcut)
|
||||
//if menuItem.Tooltip != "" {
|
||||
// newItem.SetToolTip(menuItem.Tooltip)
|
||||
//}
|
||||
if menuItem.Click != nil {
|
||||
newItem.OnClick().Bind(func(e *winc.Event) {
|
||||
menuItem.Click(&menu.CallbackData{
|
||||
MenuItem: menuItem,
|
||||
})
|
||||
})
|
||||
}
|
||||
newItem.SetEnabled(!menuItem.Disabled)
|
||||
|
||||
case menu.CheckboxType:
|
||||
shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
|
||||
newItem := parent.AddItem(menuItem.Label, shortcut)
|
||||
newItem.SetCheckable(true)
|
||||
newItem.SetChecked(menuItem.Checked)
|
||||
//if menuItem.Tooltip != "" {
|
||||
// newItem.SetToolTip(menuItem.Tooltip)
|
||||
//}
|
||||
if menuItem.Click != nil {
|
||||
newItem.OnClick().Bind(func(e *winc.Event) {
|
||||
toggleCheckBox(menuItem)
|
||||
menuItem.Click(&menu.CallbackData{
|
||||
MenuItem: menuItem,
|
||||
})
|
||||
})
|
||||
}
|
||||
newItem.SetEnabled(!menuItem.Disabled)
|
||||
addCheckBoxToMap(menuItem, newItem)
|
||||
case menu.RadioType:
|
||||
shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
|
||||
newItem := parent.AddItemRadio(menuItem.Label, shortcut)
|
||||
newItem.SetCheckable(true)
|
||||
newItem.SetChecked(menuItem.Checked)
|
||||
//if menuItem.Tooltip != "" {
|
||||
// newItem.SetToolTip(menuItem.Tooltip)
|
||||
//}
|
||||
if menuItem.Click != nil {
|
||||
newItem.OnClick().Bind(func(e *winc.Event) {
|
||||
toggleRadioItem(menuItem)
|
||||
menuItem.Click(&menu.CallbackData{
|
||||
MenuItem: menuItem,
|
||||
})
|
||||
})
|
||||
}
|
||||
newItem.SetEnabled(!menuItem.Disabled)
|
||||
addRadioItemToMap(menuItem, newItem)
|
||||
case menu.SubmenuType:
|
||||
submenu := parent.AddSubMenu(menuItem.Label)
|
||||
for _, menuItem := range menuItem.SubMenu.Items {
|
||||
processMenuItem(submenu, menuItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
|
||||
f.mainWindow.SetApplicationMenu(menu)
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuUpdateApplicationMenu() {
|
||||
processMenu(f.mainWindow, f.mainWindow.applicationMenu)
|
||||
}
|
||||
489
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/notifications.go
generated
vendored
Normal file
489
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/notifications.go
generated
vendored
Normal file
@@ -0,0 +1,489 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
wintoast "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast"
|
||||
"github.com/google/uuid"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
"git.sr.ht/~jackmordaunt/go-toast/v2"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
categories map[string]frontend.NotificationCategory
|
||||
categoriesLock sync.RWMutex
|
||||
appName string
|
||||
appGUID string
|
||||
iconPath string = ""
|
||||
exePath string
|
||||
iconOnce sync.Once
|
||||
iconErr error
|
||||
|
||||
notificationResultCallback func(result frontend.NotificationResult)
|
||||
callbackLock sync.RWMutex
|
||||
)
|
||||
|
||||
const DefaultActionIdentifier = "DEFAULT_ACTION"
|
||||
|
||||
const (
|
||||
ToastRegistryPath = `Software\Classes\AppUserModelId\`
|
||||
ToastRegistryGuidKey = "CustomActivator"
|
||||
NotificationCategoriesRegistryPath = `SOFTWARE\%s\NotificationCategories`
|
||||
NotificationCategoriesRegistryKey = "Categories"
|
||||
)
|
||||
|
||||
// NotificationPayload combines the action ID and user data into a single structure
|
||||
type NotificationPayload struct {
|
||||
Action string `json:"action"`
|
||||
Options frontend.NotificationOptions `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
func (f *Frontend) InitializeNotifications() error {
|
||||
categoriesLock.Lock()
|
||||
defer categoriesLock.Unlock()
|
||||
categories = make(map[string]frontend.NotificationCategory)
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable: %w", err)
|
||||
}
|
||||
exePath = exe
|
||||
appName = filepath.Base(exePath)
|
||||
|
||||
appGUID, err = getGUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iconPath = filepath.Join(os.TempDir(), appName+appGUID+".png")
|
||||
|
||||
// Create the registry key for the toast activator
|
||||
key, _, err := registry.CreateKey(registry.CURRENT_USER,
|
||||
`Software\Classes\CLSID\`+appGUID+`\LocalServer32`, registry.ALL_ACCESS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create CLSID key: %w", err)
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
if err := key.SetStringValue("", fmt.Sprintf("\"%s\" %%1", exePath)); err != nil {
|
||||
return fmt.Errorf("failed to set CLSID server path: %w", err)
|
||||
}
|
||||
|
||||
toast.SetAppData(toast.AppData{
|
||||
AppID: appName,
|
||||
GUID: appGUID,
|
||||
IconPath: iconPath,
|
||||
ActivationExe: exePath,
|
||||
})
|
||||
|
||||
toast.SetActivationCallback(func(args string, data []toast.UserData) {
|
||||
result := frontend.NotificationResult{}
|
||||
|
||||
actionIdentifier, options, err := parseNotificationResponse(args)
|
||||
|
||||
if err != nil {
|
||||
result.Error = err
|
||||
} else {
|
||||
// Subtitle is retained but was not shown with the notification
|
||||
response := frontend.NotificationResponse{
|
||||
ID: options.ID,
|
||||
ActionIdentifier: actionIdentifier,
|
||||
Title: options.Title,
|
||||
Subtitle: options.Subtitle,
|
||||
Body: options.Body,
|
||||
CategoryID: options.CategoryID,
|
||||
UserInfo: options.Data,
|
||||
}
|
||||
|
||||
if userText, found := getUserText(data); found {
|
||||
response.UserText = userText
|
||||
}
|
||||
|
||||
result.Response = response
|
||||
}
|
||||
|
||||
handleNotificationResult(result)
|
||||
})
|
||||
|
||||
// Register the COM class factory for toast activation.
|
||||
// This is required for Windows to activate the app when users interact with notifications.
|
||||
// The go-toast library's SetAppData and SetActivationCallback handle the callback setup,
|
||||
// but the COM class factory registration is not exposed via public APIs, so we use
|
||||
// go:linkname to access the internal registerClassFactory function.
|
||||
if err := registerToastClassFactory(wintoast.ClassFactory); err != nil {
|
||||
return fmt.Errorf("CoRegisterClassObject failed: %w", err)
|
||||
}
|
||||
|
||||
return loadCategoriesFromRegistry()
|
||||
}
|
||||
|
||||
// registerToastClassFactory registers the COM class factory required for Windows toast notification activation.
|
||||
// This function uses go:linkname to access the unexported registerClassFactory function from go-toast.
|
||||
// The class factory is necessary for Windows COM activation when users click notification actions.
|
||||
// Without this registration, notification actions will not activate the application.
|
||||
//
|
||||
// This is a workaround until go-toast exports this functionality via a public API.
|
||||
// See: https://git.sr.ht/~jackmordaunt/go-toast
|
||||
//
|
||||
//go:linkname registerToastClassFactory git.sr.ht/~jackmordaunt/go-toast/v2/wintoast.registerClassFactory
|
||||
func registerToastClassFactory(factory *wintoast.IClassFactory) error
|
||||
|
||||
// CleanupNotifications is a Windows stub that does nothing.
|
||||
// (Linux-specific cleanup)
|
||||
func (f *Frontend) CleanupNotifications() {
|
||||
// No cleanup needed on Windows
|
||||
}
|
||||
|
||||
func (f *Frontend) IsNotificationAvailable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Frontend) RequestNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *Frontend) CheckNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SendNotification sends a basic notification with a name, title, and body. All other options are ignored on Windows.
|
||||
// (subtitle is only available on macOS and Linux)
|
||||
func (f *Frontend) SendNotification(options frontend.NotificationOptions) error {
|
||||
if err := f.saveIconToDir(); err != nil {
|
||||
f.logger.Warning("Error saving icon: %v", err)
|
||||
}
|
||||
|
||||
n := toast.Notification{
|
||||
Title: options.Title,
|
||||
Body: options.Body,
|
||||
ActivationType: toast.Foreground,
|
||||
ActivationArguments: DefaultActionIdentifier,
|
||||
}
|
||||
|
||||
encodedPayload, err := encodePayload(DefaultActionIdentifier, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification payload: %w", err)
|
||||
}
|
||||
n.ActivationArguments = encodedPayload
|
||||
|
||||
return n.Push()
|
||||
}
|
||||
|
||||
// SendNotificationWithActions sends a notification with additional actions and inputs.
|
||||
// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
|
||||
// If a NotificationCategory is not registered a basic notification will be sent.
|
||||
// (subtitle is only available on macOS and Linux)
|
||||
func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error {
|
||||
if err := f.saveIconToDir(); err != nil {
|
||||
f.logger.Warning("Error saving icon: %v", err)
|
||||
}
|
||||
|
||||
categoriesLock.RLock()
|
||||
nCategory, categoryExists := categories[options.CategoryID]
|
||||
categoriesLock.RUnlock()
|
||||
|
||||
if options.CategoryID == "" || !categoryExists {
|
||||
f.logger.Warning("Category '%s' not found, sending basic notification without actions", options.CategoryID)
|
||||
return f.SendNotification(options)
|
||||
}
|
||||
|
||||
n := toast.Notification{
|
||||
Title: options.Title,
|
||||
Body: options.Body,
|
||||
ActivationType: toast.Foreground,
|
||||
ActivationArguments: DefaultActionIdentifier,
|
||||
}
|
||||
|
||||
for _, action := range nCategory.Actions {
|
||||
n.Actions = append(n.Actions, toast.Action{
|
||||
Content: action.Title,
|
||||
Arguments: action.ID,
|
||||
})
|
||||
}
|
||||
|
||||
if nCategory.HasReplyField {
|
||||
n.Inputs = append(n.Inputs, toast.Input{
|
||||
ID: "userText",
|
||||
Placeholder: nCategory.ReplyPlaceholder,
|
||||
})
|
||||
|
||||
n.Actions = append(n.Actions, toast.Action{
|
||||
Content: nCategory.ReplyButtonTitle,
|
||||
Arguments: "TEXT_REPLY",
|
||||
InputID: "userText",
|
||||
})
|
||||
}
|
||||
|
||||
encodedPayload, err := encodePayload(n.ActivationArguments, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification payload: %w", err)
|
||||
}
|
||||
n.ActivationArguments = encodedPayload
|
||||
|
||||
for index := range n.Actions {
|
||||
encodedPayload, err := encodePayload(n.Actions[index].Arguments, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification payload: %w", err)
|
||||
}
|
||||
n.Actions[index].Arguments = encodedPayload
|
||||
}
|
||||
|
||||
return n.Push()
|
||||
}
|
||||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
// Registering a category with the same name as a previously registered NotificationCategory will override it.
|
||||
func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error {
|
||||
categoriesLock.Lock()
|
||||
defer categoriesLock.Unlock()
|
||||
|
||||
categories[category.ID] = frontend.NotificationCategory{
|
||||
ID: category.ID,
|
||||
Actions: category.Actions,
|
||||
HasReplyField: category.HasReplyField,
|
||||
ReplyPlaceholder: category.ReplyPlaceholder,
|
||||
ReplyButtonTitle: category.ReplyButtonTitle,
|
||||
}
|
||||
|
||||
return saveCategoriesToRegistry()
|
||||
}
|
||||
|
||||
// RemoveNotificationCategory removes a previously registered NotificationCategory.
|
||||
func (f *Frontend) RemoveNotificationCategory(categoryId string) error {
|
||||
categoriesLock.Lock()
|
||||
defer categoriesLock.Unlock()
|
||||
|
||||
delete(categories, categoryId)
|
||||
|
||||
return saveCategoriesToRegistry()
|
||||
}
|
||||
|
||||
// RemoveAllPendingNotifications is a Windows stub that always returns nil.
|
||||
// (macOS and Linux only)
|
||||
func (f *Frontend) RemoveAllPendingNotifications() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePendingNotification is a Windows stub that always returns nil.
|
||||
// (macOS and Linux only)
|
||||
func (f *Frontend) RemovePendingNotification(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllDeliveredNotifications is a Windows stub that always returns nil.
|
||||
// (macOS and Linux only)
|
||||
func (f *Frontend) RemoveAllDeliveredNotifications() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDeliveredNotification is a Windows stub that always returns nil.
|
||||
// (macOS and Linux only)
|
||||
func (f *Frontend) RemoveDeliveredNotification(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNotification is a Windows stub that always returns nil.
|
||||
// (Linux-specific)
|
||||
func (f *Frontend) RemoveNotification(identifier string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) {
|
||||
callbackLock.Lock()
|
||||
defer callbackLock.Unlock()
|
||||
|
||||
notificationResultCallback = callback
|
||||
}
|
||||
|
||||
func (f *Frontend) saveIconToDir() error {
|
||||
iconOnce.Do(func() {
|
||||
hIcon := w32.ExtractIcon(exePath, 0)
|
||||
if hIcon == 0 {
|
||||
iconErr = fmt.Errorf("ExtractIcon failed for %s", exePath)
|
||||
return
|
||||
}
|
||||
defer w32.DestroyIcon(hIcon)
|
||||
iconErr = winc.SaveHIconAsPNG(hIcon, iconPath)
|
||||
})
|
||||
return iconErr
|
||||
}
|
||||
|
||||
func saveCategoriesToRegistry() error {
|
||||
// We assume lock is held by caller
|
||||
|
||||
registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName)
|
||||
|
||||
key, _, err := registry.CreateKey(
|
||||
registry.CURRENT_USER,
|
||||
registryPath,
|
||||
registry.ALL_ACCESS,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
data, err := json.Marshal(categories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return key.SetStringValue(NotificationCategoriesRegistryKey, string(data))
|
||||
}
|
||||
|
||||
func loadCategoriesFromRegistry() error {
|
||||
// We assume lock is held by caller
|
||||
|
||||
registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName)
|
||||
|
||||
key, err := registry.OpenKey(
|
||||
registry.CURRENT_USER,
|
||||
registryPath,
|
||||
registry.QUERY_VALUE,
|
||||
)
|
||||
if err != nil {
|
||||
if err == registry.ErrNotExist {
|
||||
// Not an error, no saved categories
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to open registry key: %w", err)
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
data, _, err := key.GetStringValue(NotificationCategoriesRegistryKey)
|
||||
if err != nil {
|
||||
if err == registry.ErrNotExist {
|
||||
// No value yet, but key exists
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to read categories from registry: %w", err)
|
||||
}
|
||||
|
||||
_categories := make(map[string]frontend.NotificationCategory)
|
||||
if err := json.Unmarshal([]byte(data), &_categories); err != nil {
|
||||
return fmt.Errorf("failed to parse notification categories from registry: %w", err)
|
||||
}
|
||||
|
||||
categories = _categories
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUserText(data []toast.UserData) (string, bool) {
|
||||
for _, d := range data {
|
||||
if d.Key == "userText" {
|
||||
return d.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// encodePayload combines an action ID and user data into a single encoded string
|
||||
func encodePayload(actionID string, options frontend.NotificationOptions) (string, error) {
|
||||
payload := NotificationPayload{
|
||||
Action: actionID,
|
||||
Options: options,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return actionID, err
|
||||
}
|
||||
|
||||
encodedPayload := base64.StdEncoding.EncodeToString(jsonData)
|
||||
return encodedPayload, nil
|
||||
}
|
||||
|
||||
// decodePayload extracts the action ID and user data from an encoded payload
|
||||
func decodePayload(encodedString string) (string, frontend.NotificationOptions, error) {
|
||||
jsonData, err := base64.StdEncoding.DecodeString(encodedString)
|
||||
if err != nil {
|
||||
return encodedString, frontend.NotificationOptions{}, fmt.Errorf("failed to decode base64 payload: %w", err)
|
||||
}
|
||||
|
||||
var payload NotificationPayload
|
||||
if err := json.Unmarshal(jsonData, &payload); err != nil {
|
||||
return encodedString, frontend.NotificationOptions{}, fmt.Errorf("failed to unmarshal notification payload: %w", err)
|
||||
}
|
||||
|
||||
return payload.Action, payload.Options, nil
|
||||
}
|
||||
|
||||
// parseNotificationResponse updated to use structured payload decoding
|
||||
func parseNotificationResponse(response string) (action string, options frontend.NotificationOptions, err error) {
|
||||
actionID, options, err := decodePayload(response)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to decode notification response: %v", err)
|
||||
return response, frontend.NotificationOptions{}, err
|
||||
}
|
||||
|
||||
return actionID, options, nil
|
||||
}
|
||||
|
||||
func handleNotificationResult(result frontend.NotificationResult) {
|
||||
callbackLock.RLock()
|
||||
callback := notificationResultCallback
|
||||
callbackLock.RUnlock()
|
||||
|
||||
if callback != nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Log panic but don't crash the app
|
||||
fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
|
||||
}
|
||||
}()
|
||||
callback(result)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func getGUID() (string, error) {
|
||||
keyPath := ToastRegistryPath + appName
|
||||
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.QUERY_VALUE)
|
||||
if err == nil {
|
||||
guid, _, err := k.GetStringValue(ToastRegistryGuidKey)
|
||||
k.Close()
|
||||
if err == nil && guid != "" {
|
||||
return guid, nil
|
||||
}
|
||||
}
|
||||
|
||||
guid := generateGUID()
|
||||
|
||||
k, _, err = registry.CreateKey(registry.CURRENT_USER, keyPath, registry.WRITE)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create registry key: %w", err)
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
if err := k.SetStringValue(ToastRegistryGuidKey, guid); err != nil {
|
||||
return "", fmt.Errorf("failed to write GUID to registry: %w", err)
|
||||
}
|
||||
|
||||
return guid, nil
|
||||
}
|
||||
|
||||
func generateGUID() string {
|
||||
guid := uuid.New()
|
||||
return fmt.Sprintf("{%s}", guid.String())
|
||||
}
|
||||
129
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/screen.go
generated
vendored
Normal file
129
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/screen.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
func MonitorsEqual(first w32.MONITORINFO, second w32.MONITORINFO) bool {
|
||||
// Checks to make sure all the fields are the same.
|
||||
// A cleaner way would be to check identity of devices. but I couldn't find a way of doing that using the win32 API
|
||||
return first.DwFlags == second.DwFlags &&
|
||||
first.RcMonitor.Top == second.RcMonitor.Top &&
|
||||
first.RcMonitor.Bottom == second.RcMonitor.Bottom &&
|
||||
first.RcMonitor.Right == second.RcMonitor.Right &&
|
||||
first.RcMonitor.Left == second.RcMonitor.Left &&
|
||||
first.RcWork.Top == second.RcWork.Top &&
|
||||
first.RcWork.Bottom == second.RcWork.Bottom &&
|
||||
first.RcWork.Right == second.RcWork.Right &&
|
||||
first.RcWork.Left == second.RcWork.Left
|
||||
}
|
||||
|
||||
func GetMonitorInfo(hMonitor w32.HMONITOR) (*w32.MONITORINFO, error) {
|
||||
// Adapted from winc.utils.getMonitorInfo TODO: add this to win32
|
||||
// See docs for
|
||||
//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
|
||||
|
||||
var info w32.MONITORINFO
|
||||
info.CbSize = uint32(unsafe.Sizeof(info))
|
||||
succeeded := w32.GetMonitorInfo(hMonitor, &info)
|
||||
if !succeeded {
|
||||
return &info, errors.New("Windows call to getMonitorInfo failed")
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func EnumProc(hMonitor w32.HMONITOR, hdcMonitor w32.HDC, lprcMonitor *w32.RECT, screenContainer *ScreenContainer) uintptr {
|
||||
// adapted from https://stackoverflow.com/a/23492886/4188138
|
||||
|
||||
// see docs for the following pages to better understand this function
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
||||
|
||||
ourMonitorData := Screen{}
|
||||
currentMonHndl := w32.MonitorFromWindow(screenContainer.mainWinHandle, w32.MONITOR_DEFAULTTONEAREST)
|
||||
currentMonInfo, currErr := GetMonitorInfo(currentMonHndl)
|
||||
|
||||
if currErr != nil {
|
||||
screenContainer.errors = append(screenContainer.errors, currErr)
|
||||
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
||||
// not sure what the consequences of returning false are, so let's just return true and handle it ourselves
|
||||
return w32.TRUE
|
||||
}
|
||||
|
||||
monInfo, err := GetMonitorInfo(hMonitor)
|
||||
if err != nil {
|
||||
screenContainer.errors = append(screenContainer.errors, err)
|
||||
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
||||
return w32.TRUE
|
||||
}
|
||||
|
||||
width := lprcMonitor.Right - lprcMonitor.Left
|
||||
height := lprcMonitor.Bottom - lprcMonitor.Top
|
||||
ourMonitorData.IsPrimary = monInfo.DwFlags&w32.MONITORINFOF_PRIMARY == 1
|
||||
ourMonitorData.Height = int(height)
|
||||
ourMonitorData.Width = int(width)
|
||||
ourMonitorData.IsCurrent = MonitorsEqual(*currentMonInfo, *monInfo)
|
||||
|
||||
ourMonitorData.PhysicalSize.Width = int(width)
|
||||
ourMonitorData.PhysicalSize.Height = int(height)
|
||||
|
||||
var dpiX, dpiY uint
|
||||
w32.GetDPIForMonitor(hMonitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
|
||||
if dpiX == 0 || dpiY == 0 {
|
||||
screenContainer.errors = append(screenContainer.errors, fmt.Errorf("unable to get DPI for screen"))
|
||||
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
||||
return w32.TRUE
|
||||
}
|
||||
ourMonitorData.Size.Width = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Width, dpiX)
|
||||
ourMonitorData.Size.Height = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Height, dpiY)
|
||||
|
||||
// the reason we need a container is that we have don't know how many times this function will be called
|
||||
// this "append" call could potentially do an allocation and rewrite the pointer to monitors. So we save the pointer in screenContainer.monitors
|
||||
// and retrieve the values after all EnumProc calls
|
||||
// If EnumProc is multi-threaded, this could be problematic. Although, I don't think it is.
|
||||
screenContainer.monitors = append(screenContainer.monitors, ourMonitorData)
|
||||
// let's keep screenContainer.errors the same size as screenContainer.monitors in case we want to match them up later if necessary
|
||||
screenContainer.errors = append(screenContainer.errors, nil)
|
||||
return w32.TRUE
|
||||
}
|
||||
|
||||
type ScreenContainer struct {
|
||||
monitors []Screen
|
||||
errors []error
|
||||
mainWinHandle w32.HWND
|
||||
}
|
||||
|
||||
func GetAllScreens(mainWinHandle w32.HWND) ([]Screen, error) {
|
||||
// TODO fix hack of container sharing by having a proper data sharing mechanism between windows and the runtime
|
||||
monitorContainer := ScreenContainer{mainWinHandle: mainWinHandle}
|
||||
returnErr := error(nil)
|
||||
errorStrings := []string{}
|
||||
|
||||
dc := w32.GetDC(0)
|
||||
defer w32.ReleaseDC(0, dc)
|
||||
succeeded := w32.EnumDisplayMonitors(dc, nil, syscall.NewCallback(EnumProc), unsafe.Pointer(&monitorContainer))
|
||||
if !succeeded {
|
||||
return monitorContainer.monitors, errors.New("Windows call to EnumDisplayMonitors failed")
|
||||
}
|
||||
for idx, err := range monitorContainer.errors {
|
||||
if err != nil {
|
||||
errorStrings = append(errorStrings, fmt.Sprintf("Error from monitor #%v, %v", idx+1, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorStrings) > 0 {
|
||||
returnErr = fmt.Errorf("%v errors encountered: %v", len(errorStrings), errorStrings)
|
||||
}
|
||||
return monitorContainer.monitors, returnErr
|
||||
}
|
||||
136
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/single_instance.go
generated
vendored
Normal file
136
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/single_instance.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"golang.org/x/sys/windows"
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type COPYDATASTRUCT struct {
|
||||
dwData uintptr
|
||||
cbData uint32
|
||||
lpData uintptr
|
||||
}
|
||||
|
||||
// WMCOPYDATA_SINGLE_INSTANCE_DATA we define our own type for WM_COPYDATA message
|
||||
const WMCOPYDATA_SINGLE_INSTANCE_DATA = 1542
|
||||
|
||||
func SendMessage(hwnd w32.HWND, data string) {
|
||||
arrUtf16, _ := syscall.UTF16FromString(data)
|
||||
|
||||
pCopyData := new(COPYDATASTRUCT)
|
||||
pCopyData.dwData = WMCOPYDATA_SINGLE_INSTANCE_DATA
|
||||
pCopyData.cbData = uint32(len(arrUtf16)*2 + 1)
|
||||
pCopyData.lpData = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(data)))
|
||||
|
||||
w32.SendMessage(hwnd, w32.WM_COPYDATA, 0, uintptr(unsafe.Pointer(pCopyData)))
|
||||
}
|
||||
|
||||
// SetupSingleInstance single instance Windows app
|
||||
func SetupSingleInstance(uniqueId string) {
|
||||
id := "wails-app-" + uniqueId
|
||||
|
||||
className := id + "-sic"
|
||||
windowName := id + "-siw"
|
||||
mutexName := id + "sim"
|
||||
|
||||
_, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(mutexName))
|
||||
|
||||
if err != nil {
|
||||
if err == windows.ERROR_ALREADY_EXISTS {
|
||||
// app is already running
|
||||
hwnd := w32.FindWindowW(windows.StringToUTF16Ptr(className), windows.StringToUTF16Ptr(windowName))
|
||||
|
||||
if hwnd != 0 {
|
||||
data := options.SecondInstanceData{
|
||||
Args: os.Args[1:],
|
||||
}
|
||||
data.WorkingDirectory, err = os.Getwd()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get working directory: %v", err)
|
||||
return
|
||||
}
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Printf("Failed to marshal data: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
SendMessage(hwnd, string(serialized))
|
||||
// exit second instance of app after sending message
|
||||
os.Exit(0)
|
||||
}
|
||||
// if we got any other unknown error we will just start new application instance
|
||||
}
|
||||
} else {
|
||||
createEventTargetWindow(className, windowName)
|
||||
}
|
||||
}
|
||||
|
||||
func createEventTargetWindow(className string, windowName string) w32.HWND {
|
||||
// callback handler in the event target window
|
||||
wndProc := func(
|
||||
hwnd w32.HWND, msg uint32, wparam w32.WPARAM, lparam w32.LPARAM,
|
||||
) w32.LRESULT {
|
||||
if msg == w32.WM_COPYDATA {
|
||||
ldata := (*COPYDATASTRUCT)(unsafe.Pointer(lparam))
|
||||
|
||||
if ldata.dwData == WMCOPYDATA_SINGLE_INSTANCE_DATA {
|
||||
serialized := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ldata.lpData)))
|
||||
|
||||
var secondInstanceData options.SecondInstanceData
|
||||
|
||||
err := json.Unmarshal([]byte(serialized), &secondInstanceData)
|
||||
|
||||
if err == nil {
|
||||
secondInstanceBuffer <- secondInstanceData
|
||||
}
|
||||
}
|
||||
|
||||
return w32.LRESULT(0)
|
||||
}
|
||||
|
||||
return w32.DefWindowProc(hwnd, msg, wparam, lparam)
|
||||
}
|
||||
|
||||
var class w32.WNDCLASSEX
|
||||
class.Size = uint32(unsafe.Sizeof(class))
|
||||
class.Style = 0
|
||||
class.WndProc = syscall.NewCallback(wndProc)
|
||||
class.ClsExtra = 0
|
||||
class.WndExtra = 0
|
||||
class.Instance = w32.GetModuleHandle("")
|
||||
class.Icon = 0
|
||||
class.Cursor = 0
|
||||
class.Background = 0
|
||||
class.MenuName = nil
|
||||
class.ClassName = windows.StringToUTF16Ptr(className)
|
||||
class.IconSm = 0
|
||||
|
||||
w32.RegisterClassEx(&class)
|
||||
|
||||
// create event window that will not be visible for user
|
||||
hwnd := w32.CreateWindowEx(
|
||||
0,
|
||||
windows.StringToUTF16Ptr(className),
|
||||
windows.StringToUTF16Ptr(windowName),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
w32.HWND_MESSAGE,
|
||||
0,
|
||||
w32.GetModuleHandle(""),
|
||||
nil,
|
||||
)
|
||||
|
||||
return hwnd
|
||||
}
|
||||
67
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/theme.go
generated
vendored
Normal file
67
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/theme.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||
)
|
||||
|
||||
func (w *Window) UpdateTheme() {
|
||||
|
||||
// Don't redraw theme if nothing has changed
|
||||
if !w.themeChanged {
|
||||
return
|
||||
}
|
||||
w.themeChanged = false
|
||||
|
||||
if win32.IsCurrentlyHighContrastMode() {
|
||||
return
|
||||
}
|
||||
|
||||
if !win32.SupportsThemes() {
|
||||
return
|
||||
}
|
||||
|
||||
var isDarkMode bool
|
||||
switch w.theme {
|
||||
case windows.SystemDefault:
|
||||
isDarkMode = win32.IsCurrentlyDarkMode()
|
||||
case windows.Dark:
|
||||
isDarkMode = true
|
||||
case windows.Light:
|
||||
isDarkMode = false
|
||||
}
|
||||
win32.SetTheme(w.Handle(), isDarkMode)
|
||||
|
||||
// Custom theme processing
|
||||
winOptions := w.frontendOptions.Windows
|
||||
var customTheme *windows.ThemeSettings
|
||||
if winOptions != nil {
|
||||
customTheme = winOptions.CustomTheme
|
||||
}
|
||||
// Custom theme
|
||||
if win32.SupportsCustomThemes() && customTheme != nil {
|
||||
if w.isActive {
|
||||
if isDarkMode {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.DarkModeTitleBar)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.DarkModeTitleText)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.DarkModeBorder)
|
||||
} else {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.LightModeTitleBar)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.LightModeTitleText)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.LightModeBorder)
|
||||
}
|
||||
} else {
|
||||
if isDarkMode {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.DarkModeTitleBarInactive)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.DarkModeTitleTextInactive)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.DarkModeBorderInactive)
|
||||
} else {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.LightModeTitleBarInactive)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.LightModeTitleTextInactive)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.LightModeBorderInactive)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
143
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/clipboard.go
generated
vendored
Normal file
143
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/clipboard.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Based on code originally from https://github.com/atotto/clipboard. Copyright (c) 2013 Ato Araki. All rights reserved.
|
||||
*/
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
cfUnicodetext = 13
|
||||
gmemMoveable = 0x0002
|
||||
)
|
||||
|
||||
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
|
||||
func waitOpenClipboard() error {
|
||||
started := time.Now()
|
||||
limit := started.Add(time.Second)
|
||||
var r uintptr
|
||||
var err error
|
||||
for time.Now().Before(limit) {
|
||||
r, _, err = procOpenClipboard.Call(0)
|
||||
if r != 0 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func GetClipboardText() (string, error) {
|
||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if formatAvailable, _, err := procIsClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
|
||||
return "", err
|
||||
}
|
||||
err := waitOpenClipboard()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h, _, err := procGetClipboardData.Call(cfUnicodetext)
|
||||
if h == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
l, _, err := kernelGlobalLock.Call(h)
|
||||
if l == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
|
||||
|
||||
r, _, err := kernelGlobalUnlock.Call(h)
|
||||
if r == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
closed, _, err := procCloseClipboard.Call()
|
||||
if closed == 0 {
|
||||
return "", err
|
||||
}
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func SetClipboardText(text string) error {
|
||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
err := waitOpenClipboard()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err := procEmptyClipboard.Call(0)
|
||||
if r == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := syscall.UTF16FromString(text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// "If the hMem parameter identifies a memory object, the object must have
|
||||
// been allocated using the function with the GMEM_MOVEABLE flag."
|
||||
h, _, err := kernelGlobalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
|
||||
if h == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if h != 0 {
|
||||
kernelGlobalFree.Call(h)
|
||||
}
|
||||
}()
|
||||
|
||||
l, _, err := kernelGlobalLock.Call(h)
|
||||
if l == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err = kernelLstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
|
||||
if r == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err = kernelGlobalUnlock.Call(h)
|
||||
if r == 0 {
|
||||
if err.(syscall.Errno) != 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r, _, err = procSetClipboardData.Call(cfUnicodetext, h)
|
||||
if r == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
h = 0 // suppress deferred cleanup
|
||||
closed, _, err := procCloseClipboard.Call()
|
||||
if closed == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
57
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/consts.go
generated
vendored
Normal file
57
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/consts.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
//go:build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
||||
)
|
||||
|
||||
type HRESULT int32
|
||||
type HANDLE uintptr
|
||||
type HMONITOR HANDLE
|
||||
|
||||
var (
|
||||
moduser32 = syscall.NewLazyDLL("user32.dll")
|
||||
procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW")
|
||||
procGetWindowLong = moduser32.NewProc("GetWindowLongW")
|
||||
procSetClassLong = moduser32.NewProc("SetClassLongW")
|
||||
procSetClassLongPtr = moduser32.NewProc("SetClassLongPtrW")
|
||||
procShowWindow = moduser32.NewProc("ShowWindow")
|
||||
procIsWindowVisible = moduser32.NewProc("IsWindowVisible")
|
||||
procGetWindowRect = moduser32.NewProc("GetWindowRect")
|
||||
procGetMonitorInfo = moduser32.NewProc("GetMonitorInfoW")
|
||||
procMonitorFromWindow = moduser32.NewProc("MonitorFromWindow")
|
||||
procIsClipboardFormatAvailable = moduser32.NewProc("IsClipboardFormatAvailable")
|
||||
procOpenClipboard = moduser32.NewProc("OpenClipboard")
|
||||
procCloseClipboard = moduser32.NewProc("CloseClipboard")
|
||||
procEmptyClipboard = moduser32.NewProc("EmptyClipboard")
|
||||
procGetClipboardData = moduser32.NewProc("GetClipboardData")
|
||||
procSetClipboardData = moduser32.NewProc("SetClipboardData")
|
||||
)
|
||||
var (
|
||||
moddwmapi = syscall.NewLazyDLL("dwmapi.dll")
|
||||
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
|
||||
procDwmExtendFrameIntoClientArea = moddwmapi.NewProc("DwmExtendFrameIntoClientArea")
|
||||
)
|
||||
var (
|
||||
modwingdi = syscall.NewLazyDLL("gdi32.dll")
|
||||
procCreateSolidBrush = modwingdi.NewProc("CreateSolidBrush")
|
||||
)
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
kernelGlobalAlloc = kernel32.NewProc("GlobalAlloc")
|
||||
kernelGlobalFree = kernel32.NewProc("GlobalFree")
|
||||
kernelGlobalLock = kernel32.NewProc("GlobalLock")
|
||||
kernelGlobalUnlock = kernel32.NewProc("GlobalUnlock")
|
||||
kernelLstrcpy = kernel32.NewProc("lstrcpyW")
|
||||
)
|
||||
|
||||
var windowsVersion, _ = operatingsystem.GetWindowsVersionInfo()
|
||||
|
||||
func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
|
||||
return windowsVersion.Major >= major &&
|
||||
windowsVersion.Minor >= minor &&
|
||||
windowsVersion.Build >= buildNumber
|
||||
}
|
||||
119
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/theme.go
generated
vendored
Normal file
119
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/theme.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
//go:build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
type DWMWINDOWATTRIBUTE int32
|
||||
|
||||
const DwmwaUseImmersiveDarkModeBefore20h1 DWMWINDOWATTRIBUTE = 19
|
||||
const DwmwaUseImmersiveDarkMode DWMWINDOWATTRIBUTE = 20
|
||||
const DwmwaBorderColor DWMWINDOWATTRIBUTE = 34
|
||||
const DwmwaCaptionColor DWMWINDOWATTRIBUTE = 35
|
||||
const DwmwaTextColor DWMWINDOWATTRIBUTE = 36
|
||||
const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38
|
||||
|
||||
const SPI_GETHIGHCONTRAST = 0x0042
|
||||
const HCF_HIGHCONTRASTON = 0x00000001
|
||||
|
||||
// BackdropType defines the type of translucency we wish to use
|
||||
type BackdropType int32
|
||||
|
||||
func dwmSetWindowAttribute(hwnd uintptr, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) {
|
||||
ret, _, err := procDwmSetWindowAttribute.Call(
|
||||
hwnd,
|
||||
uintptr(dwAttribute),
|
||||
uintptr(pvAttribute),
|
||||
cbAttribute)
|
||||
if ret != 0 {
|
||||
_ = err
|
||||
// println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func SupportsThemes() bool {
|
||||
// We can't support Windows versions before 17763
|
||||
return IsWindowsVersionAtLeast(10, 0, 17763)
|
||||
}
|
||||
|
||||
func SupportsCustomThemes() bool {
|
||||
return IsWindowsVersionAtLeast(10, 0, 17763)
|
||||
}
|
||||
|
||||
func SupportsBackdropTypes() bool {
|
||||
return IsWindowsVersionAtLeast(10, 0, 22621)
|
||||
}
|
||||
|
||||
func SupportsImmersiveDarkMode() bool {
|
||||
return IsWindowsVersionAtLeast(10, 0, 18985)
|
||||
}
|
||||
|
||||
func SetTheme(hwnd uintptr, useDarkMode bool) {
|
||||
if SupportsThemes() {
|
||||
attr := DwmwaUseImmersiveDarkModeBefore20h1
|
||||
if SupportsImmersiveDarkMode() {
|
||||
attr = DwmwaUseImmersiveDarkMode
|
||||
}
|
||||
var winDark int32
|
||||
if useDarkMode {
|
||||
winDark = 1
|
||||
}
|
||||
dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark))
|
||||
}
|
||||
}
|
||||
|
||||
func EnableTranslucency(hwnd uintptr, backdrop BackdropType) {
|
||||
if SupportsBackdropTypes() {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaSystemBackdropType, unsafe.Pointer(&backdrop), unsafe.Sizeof(backdrop))
|
||||
} else {
|
||||
println("Warning: Translucency type unavailable on Windows < 22621")
|
||||
}
|
||||
}
|
||||
|
||||
func SetTitleBarColour(hwnd uintptr, titleBarColour int32) {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour))
|
||||
}
|
||||
|
||||
func SetTitleTextColour(hwnd uintptr, titleTextColour int32) {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour))
|
||||
}
|
||||
|
||||
func SetBorderColour(hwnd uintptr, titleBorderColour int32) {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour))
|
||||
}
|
||||
|
||||
func IsCurrentlyDarkMode() bool {
|
||||
key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
AppsUseLightTheme, _, err := key.GetIntegerValue("AppsUseLightTheme")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return AppsUseLightTheme == 0
|
||||
}
|
||||
|
||||
type highContrast struct {
|
||||
CbSize uint32
|
||||
DwFlags uint32
|
||||
LpszDefaultScheme *int16
|
||||
}
|
||||
|
||||
func IsCurrentlyHighContrastMode() bool {
|
||||
var result highContrast
|
||||
result.CbSize = uint32(unsafe.Sizeof(result))
|
||||
res, _, err := procSystemParametersInfo.Call(SPI_GETHIGHCONTRAST, uintptr(result.CbSize), uintptr(unsafe.Pointer(&result)), 0)
|
||||
if res == 0 {
|
||||
_ = err
|
||||
return false
|
||||
}
|
||||
r := result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON
|
||||
return r
|
||||
}
|
||||
223
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/window.go
generated
vendored
Normal file
223
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/window.go
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
//go:build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
)
|
||||
|
||||
const (
|
||||
WS_MAXIMIZE = 0x01000000
|
||||
WS_MINIMIZE = 0x20000000
|
||||
|
||||
GWL_STYLE = -16
|
||||
|
||||
MONITOR_DEFAULTTOPRIMARY = 0x00000001
|
||||
)
|
||||
|
||||
const (
|
||||
SW_HIDE = 0
|
||||
SW_NORMAL = 1
|
||||
SW_SHOWNORMAL = 1
|
||||
SW_SHOWMINIMIZED = 2
|
||||
SW_MAXIMIZE = 3
|
||||
SW_SHOWMAXIMIZED = 3
|
||||
SW_SHOWNOACTIVATE = 4
|
||||
SW_SHOW = 5
|
||||
SW_MINIMIZE = 6
|
||||
SW_SHOWMINNOACTIVE = 7
|
||||
SW_SHOWNA = 8
|
||||
SW_RESTORE = 9
|
||||
SW_SHOWDEFAULT = 10
|
||||
SW_FORCEMINIMIZE = 11
|
||||
)
|
||||
|
||||
const (
|
||||
GCLP_HBRBACKGROUND int32 = -10
|
||||
)
|
||||
|
||||
// Power
|
||||
const (
|
||||
// WM_POWERBROADCAST - Notifies applications that a power-management event has occurred.
|
||||
WM_POWERBROADCAST = 536
|
||||
|
||||
// PBT_APMPOWERSTATUSCHANGE - Power status has changed.
|
||||
PBT_APMPOWERSTATUSCHANGE = 10
|
||||
|
||||
// PBT_APMRESUMEAUTOMATIC -Operation is resuming automatically from a low-power state. This message is sent every time the system resumes.
|
||||
PBT_APMRESUMEAUTOMATIC = 18
|
||||
|
||||
// PBT_APMRESUMESUSPEND - Operation is resuming from a low-power state. This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key.
|
||||
PBT_APMRESUMESUSPEND = 7
|
||||
|
||||
// PBT_APMSUSPEND - System is suspending operation.
|
||||
PBT_APMSUSPEND = 4
|
||||
|
||||
// PBT_POWERSETTINGCHANGE - A power setting change event has been received.
|
||||
PBT_POWERSETTINGCHANGE = 32787
|
||||
)
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773244.aspx
|
||||
type MARGINS struct {
|
||||
CxLeftWidth, CxRightWidth, CyTopHeight, CyBottomHeight int32
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162897.aspx
|
||||
type RECT struct {
|
||||
Left, Top, Right, Bottom int32
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145065.aspx
|
||||
type MONITORINFO struct {
|
||||
CbSize uint32
|
||||
RcMonitor RECT
|
||||
RcWork RECT
|
||||
DwFlags uint32
|
||||
}
|
||||
|
||||
func ExtendFrameIntoClientArea(hwnd uintptr, extend bool) {
|
||||
// -1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11)
|
||||
// Also shows the caption buttons if transparent ant translucent but they don't work.
|
||||
// 0: Adds the default frame styling but no aero shadow, does not show the caption buttons.
|
||||
// 1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) but no caption buttons
|
||||
// are shown if transparent ant translucent.
|
||||
var margins MARGINS
|
||||
if extend {
|
||||
margins = MARGINS{1, 1, 1, 1} // Only extend 1 pixel to have the default frame styling but no caption buttons
|
||||
}
|
||||
if err := dwmExtendFrameIntoClientArea(hwnd, &margins); err != nil {
|
||||
log.Fatal(fmt.Errorf("DwmExtendFrameIntoClientArea failed: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
func IsVisible(hwnd uintptr) bool {
|
||||
ret, _, _ := procIsWindowVisible.Call(hwnd)
|
||||
return ret != 0
|
||||
}
|
||||
|
||||
func IsWindowFullScreen(hwnd uintptr) bool {
|
||||
wRect := GetWindowRect(hwnd)
|
||||
m := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY)
|
||||
var mi MONITORINFO
|
||||
mi.CbSize = uint32(unsafe.Sizeof(mi))
|
||||
if !GetMonitorInfo(m, &mi) {
|
||||
return false
|
||||
}
|
||||
return wRect.Left == mi.RcMonitor.Left &&
|
||||
wRect.Top == mi.RcMonitor.Top &&
|
||||
wRect.Right == mi.RcMonitor.Right &&
|
||||
wRect.Bottom == mi.RcMonitor.Bottom
|
||||
}
|
||||
|
||||
func IsWindowMaximised(hwnd uintptr) bool {
|
||||
style := uint32(getWindowLong(hwnd, GWL_STYLE))
|
||||
return style&WS_MAXIMIZE != 0
|
||||
}
|
||||
func IsWindowMinimised(hwnd uintptr) bool {
|
||||
style := uint32(getWindowLong(hwnd, GWL_STYLE))
|
||||
return style&WS_MINIMIZE != 0
|
||||
}
|
||||
|
||||
func RestoreWindow(hwnd uintptr) {
|
||||
showWindow(hwnd, SW_RESTORE)
|
||||
}
|
||||
|
||||
func ShowWindow(hwnd uintptr) {
|
||||
showWindow(hwnd, SW_SHOW)
|
||||
}
|
||||
|
||||
func ShowWindowMaximised(hwnd uintptr) {
|
||||
showWindow(hwnd, SW_MAXIMIZE)
|
||||
}
|
||||
func ShowWindowMinimised(hwnd uintptr) {
|
||||
showWindow(hwnd, SW_MINIMIZE)
|
||||
}
|
||||
|
||||
func SetBackgroundColour(hwnd uintptr, r, g, b uint8) {
|
||||
col := winc.RGB(r, g, b)
|
||||
hbrush, _, _ := procCreateSolidBrush.Call(uintptr(col))
|
||||
setClassLongPtr(hwnd, GCLP_HBRBACKGROUND, hbrush)
|
||||
}
|
||||
|
||||
func IsWindowNormal(hwnd uintptr) bool {
|
||||
return !IsWindowMaximised(hwnd) && !IsWindowMinimised(hwnd) && !IsWindowFullScreen(hwnd)
|
||||
}
|
||||
|
||||
func dwmExtendFrameIntoClientArea(hwnd uintptr, margins *MARGINS) error {
|
||||
ret, _, _ := procDwmExtendFrameIntoClientArea.Call(
|
||||
hwnd,
|
||||
uintptr(unsafe.Pointer(margins)))
|
||||
|
||||
if ret != 0 {
|
||||
return syscall.GetLastError()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setClassLongPtr(hwnd uintptr, param int32, val uintptr) bool {
|
||||
proc := procSetClassLongPtr
|
||||
if strconv.IntSize == 32 {
|
||||
/*
|
||||
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslongptrw
|
||||
Note: To write code that is compatible with both 32-bit and 64-bit Windows, use SetClassLongPtr.
|
||||
When compiling for 32-bit Windows, SetClassLongPtr is defined as a call to the SetClassLong function
|
||||
|
||||
=> We have to do this dynamically when directly calling the DLL procedures
|
||||
*/
|
||||
proc = procSetClassLong
|
||||
}
|
||||
|
||||
ret, _, _ := proc.Call(
|
||||
hwnd,
|
||||
uintptr(param),
|
||||
val,
|
||||
)
|
||||
return ret != 0
|
||||
}
|
||||
|
||||
func getWindowLong(hwnd uintptr, index int) int32 {
|
||||
ret, _, _ := procGetWindowLong.Call(
|
||||
hwnd,
|
||||
uintptr(index))
|
||||
|
||||
return int32(ret)
|
||||
}
|
||||
|
||||
func showWindow(hwnd uintptr, cmdshow int) bool {
|
||||
ret, _, _ := procShowWindow.Call(
|
||||
hwnd,
|
||||
uintptr(cmdshow))
|
||||
return ret != 0
|
||||
}
|
||||
|
||||
func GetWindowRect(hwnd uintptr) *RECT {
|
||||
var rect RECT
|
||||
procGetWindowRect.Call(
|
||||
hwnd,
|
||||
uintptr(unsafe.Pointer(&rect)))
|
||||
|
||||
return &rect
|
||||
}
|
||||
|
||||
func MonitorFromWindow(hwnd uintptr, dwFlags uint32) HMONITOR {
|
||||
ret, _, _ := procMonitorFromWindow.Call(
|
||||
hwnd,
|
||||
uintptr(dwFlags),
|
||||
)
|
||||
return HMONITOR(ret)
|
||||
}
|
||||
|
||||
func GetMonitorInfo(hMonitor HMONITOR, lmpi *MONITORINFO) bool {
|
||||
ret, _, _ := procGetMonitorInfo.Call(
|
||||
uintptr(hMonitor),
|
||||
uintptr(unsafe.Pointer(lmpi)),
|
||||
)
|
||||
return ret != 0
|
||||
}
|
||||
12
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/.gitignore
generated
vendored
Normal file
12
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
12
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/AUTHORS
generated
vendored
Normal file
12
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# This is the official list of 'Winc' authors for copyright purposes.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
# Contributors
|
||||
# ============
|
||||
|
||||
Tad Vizbaras <tad@etasoft.com>
|
||||
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/LICENSE
generated
vendored
Normal file
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 winc Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
181
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/README.md
generated
vendored
Normal file
181
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/README.md
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
# winc
|
||||
|
||||
** This is a fork of [tadvi/winc](https://github.com/tadvi/winc) for the sole purpose of integration
|
||||
with [Wails](https://github.com/wailsapp/wails). This repository comes with ***no support*** **
|
||||
|
||||
Common library for Go GUI apps on Windows. It is for Windows OS only. This makes library smaller than some other UI
|
||||
libraries for Go.
|
||||
|
||||
Design goals: minimalism and simplicity.
|
||||
|
||||
## Dependencies
|
||||
|
||||
No other dependencies except Go standard library.
|
||||
|
||||
## Building
|
||||
|
||||
If you want to package icon files and other resources into binary **rsrc** tool is recommended:
|
||||
|
||||
rsrc -manifest app.manifest -ico=app.ico,application_edit.ico,application_error.ico -o rsrc.syso
|
||||
|
||||
Here app.manifest is XML file in format:
|
||||
|
||||
```
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="App" type="win32"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
```
|
||||
|
||||
Most Windows applications do not display command prompt. Build your Go project with flag to indicate that it is Windows
|
||||
GUI binary:
|
||||
|
||||
go build -ldflags="-H windowsgui"
|
||||
|
||||
## Samples
|
||||
|
||||
Best way to learn how to use the library is to look at the included **examples** projects.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Make sure you have a working Go installation and build environment, see more for details on page below.
|
||||
http://golang.org/doc/install
|
||||
|
||||
2. go get github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc
|
||||
|
||||
## Icons
|
||||
|
||||
When rsrc is used to pack icons into binary it displays IDs of the packed icons.
|
||||
|
||||
```
|
||||
rsrc -manifest app.manifest -ico=app.ico,lightning.ico,edit.ico,application_error.ico -o rsrc.syso
|
||||
Manifest ID: 1
|
||||
Icon app.ico ID: 10
|
||||
Icon lightning.ico ID: 13
|
||||
Icon edit.ico ID: 16
|
||||
Icon application_error.ico ID: 19
|
||||
```
|
||||
|
||||
Use IDs to reference packed icons.
|
||||
|
||||
```
|
||||
const myIcon = 13
|
||||
|
||||
btn.SetResIcon(myIcon) // Set icon on the button.
|
||||
```
|
||||
|
||||
Included source **examples** use basic building via `release.bat` files. Note that icon IDs are order dependent. So if
|
||||
you change they order in -ico flag then icon IDs will be different. If you want to keep order the same, just add new
|
||||
icons to the end of -ico comma separated list.
|
||||
|
||||
## Layout Manager
|
||||
|
||||
SimpleDock is default layout manager.
|
||||
|
||||
Current design of docking and split views allows building simple apps but if you need to have multiple split views in
|
||||
few different directions you might need to create your own layout manager.
|
||||
|
||||
Important point is to have **one** control inside SimpleDock set to dock as **Fill**. Controls that are not set to any
|
||||
docking get placed using SetPos() function. So you can have Panel set to dock at the Top and then have another dock to
|
||||
arrange controls inside that Panel or have controls placed using SetPos() at fixed positions.
|
||||
|
||||

|
||||
|
||||
This is basic layout. Instead of toolbars and status bar you can have Panel or any other control that can resize. Panel
|
||||
can have its own internal Dock that will arrange other controls inside of it.
|
||||
|
||||

|
||||
|
||||
This is layout with extra control(s) on the left. Left side is usually treeview or listview.
|
||||
|
||||
The rule is simple: you either dock controls using SimpleDock OR use SetPos() to set them at fixed positions. That's it.
|
||||
|
||||
At some point **winc** may get more sophisticated layout manager.
|
||||
|
||||
## Dialog Screens
|
||||
|
||||
Dialog screens are not based on Windows resource files (.rc). They are just windows with controls placed at fixed
|
||||
coordinates. This works fine for dialog screens up to 10-14 controls.
|
||||
|
||||
# Minimal Demo
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mainWindow := winc.NewForm(nil)
|
||||
mainWindow.SetSize(400, 300) // (width, height)
|
||||
mainWindow.SetText("Hello World Demo")
|
||||
|
||||
edt := winc.NewEdit(mainWindow)
|
||||
edt.SetPos(10, 20)
|
||||
// Most Controls have default size unless SetSize is called.
|
||||
edt.SetText("edit text")
|
||||
|
||||
btn := winc.NewPushButton(mainWindow)
|
||||
btn.SetText("Show or Hide")
|
||||
btn.SetPos(40, 50) // (x, y)
|
||||
btn.SetSize(100, 40) // (width, height)
|
||||
btn.OnClick().Bind(func(e *winc.Event) {
|
||||
if edt.Visible() {
|
||||
edt.Hide()
|
||||
} else {
|
||||
edt.Show()
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.Center()
|
||||
mainWindow.Show()
|
||||
mainWindow.OnClose().Bind(wndOnClose)
|
||||
|
||||
winc.RunMainLoop() // Must call to start event loop.
|
||||
}
|
||||
|
||||
func wndOnClose(arg *winc.Event) {
|
||||
winc.Exit()
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
Result of running sample_minimal.
|
||||
|
||||
## Create Your Own
|
||||
|
||||
It is good practice to create your own controls based on existing structures and event model. Library contains some of
|
||||
the controls built that way: IconButton (button.go), ErrorPanel (panel.go), MultiEdit (edit.go), etc. Please look at
|
||||
existing controls as examples before building your own.
|
||||
|
||||
When designing your own controls keep in mind that types have to be converted from Go into Win32 API and back. This is
|
||||
usually due to string UTF8 and UTF16 conversions. But there are other types of conversions too.
|
||||
|
||||
When developing your own controls you might also need to:
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
|
||||
w32 has Win32 API low level constants and functions.
|
||||
|
||||
Look at **sample_control** for example of custom built window.
|
||||
|
||||
## Companion Package
|
||||
|
||||
[Go package for Windows Systray icon, menu and notifications](https://github.com/tadvi/systray)
|
||||
|
||||
## Credits
|
||||
|
||||
This library is built on
|
||||
|
||||
[AllenDang/gform Windows GUI framework for Go](https://github.com/AllenDang/gform)
|
||||
|
||||
**winc** takes most design decisions from **gform** and adds many more controls and code samples to it.
|
||||
|
||||
|
||||
109
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/app.go
generated
vendored
Normal file
109
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/app.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
package winc
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
var (
|
||||
// resource compilation tool assigns app.ico ID of 3
|
||||
// rsrc -manifest app.manifest -ico app.ico -o rsrc.syso
|
||||
AppIconID = 3
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
gAppInstance = w32.GetModuleHandle("")
|
||||
if gAppInstance == 0 {
|
||||
panic("Error occurred in App.Init")
|
||||
}
|
||||
|
||||
// Initialize the common controls
|
||||
var initCtrls w32.INITCOMMONCONTROLSEX
|
||||
initCtrls.DwSize = uint32(unsafe.Sizeof(initCtrls))
|
||||
initCtrls.DwICC =
|
||||
w32.ICC_LISTVIEW_CLASSES | w32.ICC_PROGRESS_CLASS | w32.ICC_TAB_CLASSES |
|
||||
w32.ICC_TREEVIEW_CLASSES | w32.ICC_BAR_CLASSES
|
||||
|
||||
w32.InitCommonControlsEx(&initCtrls)
|
||||
}
|
||||
|
||||
// SetAppIcon sets resource icon ID for the apps windows.
|
||||
func SetAppIcon(appIconID int) {
|
||||
AppIconID = appIconID
|
||||
}
|
||||
|
||||
func GetAppInstance() w32.HINSTANCE {
|
||||
return gAppInstance
|
||||
}
|
||||
|
||||
func PreTranslateMessage(msg *w32.MSG) bool {
|
||||
// This functions is called by the MessageLoop. It processes the
|
||||
// keyboard accelerator keys and calls Controller.PreTranslateMessage for
|
||||
// keyboard and mouse events.
|
||||
|
||||
processed := false
|
||||
|
||||
if (msg.Message >= w32.WM_KEYFIRST && msg.Message <= w32.WM_KEYLAST) ||
|
||||
(msg.Message >= w32.WM_MOUSEFIRST && msg.Message <= w32.WM_MOUSELAST) {
|
||||
|
||||
if msg.Hwnd != 0 {
|
||||
if controller := GetMsgHandler(msg.Hwnd); controller != nil {
|
||||
// Search the chain of parents for pretranslated messages.
|
||||
for p := controller; p != nil; p = p.Parent() {
|
||||
|
||||
if processed = p.PreTranslateMessage(msg); processed {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processed
|
||||
}
|
||||
|
||||
// RunMainLoop processes messages in main application loop.
|
||||
func RunMainLoop() int {
|
||||
m := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{})))))
|
||||
defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(m)))
|
||||
|
||||
for w32.GetMessage(m, 0, 0, 0) != 0 {
|
||||
|
||||
if !PreTranslateMessage(m) {
|
||||
w32.TranslateMessage(m)
|
||||
w32.DispatchMessage(m)
|
||||
}
|
||||
}
|
||||
|
||||
w32.GdiplusShutdown()
|
||||
return int(m.WParam)
|
||||
}
|
||||
|
||||
// PostMessages processes recent messages. Sometimes helpful for instant window refresh.
|
||||
func PostMessages() {
|
||||
m := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{})))))
|
||||
defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(m)))
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
if w32.GetMessage(m, 0, 0, 0) != 0 {
|
||||
if !PreTranslateMessage(m) {
|
||||
w32.TranslateMessage(m)
|
||||
w32.DispatchMessage(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Exit() {
|
||||
w32.PostQuitMessage(0)
|
||||
}
|
||||
112
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/bitmap.go
generated
vendored
Normal file
112
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/bitmap.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type Bitmap struct {
|
||||
handle w32.HBITMAP
|
||||
width, height int
|
||||
}
|
||||
|
||||
func assembleBitmapFromHBITMAP(hbitmap w32.HBITMAP) (*Bitmap, error) {
|
||||
var dib w32.DIBSECTION
|
||||
if w32.GetObject(w32.HGDIOBJ(hbitmap), unsafe.Sizeof(dib), unsafe.Pointer(&dib)) == 0 {
|
||||
return nil, errors.New("GetObject for HBITMAP failed")
|
||||
}
|
||||
|
||||
return &Bitmap{
|
||||
handle: hbitmap,
|
||||
width: int(dib.DsBmih.BiWidth),
|
||||
height: int(dib.DsBmih.BiHeight),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewBitmapFromFile(filepath string, background Color) (*Bitmap, error) {
|
||||
var gpBitmap *uintptr
|
||||
var err error
|
||||
|
||||
gpBitmap, err = w32.GdipCreateBitmapFromFile(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer w32.GdipDisposeImage(gpBitmap)
|
||||
|
||||
var hbitmap w32.HBITMAP
|
||||
// Reverse RGB to BGR to satisfy gdiplus color schema.
|
||||
hbitmap, err = w32.GdipCreateHBITMAPFromBitmap(gpBitmap, uint32(RGB(background.B(), background.G(), background.R())))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return assembleBitmapFromHBITMAP(hbitmap)
|
||||
}
|
||||
|
||||
func NewBitmapFromResource(instance w32.HINSTANCE, resName *uint16, resType *uint16, background Color) (*Bitmap, error) {
|
||||
var gpBitmap *uintptr
|
||||
var err error
|
||||
var hRes w32.HRSRC
|
||||
|
||||
hRes, err = w32.FindResource(w32.HMODULE(instance), resName, resType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resSize := w32.SizeofResource(w32.HMODULE(instance), hRes)
|
||||
pResData := w32.LockResource(w32.LoadResource(w32.HMODULE(instance), hRes))
|
||||
resBuffer := w32.GlobalAlloc(w32.GMEM_MOVEABLE, resSize)
|
||||
pResBuffer := w32.GlobalLock(resBuffer)
|
||||
w32.MoveMemory(pResBuffer, pResData, resSize)
|
||||
|
||||
stream := w32.CreateStreamOnHGlobal(resBuffer, false)
|
||||
|
||||
gpBitmap, err = w32.GdipCreateBitmapFromStream(stream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stream.Release()
|
||||
defer w32.GlobalUnlock(resBuffer)
|
||||
defer w32.GlobalFree(resBuffer)
|
||||
defer w32.GdipDisposeImage(gpBitmap)
|
||||
|
||||
var hbitmap w32.HBITMAP
|
||||
// Reverse gform.RGB to BGR to satisfy gdiplus color schema.
|
||||
hbitmap, err = w32.GdipCreateHBITMAPFromBitmap(gpBitmap, uint32(RGB(background.B(), background.G(), background.R())))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return assembleBitmapFromHBITMAP(hbitmap)
|
||||
}
|
||||
|
||||
func (bm *Bitmap) Dispose() {
|
||||
if bm.handle != 0 {
|
||||
w32.DeleteObject(w32.HGDIOBJ(bm.handle))
|
||||
bm.handle = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (bm *Bitmap) GetHBITMAP() w32.HBITMAP {
|
||||
return bm.handle
|
||||
}
|
||||
|
||||
func (bm *Bitmap) Size() (int, int) {
|
||||
return bm.width, bm.height
|
||||
}
|
||||
|
||||
func (bm *Bitmap) Height() int {
|
||||
return bm.height
|
||||
}
|
||||
|
||||
func (bm *Bitmap) Width() int {
|
||||
return bm.width
|
||||
}
|
||||
74
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/brush.go
generated
vendored
Normal file
74
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/brush.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
var DefaultBackgroundBrush = NewSystemColorBrush(w32.COLOR_BTNFACE)
|
||||
|
||||
type Brush struct {
|
||||
hBrush w32.HBRUSH
|
||||
logBrush w32.LOGBRUSH
|
||||
}
|
||||
|
||||
func NewSolidColorBrush(color Color) *Brush {
|
||||
lb := w32.LOGBRUSH{LbStyle: w32.BS_SOLID, LbColor: w32.COLORREF(color)}
|
||||
hBrush := w32.CreateBrushIndirect(&lb)
|
||||
if hBrush == 0 {
|
||||
panic("Faild to create solid color brush")
|
||||
}
|
||||
|
||||
return &Brush{hBrush, lb}
|
||||
}
|
||||
|
||||
func NewSystemColorBrush(colorIndex int) *Brush {
|
||||
//lb := w32.LOGBRUSH{LbStyle: w32.BS_SOLID, LbColor: w32.COLORREF(colorIndex)}
|
||||
lb := w32.LOGBRUSH{LbStyle: w32.BS_NULL}
|
||||
hBrush := w32.GetSysColorBrush(colorIndex)
|
||||
if hBrush == 0 {
|
||||
panic("GetSysColorBrush failed")
|
||||
}
|
||||
return &Brush{hBrush, lb}
|
||||
}
|
||||
|
||||
func NewHatchedColorBrush(color Color) *Brush {
|
||||
lb := w32.LOGBRUSH{LbStyle: w32.BS_HATCHED, LbColor: w32.COLORREF(color)}
|
||||
hBrush := w32.CreateBrushIndirect(&lb)
|
||||
if hBrush == 0 {
|
||||
panic("Faild to create solid color brush")
|
||||
}
|
||||
|
||||
return &Brush{hBrush, lb}
|
||||
}
|
||||
|
||||
func NewNullBrush() *Brush {
|
||||
lb := w32.LOGBRUSH{LbStyle: w32.BS_NULL}
|
||||
hBrush := w32.CreateBrushIndirect(&lb)
|
||||
if hBrush == 0 {
|
||||
panic("Failed to create null brush")
|
||||
}
|
||||
|
||||
return &Brush{hBrush, lb}
|
||||
}
|
||||
|
||||
func (br *Brush) GetHBRUSH() w32.HBRUSH {
|
||||
return br.hBrush
|
||||
}
|
||||
|
||||
func (br *Brush) GetLOGBRUSH() *w32.LOGBRUSH {
|
||||
return &br.logBrush
|
||||
}
|
||||
|
||||
func (br *Brush) Dispose() {
|
||||
if br.hBrush != 0 {
|
||||
w32.DeleteObject(w32.HGDIOBJ(br.hBrush))
|
||||
br.hBrush = 0
|
||||
}
|
||||
}
|
||||
156
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/buttons.go
generated
vendored
Normal file
156
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/buttons.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type Button struct {
|
||||
ControlBase
|
||||
onClick EventManager
|
||||
}
|
||||
|
||||
func (bt *Button) OnClick() *EventManager {
|
||||
return &bt.onClick
|
||||
}
|
||||
|
||||
func (bt *Button) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_COMMAND:
|
||||
bt.onClick.Fire(NewEvent(bt, nil))
|
||||
/*case w32.WM_LBUTTONDOWN:
|
||||
w32.SetCapture(bt.Handle())
|
||||
case w32.WM_LBUTTONUP:
|
||||
w32.ReleaseCapture()*/
|
||||
/*case win.WM_GETDLGCODE:
|
||||
println("GETDLGCODE")*/
|
||||
}
|
||||
return w32.DefWindowProc(bt.hwnd, msg, wparam, lparam)
|
||||
//return bt.W32Control.WndProc(msg, wparam, lparam)
|
||||
}
|
||||
|
||||
func (bt *Button) Checked() bool {
|
||||
result := w32.SendMessage(bt.hwnd, w32.BM_GETCHECK, 0, 0)
|
||||
return result == w32.BST_CHECKED
|
||||
}
|
||||
|
||||
func (bt *Button) SetChecked(checked bool) {
|
||||
wparam := w32.BST_CHECKED
|
||||
if !checked {
|
||||
wparam = w32.BST_UNCHECKED
|
||||
}
|
||||
w32.SendMessage(bt.hwnd, w32.BM_SETCHECK, uintptr(wparam), 0)
|
||||
}
|
||||
|
||||
// SetIcon sets icon on the button. Recommended icons are 32x32 with 32bit color depth.
|
||||
func (bt *Button) SetIcon(ico *Icon) {
|
||||
w32.SendMessage(bt.hwnd, w32.BM_SETIMAGE, w32.IMAGE_ICON, uintptr(ico.handle))
|
||||
}
|
||||
|
||||
func (bt *Button) SetResIcon(iconID uint16) {
|
||||
if ico, err := NewIconFromResource(GetAppInstance(), iconID); err == nil {
|
||||
bt.SetIcon(ico)
|
||||
return
|
||||
}
|
||||
panic(fmt.Sprintf("missing icon with icon ID: %d", iconID))
|
||||
}
|
||||
|
||||
type PushButton struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewPushButton(parent Controller) *PushButton {
|
||||
pb := new(PushButton)
|
||||
|
||||
pb.InitControl("BUTTON", parent, 0, w32.BS_PUSHBUTTON|w32.WS_TABSTOP|w32.WS_VISIBLE|w32.WS_CHILD)
|
||||
RegMsgHandler(pb)
|
||||
|
||||
pb.SetFont(DefaultFont)
|
||||
pb.SetText("Button")
|
||||
pb.SetSize(100, 22)
|
||||
|
||||
return pb
|
||||
}
|
||||
|
||||
// SetDefault is used for dialogs to set default button.
|
||||
func (pb *PushButton) SetDefault() {
|
||||
pb.SetAndClearStyleBits(w32.BS_DEFPUSHBUTTON, w32.BS_PUSHBUTTON)
|
||||
}
|
||||
|
||||
// IconButton does not display text, requires SetResIcon call.
|
||||
type IconButton struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewIconButton(parent Controller) *IconButton {
|
||||
pb := new(IconButton)
|
||||
|
||||
pb.InitControl("BUTTON", parent, 0, w32.BS_ICON|w32.WS_TABSTOP|w32.WS_VISIBLE|w32.WS_CHILD)
|
||||
RegMsgHandler(pb)
|
||||
|
||||
pb.SetFont(DefaultFont)
|
||||
// even if text would be set it would not be displayed
|
||||
pb.SetText("")
|
||||
pb.SetSize(100, 22)
|
||||
|
||||
return pb
|
||||
}
|
||||
|
||||
type CheckBox struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewCheckBox(parent Controller) *CheckBox {
|
||||
cb := new(CheckBox)
|
||||
|
||||
cb.InitControl("BUTTON", parent, 0, w32.WS_TABSTOP|w32.WS_VISIBLE|w32.WS_CHILD|w32.BS_AUTOCHECKBOX)
|
||||
RegMsgHandler(cb)
|
||||
|
||||
cb.SetFont(DefaultFont)
|
||||
cb.SetText("CheckBox")
|
||||
cb.SetSize(100, 22)
|
||||
|
||||
return cb
|
||||
}
|
||||
|
||||
type RadioButton struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewRadioButton(parent Controller) *RadioButton {
|
||||
rb := new(RadioButton)
|
||||
|
||||
rb.InitControl("BUTTON", parent, 0, w32.WS_TABSTOP|w32.WS_VISIBLE|w32.WS_CHILD|w32.BS_AUTORADIOBUTTON)
|
||||
RegMsgHandler(rb)
|
||||
|
||||
rb.SetFont(DefaultFont)
|
||||
rb.SetText("RadioButton")
|
||||
rb.SetSize(100, 22)
|
||||
|
||||
return rb
|
||||
}
|
||||
|
||||
type GroupBox struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewGroupBox(parent Controller) *GroupBox {
|
||||
gb := new(GroupBox)
|
||||
|
||||
gb.InitControl("BUTTON", parent, 0, w32.WS_CHILD|w32.WS_VISIBLE|w32.WS_GROUP|w32.BS_GROUPBOX)
|
||||
RegMsgHandler(gb)
|
||||
|
||||
gb.SetFont(DefaultFont)
|
||||
gb.SetText("GroupBox")
|
||||
gb.SetSize(100, 100)
|
||||
|
||||
return gb
|
||||
}
|
||||
159
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/canvas.go
generated
vendored
Normal file
159
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/canvas.go
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type Canvas struct {
|
||||
hwnd w32.HWND
|
||||
hdc w32.HDC
|
||||
doNotDispose bool
|
||||
}
|
||||
|
||||
var nullBrush = NewNullBrush()
|
||||
|
||||
func NewCanvasFromHwnd(hwnd w32.HWND) *Canvas {
|
||||
hdc := w32.GetDC(hwnd)
|
||||
if hdc == 0 {
|
||||
panic(fmt.Sprintf("Create canvas from %v failed.", hwnd))
|
||||
}
|
||||
|
||||
return &Canvas{hwnd: hwnd, hdc: hdc, doNotDispose: false}
|
||||
}
|
||||
|
||||
func NewCanvasFromHDC(hdc w32.HDC) *Canvas {
|
||||
if hdc == 0 {
|
||||
panic("Cannot create canvas from invalid HDC.")
|
||||
}
|
||||
|
||||
return &Canvas{hdc: hdc, doNotDispose: true}
|
||||
}
|
||||
|
||||
func (ca *Canvas) Dispose() {
|
||||
if !ca.doNotDispose && ca.hdc != 0 {
|
||||
if ca.hwnd == 0 {
|
||||
w32.DeleteDC(ca.hdc)
|
||||
} else {
|
||||
w32.ReleaseDC(ca.hwnd, ca.hdc)
|
||||
}
|
||||
|
||||
ca.hdc = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawBitmap(bmp *Bitmap, x, y int) {
|
||||
cdc := w32.CreateCompatibleDC(0)
|
||||
defer w32.DeleteDC(cdc)
|
||||
|
||||
hbmpOld := w32.SelectObject(cdc, w32.HGDIOBJ(bmp.GetHBITMAP()))
|
||||
defer w32.SelectObject(cdc, w32.HGDIOBJ(hbmpOld))
|
||||
|
||||
w, h := bmp.Size()
|
||||
|
||||
w32.BitBlt(ca.hdc, x, y, w, h, cdc, 0, 0, w32.SRCCOPY)
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawStretchedBitmap(bmp *Bitmap, rect *Rect) {
|
||||
cdc := w32.CreateCompatibleDC(0)
|
||||
defer w32.DeleteDC(cdc)
|
||||
|
||||
hbmpOld := w32.SelectObject(cdc, w32.HGDIOBJ(bmp.GetHBITMAP()))
|
||||
defer w32.SelectObject(cdc, w32.HGDIOBJ(hbmpOld))
|
||||
|
||||
w, h := bmp.Size()
|
||||
|
||||
rc := rect.GetW32Rect()
|
||||
w32.StretchBlt(ca.hdc, int(rc.Left), int(rc.Top), int(rc.Right), int(rc.Bottom), cdc, 0, 0, w, h, w32.SRCCOPY)
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawIcon(ico *Icon, x, y int) bool {
|
||||
return w32.DrawIcon(ca.hdc, x, y, ico.Handle())
|
||||
}
|
||||
|
||||
// DrawFillRect draw and fill rectangle with color.
|
||||
func (ca *Canvas) DrawFillRect(rect *Rect, pen *Pen, brush *Brush) {
|
||||
w32Rect := rect.GetW32Rect()
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
previousBrush := w32.SelectObject(ca.hdc, w32.HGDIOBJ(brush.GetHBRUSH()))
|
||||
defer w32.SelectObject(ca.hdc, previousBrush)
|
||||
|
||||
w32.Rectangle(ca.hdc, w32Rect.Left, w32Rect.Top, w32Rect.Right, w32Rect.Bottom)
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawRect(rect *Rect, pen *Pen) {
|
||||
w32Rect := rect.GetW32Rect()
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
// nullBrush is used to make interior of the rect transparent
|
||||
previousBrush := w32.SelectObject(ca.hdc, w32.HGDIOBJ(nullBrush.GetHBRUSH()))
|
||||
defer w32.SelectObject(ca.hdc, previousBrush)
|
||||
|
||||
w32.Rectangle(ca.hdc, w32Rect.Left, w32Rect.Top, w32Rect.Right, w32Rect.Bottom)
|
||||
}
|
||||
|
||||
func (ca *Canvas) FillRect(rect *Rect, brush *Brush) {
|
||||
w32.FillRect(ca.hdc, rect.GetW32Rect(), brush.GetHBRUSH())
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawEllipse(rect *Rect, pen *Pen) {
|
||||
w32Rect := rect.GetW32Rect()
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
// nullBrush is used to make interior of the rect transparent
|
||||
previousBrush := w32.SelectObject(ca.hdc, w32.HGDIOBJ(nullBrush.GetHBRUSH()))
|
||||
defer w32.SelectObject(ca.hdc, previousBrush)
|
||||
|
||||
w32.Ellipse(ca.hdc, w32Rect.Left, w32Rect.Top, w32Rect.Right, w32Rect.Bottom)
|
||||
}
|
||||
|
||||
// DrawFillEllipse draw and fill ellipse with color.
|
||||
func (ca *Canvas) DrawFillEllipse(rect *Rect, pen *Pen, brush *Brush) {
|
||||
w32Rect := rect.GetW32Rect()
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
previousBrush := w32.SelectObject(ca.hdc, w32.HGDIOBJ(brush.GetHBRUSH()))
|
||||
defer w32.SelectObject(ca.hdc, previousBrush)
|
||||
|
||||
w32.Ellipse(ca.hdc, w32Rect.Left, w32Rect.Top, w32Rect.Right, w32Rect.Bottom)
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawLine(x, y, x2, y2 int, pen *Pen) {
|
||||
w32.MoveToEx(ca.hdc, x, y, nil)
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
w32.LineTo(ca.hdc, int32(x2), int32(y2))
|
||||
}
|
||||
|
||||
// Refer win32 DrawText document for uFormat.
|
||||
func (ca *Canvas) DrawText(text string, rect *Rect, format uint, font *Font, textColor Color) {
|
||||
previousFont := w32.SelectObject(ca.hdc, w32.HGDIOBJ(font.GetHFONT()))
|
||||
defer w32.SelectObject(ca.hdc, w32.HGDIOBJ(previousFont))
|
||||
|
||||
previousBkMode := w32.SetBkMode(ca.hdc, w32.TRANSPARENT)
|
||||
defer w32.SetBkMode(ca.hdc, previousBkMode)
|
||||
|
||||
previousTextColor := w32.SetTextColor(ca.hdc, w32.COLORREF(textColor))
|
||||
defer w32.SetTextColor(ca.hdc, previousTextColor)
|
||||
|
||||
w32.DrawText(ca.hdc, text, len(text), rect.GetW32Rect(), format)
|
||||
}
|
||||
26
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/color.go
generated
vendored
Normal file
26
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/color.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
type Color uint32
|
||||
|
||||
func RGB(r, g, b byte) Color {
|
||||
return Color(uint32(r) | uint32(g)<<8 | uint32(b)<<16)
|
||||
}
|
||||
|
||||
func (c Color) R() byte {
|
||||
return byte(c & 0xff)
|
||||
}
|
||||
|
||||
func (c Color) G() byte {
|
||||
return byte((c >> 8) & 0xff)
|
||||
}
|
||||
|
||||
func (c Color) B() byte {
|
||||
return byte((c >> 16) & 0xff)
|
||||
}
|
||||
70
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/combobox.go
generated
vendored
Normal file
70
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/combobox.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type ComboBox struct {
|
||||
ControlBase
|
||||
onSelectedChange EventManager
|
||||
}
|
||||
|
||||
func NewComboBox(parent Controller) *ComboBox {
|
||||
cb := new(ComboBox)
|
||||
|
||||
cb.InitControl("COMBOBOX", parent, 0, w32.WS_CHILD|w32.WS_VISIBLE|w32.WS_TABSTOP|w32.WS_VSCROLL|w32.CBS_DROPDOWNLIST)
|
||||
RegMsgHandler(cb)
|
||||
|
||||
cb.SetFont(DefaultFont)
|
||||
cb.SetSize(200, 400)
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *ComboBox) DeleteAllItems() bool {
|
||||
return w32.SendMessage(cb.hwnd, w32.CB_RESETCONTENT, 0, 0) == w32.TRUE
|
||||
}
|
||||
|
||||
func (cb *ComboBox) InsertItem(index int, str string) bool {
|
||||
lp := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(str)))
|
||||
return w32.SendMessage(cb.hwnd, w32.CB_INSERTSTRING, uintptr(index), lp) != w32.CB_ERR
|
||||
}
|
||||
|
||||
func (cb *ComboBox) DeleteItem(index int) bool {
|
||||
return w32.SendMessage(cb.hwnd, w32.CB_DELETESTRING, uintptr(index), 0) != w32.CB_ERR
|
||||
}
|
||||
|
||||
func (cb *ComboBox) SelectedItem() int {
|
||||
return int(int32(w32.SendMessage(cb.hwnd, w32.CB_GETCURSEL, 0, 0)))
|
||||
}
|
||||
|
||||
func (cb *ComboBox) SetSelectedItem(value int) bool {
|
||||
return int(int32(w32.SendMessage(cb.hwnd, w32.CB_SETCURSEL, uintptr(value), 0))) == value
|
||||
}
|
||||
|
||||
func (cb *ComboBox) OnSelectedChange() *EventManager {
|
||||
return &cb.onSelectedChange
|
||||
}
|
||||
|
||||
// Message processor
|
||||
func (cb *ComboBox) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_COMMAND:
|
||||
code := w32.HIWORD(uint32(wparam))
|
||||
|
||||
switch code {
|
||||
case w32.CBN_SELCHANGE:
|
||||
cb.onSelectedChange.Fire(NewEvent(cb, nil))
|
||||
}
|
||||
}
|
||||
return w32.DefWindowProc(cb.hwnd, msg, wparam, lparam)
|
||||
//return cb.W32Control.WndProc(msg, wparam, lparam)
|
||||
}
|
||||
125
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/commondlgs.go
generated
vendored
Normal file
125
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/commondlgs.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
func genOFN(parent Controller, title, filter string, filterIndex uint, initialDir string, buf []uint16) *w32.OPENFILENAME {
|
||||
var ofn w32.OPENFILENAME
|
||||
ofn.StructSize = uint32(unsafe.Sizeof(ofn))
|
||||
ofn.Owner = parent.Handle()
|
||||
|
||||
if filter != "" {
|
||||
filterBuf := make([]uint16, len(filter)+1)
|
||||
copy(filterBuf, syscall.StringToUTF16(filter))
|
||||
// Replace '|' with the expected '\0'
|
||||
for i, c := range filterBuf {
|
||||
if byte(c) == '|' {
|
||||
filterBuf[i] = uint16(0)
|
||||
}
|
||||
}
|
||||
ofn.Filter = &filterBuf[0]
|
||||
ofn.FilterIndex = uint32(filterIndex)
|
||||
}
|
||||
|
||||
ofn.File = &buf[0]
|
||||
ofn.MaxFile = uint32(len(buf))
|
||||
|
||||
if initialDir != "" {
|
||||
ofn.InitialDir = syscall.StringToUTF16Ptr(initialDir)
|
||||
}
|
||||
if title != "" {
|
||||
ofn.Title = syscall.StringToUTF16Ptr(title)
|
||||
}
|
||||
|
||||
ofn.Flags = w32.OFN_FILEMUSTEXIST
|
||||
return &ofn
|
||||
}
|
||||
|
||||
func ShowOpenFileDlg(parent Controller, title, filter string, filterIndex uint, initialDir string) (filePath string, accepted bool) {
|
||||
buf := make([]uint16, 1024)
|
||||
ofn := genOFN(parent, title, filter, filterIndex, initialDir, buf)
|
||||
|
||||
if accepted = w32.GetOpenFileName(ofn); accepted {
|
||||
filePath = syscall.UTF16ToString(buf)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ShowSaveFileDlg(parent Controller, title, filter string, filterIndex uint, initialDir string) (filePath string, accepted bool) {
|
||||
buf := make([]uint16, 1024)
|
||||
ofn := genOFN(parent, title, filter, filterIndex, initialDir, buf)
|
||||
|
||||
if accepted = w32.GetSaveFileName(ofn); accepted {
|
||||
filePath = syscall.UTF16ToString(buf)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ShowBrowseFolderDlg(parent Controller, title string) (folder string, accepted bool) {
|
||||
var bi w32.BROWSEINFO
|
||||
bi.Owner = parent.Handle()
|
||||
bi.Title = syscall.StringToUTF16Ptr(title)
|
||||
bi.Flags = w32.BIF_RETURNONLYFSDIRS | w32.BIF_NEWDIALOGSTYLE
|
||||
|
||||
w32.CoInitialize()
|
||||
ret := w32.SHBrowseForFolder(&bi)
|
||||
w32.CoUninitialize()
|
||||
|
||||
folder = w32.SHGetPathFromIDList(ret)
|
||||
accepted = folder != ""
|
||||
return
|
||||
}
|
||||
|
||||
// MsgBoxOkCancel basic pop up message. Returns 1 for OK and 2 for CANCEL.
|
||||
func MsgBoxOkCancel(parent Controller, title, caption string) int {
|
||||
return MsgBox(parent, title, caption, w32.MB_ICONEXCLAMATION|w32.MB_OKCANCEL)
|
||||
}
|
||||
|
||||
func MsgBoxYesNo(parent Controller, title, caption string) int {
|
||||
return MsgBox(parent, title, caption, w32.MB_ICONEXCLAMATION|w32.MB_YESNO)
|
||||
}
|
||||
|
||||
func MsgBoxOk(parent Controller, title, caption string) {
|
||||
MsgBox(parent, title, caption, w32.MB_ICONINFORMATION|w32.MB_OK)
|
||||
}
|
||||
|
||||
// Warningf is generic warning message with OK and Cancel buttons. Returns 1 for OK.
|
||||
func Warningf(parent Controller, format string, data ...interface{}) int {
|
||||
caption := fmt.Sprintf(format, data...)
|
||||
return MsgBox(parent, "Warning", caption, w32.MB_ICONWARNING|w32.MB_OKCANCEL)
|
||||
}
|
||||
|
||||
// Printf is generic info message with OK button.
|
||||
func Printf(parent Controller, format string, data ...interface{}) {
|
||||
caption := fmt.Sprintf(format, data...)
|
||||
MsgBox(parent, "Information", caption, w32.MB_ICONINFORMATION|w32.MB_OK)
|
||||
}
|
||||
|
||||
// Errorf is generic error message with OK button.
|
||||
func Errorf(parent Controller, format string, data ...interface{}) {
|
||||
caption := fmt.Sprintf(format, data...)
|
||||
MsgBox(parent, "Error", caption, w32.MB_ICONERROR|w32.MB_OK)
|
||||
}
|
||||
|
||||
func MsgBox(parent Controller, title, caption string, flags uint) int {
|
||||
var result int
|
||||
if parent != nil {
|
||||
result = w32.MessageBox(parent.Handle(), caption, title, flags)
|
||||
} else {
|
||||
result = w32.MessageBox(0, caption, title, flags)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
560
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/controlbase.go
generated
vendored
Normal file
560
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/controlbase.go
generated
vendored
Normal file
@@ -0,0 +1,560 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type ControlBase struct {
|
||||
hwnd w32.HWND
|
||||
font *Font
|
||||
parent Controller
|
||||
contextMenu *MenuItem
|
||||
|
||||
isForm bool
|
||||
|
||||
minWidth, minHeight int
|
||||
maxWidth, maxHeight int
|
||||
|
||||
// General events
|
||||
onCreate EventManager
|
||||
onClose EventManager
|
||||
|
||||
// Focus events
|
||||
onKillFocus EventManager
|
||||
onSetFocus EventManager
|
||||
|
||||
// Drag and drop events
|
||||
onDropFiles EventManager
|
||||
|
||||
// Mouse events
|
||||
onLBDown EventManager
|
||||
onLBUp EventManager
|
||||
onLBDbl EventManager
|
||||
onMBDown EventManager
|
||||
onMBUp EventManager
|
||||
onRBDown EventManager
|
||||
onRBUp EventManager
|
||||
onRBDbl EventManager
|
||||
onMouseMove EventManager
|
||||
|
||||
// use MouseControl to capture onMouseHover and onMouseLeave events.
|
||||
onMouseHover EventManager
|
||||
onMouseLeave EventManager
|
||||
|
||||
// Keyboard events
|
||||
onKeyUp EventManager
|
||||
|
||||
// Paint events
|
||||
onPaint EventManager
|
||||
onSize EventManager
|
||||
|
||||
m sync.Mutex
|
||||
dispatchq []func()
|
||||
}
|
||||
|
||||
// InitControl is called by controls: edit, button, treeview, listview, and so on.
|
||||
func (cba *ControlBase) InitControl(className string, parent Controller, exstyle, style uint) {
|
||||
cba.hwnd = CreateWindow(className, parent, exstyle, style)
|
||||
if cba.hwnd == 0 {
|
||||
panic("cannot create window for " + className)
|
||||
}
|
||||
cba.parent = parent
|
||||
}
|
||||
|
||||
// InitWindow is called by custom window based controls such as split, panel, etc.
|
||||
func (cba *ControlBase) InitWindow(className string, parent Controller, exstyle, style uint) {
|
||||
RegClassOnlyOnce(className)
|
||||
cba.hwnd = CreateWindow(className, parent, exstyle, style)
|
||||
if cba.hwnd == 0 {
|
||||
panic("cannot create window for " + className)
|
||||
}
|
||||
cba.parent = parent
|
||||
}
|
||||
|
||||
// SetTheme for TreeView and ListView controls.
|
||||
func (cba *ControlBase) SetTheme(appName string) error {
|
||||
if hr := w32.SetWindowTheme(cba.hwnd, syscall.StringToUTF16Ptr(appName), nil); w32.FAILED(hr) {
|
||||
return fmt.Errorf("SetWindowTheme %d", hr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Handle() w32.HWND {
|
||||
return cba.hwnd
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetHandle(hwnd w32.HWND) {
|
||||
cba.hwnd = hwnd
|
||||
}
|
||||
|
||||
func (cba *ControlBase) GetWindowDPI() (w32.UINT, w32.UINT) {
|
||||
if w32.HasGetDpiForWindowFunc() {
|
||||
// GetDpiForWindow is supported beginning with Windows 10, 1607 and is the most accureate
|
||||
// one, especially it is consistent with the WM_DPICHANGED event.
|
||||
dpi := w32.GetDpiForWindow(cba.hwnd)
|
||||
return dpi, dpi
|
||||
}
|
||||
|
||||
if w32.HasGetDPIForMonitorFunc() {
|
||||
// GetDpiForWindow is supported beginning with Windows 8.1
|
||||
monitor := w32.MonitorFromWindow(cba.hwnd, w32.MONITOR_DEFAULTTONEAREST)
|
||||
if monitor == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
var dpiX, dpiY w32.UINT
|
||||
w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
|
||||
return dpiX, dpiY
|
||||
}
|
||||
|
||||
// If none of the above is supported fallback to the System DPI.
|
||||
screen := w32.GetDC(0)
|
||||
x := w32.GetDeviceCaps(screen, w32.LOGPIXELSX)
|
||||
y := w32.GetDeviceCaps(screen, w32.LOGPIXELSY)
|
||||
w32.ReleaseDC(0, screen)
|
||||
return w32.UINT(x), w32.UINT(y)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetAndClearStyleBits(set, clear uint32) error {
|
||||
style := uint32(w32.GetWindowLong(cba.hwnd, w32.GWL_STYLE))
|
||||
if style == 0 {
|
||||
return fmt.Errorf("GetWindowLong")
|
||||
}
|
||||
|
||||
if newStyle := style&^clear | set; newStyle != style {
|
||||
if w32.SetWindowLong(cba.hwnd, w32.GWL_STYLE, newStyle) == 0 {
|
||||
return fmt.Errorf("SetWindowLong")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetIsForm(isform bool) {
|
||||
cba.isForm = isform
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetText(caption string) {
|
||||
w32.SetWindowText(cba.hwnd, caption)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Text() string {
|
||||
return w32.GetWindowText(cba.hwnd)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Close() {
|
||||
UnRegMsgHandler(cba.hwnd)
|
||||
w32.DestroyWindow(cba.hwnd)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetTranslucentBackground() {
|
||||
var accent = w32.ACCENT_POLICY{
|
||||
AccentState: w32.ACCENT_ENABLE_BLURBEHIND,
|
||||
}
|
||||
var data w32.WINDOWCOMPOSITIONATTRIBDATA
|
||||
data.Attrib = w32.WCA_ACCENT_POLICY
|
||||
data.PvData = unsafe.Pointer(&accent)
|
||||
data.CbData = unsafe.Sizeof(accent)
|
||||
|
||||
w32.SetWindowCompositionAttribute(cba.hwnd, &data)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetContentProtection(enable bool) {
|
||||
if enable {
|
||||
w32.SetWindowDisplayAffinity(uintptr(cba.hwnd), w32.WDA_EXCLUDEFROMCAPTURE)
|
||||
} else {
|
||||
w32.SetWindowDisplayAffinity(uintptr(cba.hwnd), w32.WDA_NONE)
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (cba *ControlBase) clampSize(width, height int) (int, int) {
|
||||
if cba.minWidth != 0 {
|
||||
width = max(width, cba.minWidth)
|
||||
}
|
||||
if cba.maxWidth != 0 {
|
||||
width = min(width, cba.maxWidth)
|
||||
}
|
||||
if cba.minHeight != 0 {
|
||||
height = max(height, cba.minHeight)
|
||||
}
|
||||
if cba.maxHeight != 0 {
|
||||
height = min(height, cba.maxHeight)
|
||||
}
|
||||
return width, height
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetSize(width, height int) {
|
||||
x, y := cba.Pos()
|
||||
width, height = cba.clampSize(width, height)
|
||||
width, height = cba.scaleWithWindowDPI(width, height)
|
||||
w32.MoveWindow(cba.hwnd, x, y, width, height, true)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetMinSize(width, height int) {
|
||||
cba.minWidth = width
|
||||
cba.minHeight = height
|
||||
|
||||
// Ensure we set max if min > max
|
||||
if cba.maxWidth > 0 {
|
||||
cba.maxWidth = max(cba.minWidth, cba.maxWidth)
|
||||
}
|
||||
if cba.maxHeight > 0 {
|
||||
cba.maxHeight = max(cba.minHeight, cba.maxHeight)
|
||||
}
|
||||
|
||||
x, y := cba.Pos()
|
||||
currentWidth, currentHeight := cba.Size()
|
||||
clampedWidth, clampedHeight := cba.clampSize(currentWidth, currentHeight)
|
||||
if clampedWidth != currentWidth || clampedHeight != currentHeight {
|
||||
w32.MoveWindow(cba.hwnd, x, y, clampedWidth, clampedHeight, true)
|
||||
}
|
||||
}
|
||||
func (cba *ControlBase) SetMaxSize(width, height int) {
|
||||
cba.maxWidth = width
|
||||
cba.maxHeight = height
|
||||
|
||||
// Ensure we set min if max > min
|
||||
if cba.maxWidth > 0 {
|
||||
cba.minWidth = min(cba.maxWidth, cba.minWidth)
|
||||
}
|
||||
if cba.maxHeight > 0 {
|
||||
cba.minHeight = min(cba.maxHeight, cba.minHeight)
|
||||
}
|
||||
|
||||
x, y := cba.Pos()
|
||||
currentWidth, currentHeight := cba.Size()
|
||||
clampedWidth, clampedHeight := cba.clampSize(currentWidth, currentHeight)
|
||||
if clampedWidth != currentWidth || clampedHeight != currentHeight {
|
||||
w32.MoveWindow(cba.hwnd, x, y, clampedWidth, clampedHeight, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Size() (width, height int) {
|
||||
rect := w32.GetWindowRect(cba.hwnd)
|
||||
width = int(rect.Right - rect.Left)
|
||||
height = int(rect.Bottom - rect.Top)
|
||||
width, height = cba.scaleToDefaultDPI(width, height)
|
||||
return
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Width() int {
|
||||
rect := w32.GetWindowRect(cba.hwnd)
|
||||
return int(rect.Right - rect.Left)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Height() int {
|
||||
rect := w32.GetWindowRect(cba.hwnd)
|
||||
return int(rect.Bottom - rect.Top)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetPos(x, y int) {
|
||||
info := getMonitorInfo(cba.hwnd)
|
||||
workRect := info.RcWork
|
||||
|
||||
w32.SetWindowPos(cba.hwnd, w32.HWND_TOP, int(workRect.Left)+x, int(workRect.Top)+y, 0, 0, w32.SWP_NOSIZE)
|
||||
}
|
||||
func (cba *ControlBase) SetAlwaysOnTop(b bool) {
|
||||
if b {
|
||||
w32.SetWindowPos(cba.hwnd, w32.HWND_TOPMOST, 0, 0, 0, 0, w32.SWP_NOSIZE|w32.SWP_NOMOVE)
|
||||
} else {
|
||||
w32.SetWindowPos(cba.hwnd, w32.HWND_NOTOPMOST, 0, 0, 0, 0, w32.SWP_NOSIZE|w32.SWP_NOMOVE)
|
||||
}
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Pos() (x, y int) {
|
||||
rect := w32.GetWindowRect(cba.hwnd)
|
||||
x = int(rect.Left)
|
||||
y = int(rect.Top)
|
||||
if !cba.isForm && cba.parent != nil {
|
||||
x, y, _ = w32.ScreenToClient(cba.parent.Handle(), x, y)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Visible() bool {
|
||||
return w32.IsWindowVisible(cba.hwnd)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) ToggleVisible() bool {
|
||||
visible := w32.IsWindowVisible(cba.hwnd)
|
||||
if visible {
|
||||
cba.Hide()
|
||||
} else {
|
||||
cba.Show()
|
||||
}
|
||||
return !visible
|
||||
}
|
||||
|
||||
func (cba *ControlBase) ContextMenu() *MenuItem {
|
||||
return cba.contextMenu
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetContextMenu(menu *MenuItem) {
|
||||
cba.contextMenu = menu
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Bounds() *Rect {
|
||||
rect := w32.GetWindowRect(cba.hwnd)
|
||||
if cba.isForm {
|
||||
return &Rect{*rect}
|
||||
}
|
||||
|
||||
return ScreenToClientRect(cba.hwnd, rect)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) ClientRect() *Rect {
|
||||
rect := w32.GetClientRect(cba.hwnd)
|
||||
return ScreenToClientRect(cba.hwnd, rect)
|
||||
}
|
||||
func (cba *ControlBase) ClientWidth() int {
|
||||
rect := w32.GetClientRect(cba.hwnd)
|
||||
return int(rect.Right - rect.Left)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) ClientHeight() int {
|
||||
rect := w32.GetClientRect(cba.hwnd)
|
||||
return int(rect.Bottom - rect.Top)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Show() {
|
||||
// WindowPos is used with HWND_TOPMOST to guarantee bring our app on top
|
||||
// force set our main window on top
|
||||
w32.SetWindowPos(
|
||||
cba.hwnd,
|
||||
w32.HWND_TOPMOST,
|
||||
0, 0, 0, 0,
|
||||
w32.SWP_SHOWWINDOW|w32.SWP_NOSIZE|w32.SWP_NOMOVE,
|
||||
)
|
||||
// remove topmost to allow normal windows manipulations
|
||||
w32.SetWindowPos(
|
||||
cba.hwnd,
|
||||
w32.HWND_NOTOPMOST,
|
||||
0, 0, 0, 0,
|
||||
w32.SWP_SHOWWINDOW|w32.SWP_NOSIZE|w32.SWP_NOMOVE,
|
||||
)
|
||||
// put main window on tops foreground
|
||||
w32.SetForegroundWindow(cba.hwnd)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Hide() {
|
||||
w32.ShowWindow(cba.hwnd, w32.SW_HIDE)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Enabled() bool {
|
||||
return w32.IsWindowEnabled(cba.hwnd)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetEnabled(b bool) {
|
||||
w32.EnableWindow(cba.hwnd, b)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetFocus() {
|
||||
w32.SetFocus(cba.hwnd)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Invalidate(erase bool) {
|
||||
// pRect := w32.GetClientRect(cba.hwnd)
|
||||
// if cba.isForm {
|
||||
// w32.InvalidateRect(cba.hwnd, pRect, erase)
|
||||
// } else {
|
||||
// rc := ScreenToClientRect(cba.hwnd, pRect)
|
||||
// w32.InvalidateRect(cba.hwnd, rc.GetW32Rect(), erase)
|
||||
// }
|
||||
w32.InvalidateRect(cba.hwnd, nil, erase)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Parent() Controller {
|
||||
return cba.parent
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetParent(parent Controller) {
|
||||
cba.parent = parent
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Font() *Font {
|
||||
return cba.font
|
||||
}
|
||||
|
||||
func (cba *ControlBase) SetFont(font *Font) {
|
||||
w32.SendMessage(cba.hwnd, w32.WM_SETFONT, uintptr(font.hfont), 1)
|
||||
cba.font = font
|
||||
}
|
||||
|
||||
func (cba *ControlBase) EnableDragAcceptFiles(b bool) {
|
||||
w32.DragAcceptFiles(cba.hwnd, b)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) InvokeRequired() bool {
|
||||
if cba.hwnd == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
windowThreadId, _ := w32.GetWindowThreadProcessId(cba.hwnd)
|
||||
currentThreadId := w32.GetCurrentThreadId()
|
||||
|
||||
return windowThreadId != currentThreadId
|
||||
}
|
||||
|
||||
func (cba *ControlBase) Invoke(f func()) {
|
||||
if cba.tryInvokeOnCurrentGoRoutine(f) {
|
||||
return
|
||||
}
|
||||
|
||||
cba.m.Lock()
|
||||
cba.dispatchq = append(cba.dispatchq, f)
|
||||
cba.m.Unlock()
|
||||
w32.PostMessage(cba.hwnd, wmInvokeCallback, 0, 0)
|
||||
}
|
||||
|
||||
func (cba *ControlBase) PreTranslateMessage(msg *w32.MSG) bool {
|
||||
if msg.Message == w32.WM_GETDLGCODE {
|
||||
println("pretranslate, WM_GETDLGCODE")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Events
|
||||
func (cba *ControlBase) OnCreate() *EventManager {
|
||||
return &cba.onCreate
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnClose() *EventManager {
|
||||
return &cba.onClose
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnKillFocus() *EventManager {
|
||||
return &cba.onKillFocus
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnSetFocus() *EventManager {
|
||||
return &cba.onSetFocus
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnDropFiles() *EventManager {
|
||||
return &cba.onDropFiles
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnLBDown() *EventManager {
|
||||
return &cba.onLBDown
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnLBUp() *EventManager {
|
||||
return &cba.onLBUp
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnLBDbl() *EventManager {
|
||||
return &cba.onLBDbl
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnMBDown() *EventManager {
|
||||
return &cba.onMBDown
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnMBUp() *EventManager {
|
||||
return &cba.onMBUp
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnRBDown() *EventManager {
|
||||
return &cba.onRBDown
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnRBUp() *EventManager {
|
||||
return &cba.onRBUp
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnRBDbl() *EventManager {
|
||||
return &cba.onRBDbl
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnMouseMove() *EventManager {
|
||||
return &cba.onMouseMove
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnMouseHover() *EventManager {
|
||||
return &cba.onMouseHover
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnMouseLeave() *EventManager {
|
||||
return &cba.onMouseLeave
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnPaint() *EventManager {
|
||||
return &cba.onPaint
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnSize() *EventManager {
|
||||
return &cba.onSize
|
||||
}
|
||||
|
||||
func (cba *ControlBase) OnKeyUp() *EventManager {
|
||||
return &cba.onKeyUp
|
||||
}
|
||||
|
||||
func (cba *ControlBase) scaleWithWindowDPI(width, height int) (int, int) {
|
||||
dpix, dpiy := cba.GetWindowDPI()
|
||||
scaledWidth := ScaleWithDPI(width, dpix)
|
||||
scaledHeight := ScaleWithDPI(height, dpiy)
|
||||
|
||||
return scaledWidth, scaledHeight
|
||||
}
|
||||
|
||||
func (cba *ControlBase) scaleToDefaultDPI(width, height int) (int, int) {
|
||||
dpix, dpiy := cba.GetWindowDPI()
|
||||
scaledWidth := ScaleToDefaultDPI(width, dpix)
|
||||
scaledHeight := ScaleToDefaultDPI(height, dpiy)
|
||||
|
||||
return scaledWidth, scaledHeight
|
||||
}
|
||||
|
||||
func (cba *ControlBase) tryInvokeOnCurrentGoRoutine(f func()) bool {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if cba.InvokeRequired() {
|
||||
return false
|
||||
}
|
||||
f()
|
||||
return true
|
||||
}
|
||||
|
||||
func (cba *ControlBase) invokeCallbacks() {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if cba.InvokeRequired() {
|
||||
panic("InvokeCallbacks must always be called on the window thread")
|
||||
}
|
||||
|
||||
cba.m.Lock()
|
||||
q := append([]func(){}, cba.dispatchq...)
|
||||
cba.dispatchq = []func(){}
|
||||
cba.m.Unlock()
|
||||
for _, v := range q {
|
||||
v()
|
||||
}
|
||||
}
|
||||
85
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/controller.go
generated
vendored
Normal file
85
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/controller.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type Controller interface {
|
||||
Text() string
|
||||
|
||||
Enabled() bool
|
||||
SetFocus()
|
||||
|
||||
Handle() w32.HWND
|
||||
Invalidate(erase bool)
|
||||
Parent() Controller
|
||||
|
||||
Pos() (x, y int)
|
||||
Size() (w, h int)
|
||||
Height() int
|
||||
Width() int
|
||||
Visible() bool
|
||||
Bounds() *Rect
|
||||
ClientRect() *Rect
|
||||
|
||||
SetText(s string)
|
||||
SetEnabled(b bool)
|
||||
SetPos(x, y int)
|
||||
SetSize(w, h int)
|
||||
EnableDragAcceptFiles(b bool)
|
||||
Show()
|
||||
Hide()
|
||||
|
||||
ContextMenu() *MenuItem
|
||||
SetContextMenu(menu *MenuItem)
|
||||
|
||||
Font() *Font
|
||||
SetFont(font *Font)
|
||||
InvokeRequired() bool
|
||||
Invoke(func())
|
||||
PreTranslateMessage(msg *w32.MSG) bool
|
||||
WndProc(msg uint32, wparam, lparam uintptr) uintptr
|
||||
|
||||
//General events
|
||||
OnCreate() *EventManager
|
||||
OnClose() *EventManager
|
||||
|
||||
// Focus events
|
||||
OnKillFocus() *EventManager
|
||||
OnSetFocus() *EventManager
|
||||
|
||||
//Drag and drop events
|
||||
OnDropFiles() *EventManager
|
||||
|
||||
//Mouse events
|
||||
OnLBDown() *EventManager
|
||||
OnLBUp() *EventManager
|
||||
OnLBDbl() *EventManager
|
||||
OnMBDown() *EventManager
|
||||
OnMBUp() *EventManager
|
||||
OnRBDown() *EventManager
|
||||
OnRBUp() *EventManager
|
||||
OnRBDbl() *EventManager
|
||||
OnMouseMove() *EventManager
|
||||
|
||||
// OnMouseLeave and OnMouseHover does not fire unless control called internalTrackMouseEvent.
|
||||
// Use MouseControl for a how to example.
|
||||
OnMouseHover() *EventManager
|
||||
OnMouseLeave() *EventManager
|
||||
|
||||
//Keyboard events
|
||||
OnKeyUp() *EventManager
|
||||
|
||||
//Paint events
|
||||
OnPaint() *EventManager
|
||||
OnSize() *EventManager
|
||||
|
||||
invokeCallbacks()
|
||||
}
|
||||
136
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/dialog.go
generated
vendored
Normal file
136
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/dialog.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
|
||||
// Dialog displayed as z-order top window until closed.
|
||||
// It also disables parent window so it can not be clicked.
|
||||
type Dialog struct {
|
||||
Form
|
||||
isModal bool
|
||||
|
||||
btnOk *PushButton
|
||||
btnCancel *PushButton
|
||||
|
||||
onLoad EventManager
|
||||
onOk EventManager
|
||||
onCancel EventManager
|
||||
}
|
||||
|
||||
func NewDialog(parent Controller) *Dialog {
|
||||
dlg := new(Dialog)
|
||||
|
||||
dlg.isForm = true
|
||||
dlg.isModal = true
|
||||
RegClassOnlyOnce("winc_Dialog")
|
||||
|
||||
dlg.hwnd = CreateWindow("winc_Dialog", parent, w32.WS_EX_CONTROLPARENT, /* IMPORTANT */
|
||||
w32.WS_SYSMENU|w32.WS_CAPTION|w32.WS_THICKFRAME /*|w32.WS_BORDER|w32.WS_POPUP*/)
|
||||
dlg.parent = parent
|
||||
|
||||
// dlg might fail if icon resource is not embedded in the binary
|
||||
if ico, err := NewIconFromResource(GetAppInstance(), uint16(AppIconID)); err == nil {
|
||||
dlg.SetIcon(0, ico)
|
||||
}
|
||||
|
||||
// Dlg forces display of focus rectangles, as soon as the user starts to type.
|
||||
w32.SendMessage(dlg.hwnd, w32.WM_CHANGEUISTATE, w32.UIS_INITIALIZE, 0)
|
||||
RegMsgHandler(dlg)
|
||||
|
||||
dlg.SetFont(DefaultFont)
|
||||
dlg.SetText("Form")
|
||||
dlg.SetSize(200, 100)
|
||||
return dlg
|
||||
}
|
||||
|
||||
func (dlg *Dialog) SetModal(modal bool) {
|
||||
dlg.isModal = modal
|
||||
}
|
||||
|
||||
// SetButtons wires up dialog events to buttons. btnCancel can be nil.
|
||||
func (dlg *Dialog) SetButtons(btnOk *PushButton, btnCancel *PushButton) {
|
||||
dlg.btnOk = btnOk
|
||||
dlg.btnOk.SetDefault()
|
||||
dlg.btnCancel = btnCancel
|
||||
}
|
||||
|
||||
// Events
|
||||
func (dlg *Dialog) OnLoad() *EventManager {
|
||||
return &dlg.onLoad
|
||||
}
|
||||
|
||||
func (dlg *Dialog) OnOk() *EventManager {
|
||||
return &dlg.onOk
|
||||
}
|
||||
|
||||
func (dlg *Dialog) OnCancel() *EventManager {
|
||||
return &dlg.onCancel
|
||||
}
|
||||
|
||||
// PreTranslateMessage handles dialog specific messages. IMPORTANT.
|
||||
func (dlg *Dialog) PreTranslateMessage(msg *w32.MSG) bool {
|
||||
if msg.Message >= w32.WM_KEYFIRST && msg.Message <= w32.WM_KEYLAST {
|
||||
if w32.IsDialogMessage(dlg.hwnd, msg) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Show dialog performs special setup for dialog windows.
|
||||
func (dlg *Dialog) Show() {
|
||||
if dlg.isModal {
|
||||
dlg.Parent().SetEnabled(false)
|
||||
}
|
||||
dlg.onLoad.Fire(NewEvent(dlg, nil))
|
||||
dlg.Form.Show()
|
||||
}
|
||||
|
||||
// Close dialog when you done with it.
|
||||
func (dlg *Dialog) Close() {
|
||||
if dlg.isModal {
|
||||
dlg.Parent().SetEnabled(true)
|
||||
}
|
||||
dlg.ControlBase.Close()
|
||||
}
|
||||
|
||||
func (dlg *Dialog) cancel() {
|
||||
if dlg.btnCancel != nil {
|
||||
dlg.btnCancel.onClick.Fire(NewEvent(dlg.btnCancel, nil))
|
||||
}
|
||||
dlg.onCancel.Fire(NewEvent(dlg, nil))
|
||||
}
|
||||
|
||||
func (dlg *Dialog) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_COMMAND:
|
||||
switch w32.LOWORD(uint32(wparam)) {
|
||||
case w32.IDOK:
|
||||
if dlg.btnOk != nil {
|
||||
dlg.btnOk.onClick.Fire(NewEvent(dlg.btnOk, nil))
|
||||
}
|
||||
dlg.onOk.Fire(NewEvent(dlg, nil))
|
||||
return w32.TRUE
|
||||
|
||||
case w32.IDCANCEL:
|
||||
dlg.cancel()
|
||||
return w32.TRUE
|
||||
}
|
||||
|
||||
case w32.WM_CLOSE:
|
||||
dlg.cancel() // use onCancel or dlg.btnCancel.OnClick to close
|
||||
return 0
|
||||
|
||||
case w32.WM_DESTROY:
|
||||
if dlg.isModal {
|
||||
dlg.Parent().SetEnabled(true)
|
||||
}
|
||||
}
|
||||
return w32.DefWindowProc(dlg.hwnd, msg, wparam, lparam)
|
||||
}
|
||||
BIN
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/dock_topbottom.png
generated
vendored
Normal file
BIN
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/dock_topbottom.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/dock_topleft.png
generated
vendored
Normal file
BIN
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/dock_topleft.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
113
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/edit.go
generated
vendored
Normal file
113
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/edit.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
|
||||
type Edit struct {
|
||||
ControlBase
|
||||
onChange EventManager
|
||||
}
|
||||
|
||||
const passwordChar = '*'
|
||||
const nopasswordChar = ' '
|
||||
|
||||
func NewEdit(parent Controller) *Edit {
|
||||
edt := new(Edit)
|
||||
|
||||
edt.InitControl("EDIT", parent, w32.WS_EX_CLIENTEDGE, w32.WS_CHILD|w32.WS_VISIBLE|w32.WS_TABSTOP|w32.ES_LEFT|
|
||||
w32.ES_AUTOHSCROLL)
|
||||
RegMsgHandler(edt)
|
||||
|
||||
edt.SetFont(DefaultFont)
|
||||
edt.SetSize(200, 22)
|
||||
return edt
|
||||
}
|
||||
|
||||
// Events.
|
||||
func (ed *Edit) OnChange() *EventManager {
|
||||
return &ed.onChange
|
||||
}
|
||||
|
||||
// Public methods.
|
||||
func (ed *Edit) SetReadOnly(isReadOnly bool) {
|
||||
w32.SendMessage(ed.hwnd, w32.EM_SETREADONLY, uintptr(w32.BoolToBOOL(isReadOnly)), 0)
|
||||
}
|
||||
|
||||
// Public methods
|
||||
func (ed *Edit) SetPassword(isPassword bool) {
|
||||
if isPassword {
|
||||
w32.SendMessage(ed.hwnd, w32.EM_SETPASSWORDCHAR, uintptr(passwordChar), 0)
|
||||
} else {
|
||||
w32.SendMessage(ed.hwnd, w32.EM_SETPASSWORDCHAR, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Edit) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_COMMAND:
|
||||
switch w32.HIWORD(uint32(wparam)) {
|
||||
case w32.EN_CHANGE:
|
||||
ed.onChange.Fire(NewEvent(ed, nil))
|
||||
}
|
||||
/*case w32.WM_GETDLGCODE:
|
||||
println("Edit")
|
||||
if wparam == w32.VK_RETURN {
|
||||
return w32.DLGC_WANTALLKEYS
|
||||
}*/
|
||||
}
|
||||
return w32.DefWindowProc(ed.hwnd, msg, wparam, lparam)
|
||||
}
|
||||
|
||||
// MultiEdit is multiline text edit.
|
||||
type MultiEdit struct {
|
||||
ControlBase
|
||||
onChange EventManager
|
||||
}
|
||||
|
||||
func NewMultiEdit(parent Controller) *MultiEdit {
|
||||
med := new(MultiEdit)
|
||||
|
||||
med.InitControl("EDIT", parent, w32.WS_EX_CLIENTEDGE, w32.WS_CHILD|w32.WS_VISIBLE|w32.WS_TABSTOP|w32.ES_LEFT|
|
||||
w32.WS_VSCROLL|w32.WS_HSCROLL|w32.ES_MULTILINE|w32.ES_WANTRETURN|w32.ES_AUTOHSCROLL|w32.ES_AUTOVSCROLL)
|
||||
RegMsgHandler(med)
|
||||
|
||||
med.SetFont(DefaultFont)
|
||||
med.SetSize(200, 400)
|
||||
return med
|
||||
}
|
||||
|
||||
// Events
|
||||
func (med *MultiEdit) OnChange() *EventManager {
|
||||
return &med.onChange
|
||||
}
|
||||
|
||||
// Public methods
|
||||
func (med *MultiEdit) SetReadOnly(isReadOnly bool) {
|
||||
w32.SendMessage(med.hwnd, w32.EM_SETREADONLY, uintptr(w32.BoolToBOOL(isReadOnly)), 0)
|
||||
}
|
||||
|
||||
func (med *MultiEdit) AddLine(text string) {
|
||||
if len(med.Text()) == 0 {
|
||||
med.SetText(text)
|
||||
} else {
|
||||
med.SetText(med.Text() + "\r\n" + text)
|
||||
}
|
||||
}
|
||||
|
||||
func (med *MultiEdit) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
|
||||
case w32.WM_COMMAND:
|
||||
switch w32.HIWORD(uint32(wparam)) {
|
||||
case w32.EN_CHANGE:
|
||||
med.onChange.Fire(NewEvent(med, nil))
|
||||
}
|
||||
}
|
||||
return w32.DefWindowProc(med.hwnd, msg, wparam, lparam)
|
||||
}
|
||||
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/event.go
generated
vendored
Normal file
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/event.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
type Event struct {
|
||||
Sender Controller
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
func NewEvent(sender Controller, data interface{}) *Event {
|
||||
return &Event{Sender: sender, Data: data}
|
||||
}
|
||||
52
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/eventdata.go
generated
vendored
Normal file
52
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/eventdata.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type RawMsg struct {
|
||||
Hwnd w32.HWND
|
||||
Msg uint32
|
||||
WParam, LParam uintptr
|
||||
}
|
||||
|
||||
type MouseEventData struct {
|
||||
X, Y int
|
||||
Button int
|
||||
Wheel int
|
||||
}
|
||||
|
||||
type DropFilesEventData struct {
|
||||
X, Y int
|
||||
Files []string
|
||||
}
|
||||
|
||||
type PaintEventData struct {
|
||||
Canvas *Canvas
|
||||
}
|
||||
|
||||
type LabelEditEventData struct {
|
||||
Item ListItem
|
||||
Text string
|
||||
//PszText *uint16
|
||||
}
|
||||
|
||||
/*type LVDBLClickEventData struct {
|
||||
NmItem *w32.NMITEMACTIVATE
|
||||
}*/
|
||||
|
||||
type KeyUpEventData struct {
|
||||
VKey, Code int
|
||||
}
|
||||
|
||||
type SizeEventData struct {
|
||||
Type uint
|
||||
X, Y int
|
||||
}
|
||||
24
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/eventmanager.go
generated
vendored
Normal file
24
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/eventmanager.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
type EventHandler func(arg *Event)
|
||||
|
||||
type EventManager struct {
|
||||
handler EventHandler
|
||||
}
|
||||
|
||||
func (evm *EventManager) Fire(arg *Event) {
|
||||
if evm.handler != nil {
|
||||
evm.handler(arg)
|
||||
}
|
||||
}
|
||||
|
||||
func (evm *EventManager) Bind(handler EventHandler) {
|
||||
evm.handler = handler
|
||||
}
|
||||
121
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/font.go
generated
vendored
Normal file
121
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/font.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
const (
|
||||
FontBold byte = 0x01
|
||||
FontItalic byte = 0x02
|
||||
FontUnderline byte = 0x04
|
||||
FontStrikeOut byte = 0x08
|
||||
)
|
||||
|
||||
func init() {
|
||||
DefaultFont = NewFont("MS Shell Dlg 2", 8, 0)
|
||||
}
|
||||
|
||||
type Font struct {
|
||||
hfont w32.HFONT
|
||||
family string
|
||||
pointSize int
|
||||
style byte
|
||||
}
|
||||
|
||||
func NewFont(family string, pointSize int, style byte) *Font {
|
||||
if style > FontBold|FontItalic|FontUnderline|FontStrikeOut {
|
||||
panic("Invalid font style")
|
||||
}
|
||||
|
||||
//Retrive screen DPI
|
||||
hDC := w32.GetDC(0)
|
||||
defer w32.ReleaseDC(0, hDC)
|
||||
screenDPIY := w32.GetDeviceCaps(hDC, w32.LOGPIXELSY)
|
||||
|
||||
font := Font{
|
||||
family: family,
|
||||
pointSize: pointSize,
|
||||
style: style,
|
||||
}
|
||||
|
||||
font.hfont = font.createForDPI(screenDPIY)
|
||||
if font.hfont == 0 {
|
||||
panic("CreateFontIndirect failed")
|
||||
}
|
||||
|
||||
return &font
|
||||
}
|
||||
|
||||
func (fnt *Font) createForDPI(dpi int) w32.HFONT {
|
||||
var lf w32.LOGFONT
|
||||
|
||||
lf.Height = int32(-w32.MulDiv(fnt.pointSize, dpi, 72))
|
||||
if fnt.style&FontBold > 0 {
|
||||
lf.Weight = w32.FW_BOLD
|
||||
} else {
|
||||
lf.Weight = w32.FW_NORMAL
|
||||
}
|
||||
if fnt.style&FontItalic > 0 {
|
||||
lf.Italic = 1
|
||||
}
|
||||
if fnt.style&FontUnderline > 0 {
|
||||
lf.Underline = 1
|
||||
}
|
||||
if fnt.style&FontStrikeOut > 0 {
|
||||
lf.StrikeOut = 1
|
||||
}
|
||||
lf.CharSet = w32.DEFAULT_CHARSET
|
||||
lf.OutPrecision = w32.OUT_TT_PRECIS
|
||||
lf.ClipPrecision = w32.CLIP_DEFAULT_PRECIS
|
||||
lf.Quality = w32.CLEARTYPE_QUALITY
|
||||
lf.PitchAndFamily = w32.VARIABLE_PITCH | w32.FF_SWISS
|
||||
|
||||
src := syscall.StringToUTF16(fnt.family)
|
||||
dest := lf.FaceName[:]
|
||||
copy(dest, src)
|
||||
|
||||
return w32.CreateFontIndirect(&lf)
|
||||
}
|
||||
|
||||
func (fnt *Font) GetHFONT() w32.HFONT {
|
||||
return fnt.hfont
|
||||
}
|
||||
|
||||
func (fnt *Font) Bold() bool {
|
||||
return fnt.style&FontBold > 0
|
||||
}
|
||||
|
||||
func (fnt *Font) Dispose() {
|
||||
if fnt.hfont != 0 {
|
||||
w32.DeleteObject(w32.HGDIOBJ(fnt.hfont))
|
||||
}
|
||||
}
|
||||
|
||||
func (fnt *Font) Family() string {
|
||||
return fnt.family
|
||||
}
|
||||
|
||||
func (fnt *Font) Italic() bool {
|
||||
return fnt.style&FontItalic > 0
|
||||
}
|
||||
|
||||
func (fnt *Font) StrikeOut() bool {
|
||||
return fnt.style&FontStrikeOut > 0
|
||||
}
|
||||
|
||||
func (fnt *Font) Underline() bool {
|
||||
return fnt.style&FontUnderline > 0
|
||||
}
|
||||
|
||||
func (fnt *Font) Style() byte {
|
||||
return fnt.style
|
||||
}
|
||||
317
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/form.go
generated
vendored
Normal file
317
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/form.go
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type LayoutManager interface {
|
||||
Update()
|
||||
}
|
||||
|
||||
// A Form is main window of the application.
|
||||
type Form struct {
|
||||
ControlBase
|
||||
|
||||
layoutMng LayoutManager
|
||||
|
||||
// Fullscreen / Unfullscreen
|
||||
isFullscreen bool
|
||||
previousWindowStyle uint32
|
||||
previousWindowExStyle uint32
|
||||
previousWindowPlacement w32.WINDOWPLACEMENT
|
||||
}
|
||||
|
||||
func NewCustomForm(parent Controller, exStyle int, dwStyle uint) *Form {
|
||||
fm := new(Form)
|
||||
|
||||
RegClassOnlyOnce("winc_Form")
|
||||
|
||||
fm.isForm = true
|
||||
|
||||
if exStyle == 0 {
|
||||
exStyle = w32.WS_EX_CONTROLPARENT | w32.WS_EX_APPWINDOW
|
||||
}
|
||||
|
||||
if dwStyle == 0 {
|
||||
dwStyle = w32.WS_OVERLAPPEDWINDOW
|
||||
}
|
||||
|
||||
fm.hwnd = CreateWindow("winc_Form", parent, uint(exStyle), dwStyle)
|
||||
fm.parent = parent
|
||||
|
||||
// this might fail if icon resource is not embedded in the binary
|
||||
if ico, err := NewIconFromResource(GetAppInstance(), uint16(AppIconID)); err == nil {
|
||||
fm.SetIcon(0, ico)
|
||||
}
|
||||
|
||||
// This forces display of focus rectangles, as soon as the user starts to type.
|
||||
w32.SendMessage(fm.hwnd, w32.WM_CHANGEUISTATE, w32.UIS_INITIALIZE, 0)
|
||||
|
||||
RegMsgHandler(fm)
|
||||
|
||||
fm.SetFont(DefaultFont)
|
||||
fm.SetText("Form")
|
||||
return fm
|
||||
}
|
||||
|
||||
func NewForm(parent Controller) *Form {
|
||||
fm := new(Form)
|
||||
|
||||
RegClassOnlyOnce("winc_Form")
|
||||
|
||||
fm.isForm = true
|
||||
fm.hwnd = CreateWindow("winc_Form", parent, w32.WS_EX_CONTROLPARENT|w32.WS_EX_APPWINDOW, w32.WS_OVERLAPPEDWINDOW)
|
||||
fm.parent = parent
|
||||
|
||||
// this might fail if icon resource is not embedded in the binary
|
||||
if ico, err := NewIconFromResource(GetAppInstance(), uint16(AppIconID)); err == nil {
|
||||
fm.SetIcon(0, ico)
|
||||
}
|
||||
|
||||
// This forces display of focus rectangles, as soon as the user starts to type.
|
||||
w32.SendMessage(fm.hwnd, w32.WM_CHANGEUISTATE, w32.UIS_INITIALIZE, 0)
|
||||
|
||||
RegMsgHandler(fm)
|
||||
|
||||
fm.SetFont(DefaultFont)
|
||||
fm.SetText("Form")
|
||||
return fm
|
||||
}
|
||||
|
||||
func (fm *Form) SetLayout(mng LayoutManager) {
|
||||
fm.layoutMng = mng
|
||||
}
|
||||
|
||||
// UpdateLayout refresh layout.
|
||||
func (fm *Form) UpdateLayout() {
|
||||
if fm.layoutMng != nil {
|
||||
fm.layoutMng.Update()
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *Form) NewMenu() *Menu {
|
||||
hMenu := w32.CreateMenu()
|
||||
if hMenu == 0 {
|
||||
panic("failed CreateMenu")
|
||||
}
|
||||
m := &Menu{hMenu: hMenu, hwnd: fm.hwnd}
|
||||
if !w32.SetMenu(fm.hwnd, hMenu) {
|
||||
panic("failed SetMenu")
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (fm *Form) DisableIcon() {
|
||||
windowInfo := getWindowInfo(fm.hwnd)
|
||||
frameless := windowInfo.IsPopup()
|
||||
if frameless {
|
||||
return
|
||||
}
|
||||
exStyle := w32.GetWindowLong(fm.hwnd, w32.GWL_EXSTYLE)
|
||||
w32.SetWindowLong(fm.hwnd, w32.GWL_EXSTYLE, uint32(exStyle|w32.WS_EX_DLGMODALFRAME))
|
||||
w32.SetWindowPos(fm.hwnd, 0, 0, 0, 0, 0,
|
||||
uint(
|
||||
w32.SWP_FRAMECHANGED|
|
||||
w32.SWP_NOMOVE|
|
||||
w32.SWP_NOSIZE|
|
||||
w32.SWP_NOZORDER),
|
||||
)
|
||||
}
|
||||
|
||||
func (fm *Form) Maximise() {
|
||||
w32.ShowWindow(fm.hwnd, w32.SW_MAXIMIZE)
|
||||
}
|
||||
|
||||
func (fm *Form) Minimise() {
|
||||
w32.ShowWindow(fm.hwnd, w32.SW_MINIMIZE)
|
||||
}
|
||||
|
||||
func (fm *Form) Restore() {
|
||||
// SC_RESTORE param for WM_SYSCOMMAND to restore app if it is minimized
|
||||
const SC_RESTORE = 0xF120
|
||||
// restore the minimized window, if it is
|
||||
w32.SendMessage(
|
||||
fm.hwnd,
|
||||
w32.WM_SYSCOMMAND,
|
||||
SC_RESTORE,
|
||||
0,
|
||||
)
|
||||
w32.ShowWindow(fm.hwnd, w32.SW_SHOW)
|
||||
}
|
||||
|
||||
// Public methods
|
||||
func (fm *Form) Center() {
|
||||
|
||||
windowInfo := getWindowInfo(fm.hwnd)
|
||||
frameless := windowInfo.IsPopup()
|
||||
|
||||
info := getMonitorInfo(fm.hwnd)
|
||||
workRect := info.RcWork
|
||||
screenMiddleW := workRect.Left + (workRect.Right-workRect.Left)/2
|
||||
screenMiddleH := workRect.Top + (workRect.Bottom-workRect.Top)/2
|
||||
var winRect *w32.RECT
|
||||
if !frameless {
|
||||
winRect = w32.GetWindowRect(fm.hwnd)
|
||||
} else {
|
||||
winRect = w32.GetClientRect(fm.hwnd)
|
||||
}
|
||||
winWidth := winRect.Right - winRect.Left
|
||||
winHeight := winRect.Bottom - winRect.Top
|
||||
windowX := screenMiddleW - (winWidth / 2)
|
||||
windowY := screenMiddleH - (winHeight / 2)
|
||||
w32.SetWindowPos(fm.hwnd, w32.HWND_TOP, int(windowX), int(windowY), int(winWidth), int(winHeight), w32.SWP_NOSIZE)
|
||||
}
|
||||
|
||||
func (fm *Form) Fullscreen() {
|
||||
if fm.isFullscreen {
|
||||
return
|
||||
}
|
||||
|
||||
fm.previousWindowStyle = uint32(w32.GetWindowLongPtr(fm.hwnd, w32.GWL_STYLE))
|
||||
fm.previousWindowExStyle = uint32(w32.GetWindowLong(fm.hwnd, w32.GWL_EXSTYLE))
|
||||
|
||||
monitor := w32.MonitorFromWindow(fm.hwnd, w32.MONITOR_DEFAULTTOPRIMARY)
|
||||
var monitorInfo w32.MONITORINFO
|
||||
monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
|
||||
if !w32.GetMonitorInfo(monitor, &monitorInfo) {
|
||||
return
|
||||
}
|
||||
if !w32.GetWindowPlacement(fm.hwnd, &fm.previousWindowPlacement) {
|
||||
return
|
||||
}
|
||||
// According to https://devblogs.microsoft.com/oldnewthing/20050505-04/?p=35703 one should use w32.WS_POPUP | w32.WS_VISIBLE
|
||||
w32.SetWindowLong(fm.hwnd, w32.GWL_STYLE, fm.previousWindowStyle & ^uint32(w32.WS_OVERLAPPEDWINDOW) | (w32.WS_POPUP|w32.WS_VISIBLE))
|
||||
w32.SetWindowLong(fm.hwnd, w32.GWL_EXSTYLE, fm.previousWindowExStyle & ^uint32(w32.WS_EX_DLGMODALFRAME))
|
||||
fm.isFullscreen = true
|
||||
w32.SetWindowPos(fm.hwnd, w32.HWND_TOP,
|
||||
int(monitorInfo.RcMonitor.Left),
|
||||
int(monitorInfo.RcMonitor.Top),
|
||||
int(monitorInfo.RcMonitor.Right-monitorInfo.RcMonitor.Left),
|
||||
int(monitorInfo.RcMonitor.Bottom-monitorInfo.RcMonitor.Top),
|
||||
w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED)
|
||||
}
|
||||
|
||||
func (fm *Form) UnFullscreen() {
|
||||
if !fm.isFullscreen {
|
||||
return
|
||||
}
|
||||
w32.SetWindowLong(fm.hwnd, w32.GWL_STYLE, fm.previousWindowStyle)
|
||||
w32.SetWindowLong(fm.hwnd, w32.GWL_EXSTYLE, fm.previousWindowExStyle)
|
||||
w32.SetWindowPlacement(fm.hwnd, &fm.previousWindowPlacement)
|
||||
fm.isFullscreen = false
|
||||
w32.SetWindowPos(fm.hwnd, 0, 0, 0, 0, 0,
|
||||
w32.SWP_NOMOVE|w32.SWP_NOSIZE|w32.SWP_NOZORDER|w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED)
|
||||
}
|
||||
|
||||
func (fm *Form) IsFullScreen() bool {
|
||||
return fm.isFullscreen
|
||||
}
|
||||
|
||||
// IconType: 1 - ICON_BIG; 0 - ICON_SMALL
|
||||
func (fm *Form) SetIcon(iconType int, icon *Icon) {
|
||||
if iconType > 1 {
|
||||
panic("IconType is invalid")
|
||||
}
|
||||
w32.SendMessage(fm.hwnd, w32.WM_SETICON, uintptr(iconType), uintptr(icon.Handle()))
|
||||
}
|
||||
|
||||
func (fm *Form) EnableMaxButton(b bool) {
|
||||
SetStyle(fm.hwnd, b, w32.WS_MAXIMIZEBOX)
|
||||
}
|
||||
|
||||
func (fm *Form) EnableMinButton(b bool) {
|
||||
SetStyle(fm.hwnd, b, w32.WS_MINIMIZEBOX)
|
||||
}
|
||||
|
||||
func (fm *Form) EnableSizable(b bool) {
|
||||
SetStyle(fm.hwnd, b, w32.WS_THICKFRAME)
|
||||
}
|
||||
|
||||
func (fm *Form) EnableDragMove(_ bool) {
|
||||
//fm.isDragMove = b
|
||||
}
|
||||
|
||||
func (fm *Form) EnableTopMost(b bool) {
|
||||
tag := w32.HWND_NOTOPMOST
|
||||
if b {
|
||||
tag = w32.HWND_TOPMOST
|
||||
}
|
||||
w32.SetWindowPos(fm.hwnd, tag, 0, 0, 0, 0, w32.SWP_NOMOVE|w32.SWP_NOSIZE)
|
||||
}
|
||||
|
||||
func (fm *Form) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
|
||||
switch msg {
|
||||
case w32.WM_COMMAND:
|
||||
if lparam == 0 && w32.HIWORD(uint32(wparam)) == 0 {
|
||||
// Menu support.
|
||||
actionID := uint16(w32.LOWORD(uint32(wparam)))
|
||||
if action, ok := actionsByID[actionID]; ok {
|
||||
action.onClick.Fire(NewEvent(fm, nil))
|
||||
}
|
||||
}
|
||||
case w32.WM_KEYDOWN:
|
||||
// Accelerator support.
|
||||
key := Key(wparam)
|
||||
if uint32(lparam)>>30 == 0 {
|
||||
// Using TranslateAccelerators refused to work, so we handle them
|
||||
// ourselves, at least for now.
|
||||
shortcut := Shortcut{ModifiersDown(), key}
|
||||
if action, ok := shortcut2Action[shortcut]; ok {
|
||||
if action.Enabled() {
|
||||
action.onClick.Fire(NewEvent(fm, nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case w32.WM_CLOSE:
|
||||
return 0
|
||||
case w32.WM_DESTROY:
|
||||
w32.PostQuitMessage(0)
|
||||
return 0
|
||||
|
||||
case w32.WM_SIZE, w32.WM_PAINT:
|
||||
if fm.layoutMng != nil {
|
||||
fm.layoutMng.Update()
|
||||
}
|
||||
case w32.WM_GETMINMAXINFO:
|
||||
mmi := (*w32.MINMAXINFO)(unsafe.Pointer(lparam))
|
||||
hasConstraints := false
|
||||
if fm.minWidth > 0 || fm.minHeight > 0 {
|
||||
hasConstraints = true
|
||||
|
||||
width, height := fm.scaleWithWindowDPI(fm.minWidth, fm.minHeight)
|
||||
if width > 0 {
|
||||
mmi.PtMinTrackSize.X = int32(width)
|
||||
}
|
||||
if height > 0 {
|
||||
mmi.PtMinTrackSize.Y = int32(height)
|
||||
}
|
||||
}
|
||||
if fm.maxWidth > 0 || fm.maxHeight > 0 {
|
||||
hasConstraints = true
|
||||
|
||||
width, height := fm.scaleWithWindowDPI(fm.maxWidth, fm.maxHeight)
|
||||
if width > 0 {
|
||||
mmi.PtMaxTrackSize.X = int32(width)
|
||||
}
|
||||
if height > 0 {
|
||||
mmi.PtMaxTrackSize.Y = int32(height)
|
||||
}
|
||||
}
|
||||
if hasConstraints {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return w32.DefWindowProc(fm.hwnd, msg, wparam, lparam)
|
||||
}
|
||||
27
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/globalvars.go
generated
vendored
Normal file
27
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/globalvars.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
// Private global variables.
|
||||
var (
|
||||
gAppInstance w32.HINSTANCE
|
||||
gControllerRegistry map[w32.HWND]Controller
|
||||
gRegisteredClasses []string
|
||||
)
|
||||
|
||||
// Public global variables.
|
||||
var (
|
||||
GeneralWndprocCallBack = syscall.NewCallback(generalWndProc)
|
||||
DefaultFont *Font
|
||||
)
|
||||
219
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/icon.go
generated
vendored
Normal file
219
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/icon.go
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
var (
|
||||
user32 = syscall.NewLazyDLL("user32.dll")
|
||||
gdi32 = syscall.NewLazyDLL("gdi32.dll")
|
||||
procGetIconInfo = user32.NewProc("GetIconInfo")
|
||||
procDeleteObject = gdi32.NewProc("DeleteObject")
|
||||
procGetObject = gdi32.NewProc("GetObjectW")
|
||||
procGetDIBits = gdi32.NewProc("GetDIBits")
|
||||
procCreateCompatibleDC = gdi32.NewProc("CreateCompatibleDC")
|
||||
procSelectObject = gdi32.NewProc("SelectObject")
|
||||
procDeleteDC = gdi32.NewProc("DeleteDC")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Validate DLL loads at initialization time to surface missing APIs early
|
||||
if err := user32.Load(); err != nil {
|
||||
panic(fmt.Sprintf("failed to load user32.dll: %v", err))
|
||||
}
|
||||
if err := gdi32.Load(); err != nil {
|
||||
panic(fmt.Sprintf("failed to load gdi32.dll: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// ICONINFO mirrors the Win32 ICONINFO struct
|
||||
type ICONINFO struct {
|
||||
FIcon int32
|
||||
XHotspot uint32
|
||||
YHotspot uint32
|
||||
HbmMask uintptr
|
||||
HbmColor uintptr
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx
|
||||
type BITMAPINFOHEADER struct {
|
||||
BiSize uint32
|
||||
BiWidth int32
|
||||
BiHeight int32
|
||||
BiPlanes uint16
|
||||
BiBitCount uint16
|
||||
BiCompression uint32
|
||||
BiSizeImage uint32
|
||||
BiXPelsPerMeter int32
|
||||
BiYPelsPerMeter int32
|
||||
BiClrUsed uint32
|
||||
BiClrImportant uint32
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162938.aspx
|
||||
type RGBQUAD struct {
|
||||
RgbBlue byte
|
||||
RgbGreen byte
|
||||
RgbRed byte
|
||||
RgbReserved byte
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183375.aspx
|
||||
type BITMAPINFO struct {
|
||||
BmiHeader BITMAPINFOHEADER
|
||||
BmiColors *RGBQUAD
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183371.aspx
|
||||
type BITMAP struct {
|
||||
BmType int32
|
||||
BmWidth int32
|
||||
BmHeight int32
|
||||
BmWidthBytes int32
|
||||
BmPlanes uint16
|
||||
BmBitsPixel uint16
|
||||
BmBits unsafe.Pointer
|
||||
}
|
||||
|
||||
type Icon struct {
|
||||
handle w32.HICON
|
||||
}
|
||||
|
||||
func NewIconFromFile(path string) (*Icon, error) {
|
||||
ico := new(Icon)
|
||||
var err error
|
||||
if ico.handle = w32.LoadIcon(0, syscall.StringToUTF16Ptr(path)); ico.handle == 0 {
|
||||
err = errors.New(fmt.Sprintf("Cannot load icon from %s", path))
|
||||
}
|
||||
return ico, err
|
||||
}
|
||||
|
||||
func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (*Icon, error) {
|
||||
ico := new(Icon)
|
||||
var err error
|
||||
if ico.handle = w32.LoadIconWithResourceID(instance, resId); ico.handle == 0 {
|
||||
err = errors.New(fmt.Sprintf("Cannot load icon from resource with id %v", resId))
|
||||
}
|
||||
return ico, err
|
||||
}
|
||||
|
||||
func ExtractIcon(fileName string, index int) (*Icon, error) {
|
||||
ico := new(Icon)
|
||||
var err error
|
||||
if ico.handle = w32.ExtractIcon(fileName, index); ico.handle == 0 || ico.handle == 1 {
|
||||
err = errors.New(fmt.Sprintf("Cannot extract icon from %s at index %v", fileName, index))
|
||||
}
|
||||
return ico, err
|
||||
}
|
||||
|
||||
func SaveHIconAsPNG(hIcon w32.HICON, filePath string) error {
|
||||
// Get icon info
|
||||
var iconInfo ICONINFO
|
||||
ret, _, err := procGetIconInfo.Call(
|
||||
uintptr(hIcon),
|
||||
uintptr(unsafe.Pointer(&iconInfo)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
defer procDeleteObject.Call(uintptr(iconInfo.HbmMask))
|
||||
defer procDeleteObject.Call(uintptr(iconInfo.HbmColor))
|
||||
|
||||
// Get bitmap info
|
||||
var bmp BITMAP
|
||||
ret, _, err = procGetObject.Call(
|
||||
uintptr(iconInfo.HbmColor),
|
||||
unsafe.Sizeof(bmp),
|
||||
uintptr(unsafe.Pointer(&bmp)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get screen DC for GetDIBits (bitmap must not be selected into a DC)
|
||||
screenDC := w32.GetDC(0)
|
||||
if screenDC == 0 {
|
||||
return fmt.Errorf("failed to get screen DC")
|
||||
}
|
||||
defer w32.ReleaseDC(0, screenDC)
|
||||
|
||||
// Prepare bitmap info header
|
||||
var bi BITMAPINFO
|
||||
bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader))
|
||||
bi.BmiHeader.BiWidth = bmp.BmWidth
|
||||
bi.BmiHeader.BiHeight = bmp.BmHeight
|
||||
bi.BmiHeader.BiPlanes = 1
|
||||
bi.BmiHeader.BiBitCount = 32
|
||||
bi.BmiHeader.BiCompression = w32.BI_RGB
|
||||
|
||||
// Allocate memory for bitmap bits
|
||||
width, height := int(bmp.BmWidth), int(bmp.BmHeight)
|
||||
bufferSize := width * height * 4
|
||||
bits := make([]byte, bufferSize)
|
||||
|
||||
// Get bitmap bits using screen DC (bitmap must not be selected into any DC)
|
||||
ret, _, err = procGetDIBits.Call(
|
||||
uintptr(screenDC),
|
||||
uintptr(iconInfo.HbmColor),
|
||||
0,
|
||||
uintptr(bmp.BmHeight),
|
||||
uintptr(unsafe.Pointer(&bits[0])),
|
||||
uintptr(unsafe.Pointer(&bi)),
|
||||
w32.DIB_RGB_COLORS,
|
||||
)
|
||||
if ret == 0 {
|
||||
return fmt.Errorf("failed to get bitmap bits: %w", err)
|
||||
}
|
||||
|
||||
// Create Go image
|
||||
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
|
||||
// Convert DIB to RGBA
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
// DIB is bottom-up, so we need to invert Y
|
||||
dibIndex := ((height-1-y)*width + x) * 4
|
||||
// RGBA image is top-down
|
||||
imgIndex := (y*width + x) * 4
|
||||
|
||||
// BGRA to RGBA
|
||||
img.Pix[imgIndex] = bits[dibIndex+2] // R
|
||||
img.Pix[imgIndex+1] = bits[dibIndex+1] // G
|
||||
img.Pix[imgIndex+2] = bits[dibIndex] // B
|
||||
img.Pix[imgIndex+3] = bits[dibIndex+3] // A
|
||||
}
|
||||
}
|
||||
|
||||
// Create output file
|
||||
outFile, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
// Encode and save the image
|
||||
return png.Encode(outFile, img)
|
||||
}
|
||||
|
||||
func (ic *Icon) Destroy() bool {
|
||||
return w32.DestroyIcon(ic.handle)
|
||||
}
|
||||
|
||||
func (ic *Icon) Handle() w32.HICON {
|
||||
return ic.handle
|
||||
}
|
||||
64
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/imagelist.go
generated
vendored
Normal file
64
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/imagelist.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type ImageList struct {
|
||||
handle w32.HIMAGELIST
|
||||
}
|
||||
|
||||
func NewImageList(cx, cy int) *ImageList {
|
||||
return newImageList(cx, cy, w32.ILC_COLOR32, 0, 0)
|
||||
}
|
||||
|
||||
func newImageList(cx, cy int, flags uint, cInitial, cGrow int) *ImageList {
|
||||
imgl := new(ImageList)
|
||||
imgl.handle = w32.ImageList_Create(cx, cy, flags, cInitial, cGrow)
|
||||
return imgl
|
||||
}
|
||||
|
||||
func (im *ImageList) Handle() w32.HIMAGELIST {
|
||||
return im.handle
|
||||
}
|
||||
|
||||
func (im *ImageList) Destroy() bool {
|
||||
return w32.ImageList_Destroy(im.handle)
|
||||
}
|
||||
|
||||
func (im *ImageList) SetImageCount(uNewCount uint) bool {
|
||||
return w32.ImageList_SetImageCount(im.handle, uNewCount)
|
||||
}
|
||||
|
||||
func (im *ImageList) ImageCount() int {
|
||||
return w32.ImageList_GetImageCount(im.handle)
|
||||
}
|
||||
|
||||
func (im *ImageList) AddIcon(icon *Icon) int {
|
||||
return w32.ImageList_AddIcon(im.handle, icon.Handle())
|
||||
}
|
||||
|
||||
func (im *ImageList) AddResIcon(iconID uint16) {
|
||||
if ico, err := NewIconFromResource(GetAppInstance(), iconID); err == nil {
|
||||
im.AddIcon(ico)
|
||||
return
|
||||
}
|
||||
panic(fmt.Sprintf("missing icon with icon ID: %d", iconID))
|
||||
}
|
||||
|
||||
func (im *ImageList) RemoveAll() bool {
|
||||
return w32.ImageList_RemoveAll(im.handle)
|
||||
}
|
||||
|
||||
func (im *ImageList) Remove(i int) bool {
|
||||
return w32.ImageList_Remove(im.handle, i)
|
||||
}
|
||||
59
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/imageview.go
generated
vendored
Normal file
59
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/imageview.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
|
||||
type ImageView struct {
|
||||
ControlBase
|
||||
|
||||
bmp *Bitmap
|
||||
}
|
||||
|
||||
func NewImageView(parent Controller) *ImageView {
|
||||
iv := new(ImageView)
|
||||
|
||||
iv.InitWindow("winc_ImageView", parent, w32.WS_EX_CONTROLPARENT, w32.WS_CHILD|w32.WS_VISIBLE)
|
||||
RegMsgHandler(iv)
|
||||
|
||||
iv.SetFont(DefaultFont)
|
||||
iv.SetText("")
|
||||
iv.SetSize(200, 65)
|
||||
return iv
|
||||
}
|
||||
|
||||
func (iv *ImageView) DrawImageFile(filepath string) error {
|
||||
bmp, err := NewBitmapFromFile(filepath, RGB(255, 255, 0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iv.bmp = bmp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iv *ImageView) DrawImage(bmp *Bitmap) {
|
||||
iv.bmp = bmp
|
||||
}
|
||||
|
||||
func (iv *ImageView) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_SIZE, w32.WM_SIZING:
|
||||
iv.Invalidate(true)
|
||||
|
||||
case w32.WM_ERASEBKGND:
|
||||
return 1 // important
|
||||
|
||||
case w32.WM_PAINT:
|
||||
if iv.bmp != nil {
|
||||
canvas := NewCanvasFromHwnd(iv.hwnd)
|
||||
defer canvas.Dispose()
|
||||
iv.SetSize(iv.bmp.Size())
|
||||
canvas.DrawBitmap(iv.bmp, 0, 0)
|
||||
}
|
||||
}
|
||||
return w32.DefWindowProc(iv.hwnd, msg, wparam, lparam)
|
||||
}
|
||||
342
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/imageviewbox.go
generated
vendored
Normal file
342
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/imageviewbox.go
generated
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type direction int
|
||||
|
||||
const (
|
||||
DirNone direction = iota
|
||||
DirX
|
||||
DirY
|
||||
DirX2
|
||||
DirY2
|
||||
)
|
||||
|
||||
var ImageBoxPen = NewPen(w32.PS_GEOMETRIC, 2, NewSolidColorBrush(RGB(140, 140, 220)))
|
||||
var ImageBoxHiPen = NewPen(w32.PS_GEOMETRIC, 2, NewSolidColorBrush(RGB(220, 140, 140)))
|
||||
var ImageBoxMarkBrush = NewSolidColorBrush(RGB(40, 40, 40))
|
||||
var ImageBoxMarkPen = NewPen(w32.PS_GEOMETRIC, 2, ImageBoxMarkBrush)
|
||||
|
||||
type ImageBox struct {
|
||||
Name string
|
||||
Type int
|
||||
X, Y, X2, Y2 int
|
||||
|
||||
underMouse bool // dynamic value
|
||||
}
|
||||
|
||||
func (b *ImageBox) Rect() *Rect {
|
||||
return NewRect(b.X, b.Y, b.X2, b.Y2)
|
||||
}
|
||||
|
||||
// ImageViewBox is image view with boxes.
|
||||
type ImageViewBox struct {
|
||||
ControlBase
|
||||
|
||||
bmp *Bitmap
|
||||
mouseLeft bool
|
||||
modified bool // used by GUI to see if any image box modified
|
||||
|
||||
add bool
|
||||
|
||||
Boxes []*ImageBox // might be persisted to file
|
||||
dragBox *ImageBox
|
||||
selBox *ImageBox
|
||||
|
||||
dragStartX, dragStartY int
|
||||
resize direction
|
||||
|
||||
onSelectedChange EventManager
|
||||
onAdd EventManager
|
||||
onModify EventManager
|
||||
}
|
||||
|
||||
func NewImageViewBox(parent Controller) *ImageViewBox {
|
||||
iv := new(ImageViewBox)
|
||||
|
||||
iv.InitWindow("winc_ImageViewBox", parent, w32.WS_EX_CONTROLPARENT, w32.WS_CHILD|w32.WS_VISIBLE)
|
||||
RegMsgHandler(iv)
|
||||
|
||||
iv.SetFont(DefaultFont)
|
||||
iv.SetText("")
|
||||
iv.SetSize(200, 65)
|
||||
|
||||
return iv
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) OnSelectedChange() *EventManager {
|
||||
return &iv.onSelectedChange
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) OnAdd() *EventManager {
|
||||
return &iv.onAdd
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) OnModify() *EventManager {
|
||||
return &iv.onModify
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) IsModified() bool { return iv.modified }
|
||||
func (iv *ImageViewBox) SetModified(modified bool) { iv.modified = modified }
|
||||
func (iv *ImageViewBox) IsLoaded() bool { return iv.bmp != nil }
|
||||
func (iv *ImageViewBox) AddMode() bool { return iv.add }
|
||||
func (iv *ImageViewBox) SetAddMode(add bool) { iv.add = add }
|
||||
func (iv *ImageViewBox) HasSelected() bool { return iv.selBox != nil && iv.bmp != nil }
|
||||
|
||||
func (iv *ImageViewBox) wasModified() {
|
||||
iv.modified = true
|
||||
iv.onModify.Fire(NewEvent(iv, nil))
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) DeleteSelected() {
|
||||
if iv.selBox != nil {
|
||||
for i, b := range iv.Boxes {
|
||||
if b == iv.selBox {
|
||||
iv.Boxes = append(iv.Boxes[:i], iv.Boxes[i+1:]...)
|
||||
iv.selBox = nil
|
||||
iv.Invalidate(true)
|
||||
iv.wasModified()
|
||||
iv.onSelectedChange.Fire(NewEvent(iv, nil))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) NameSelected() string {
|
||||
if iv.selBox != nil {
|
||||
return iv.selBox.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) SetNameSelected(name string) {
|
||||
if iv.selBox != nil {
|
||||
iv.selBox.Name = name
|
||||
iv.wasModified()
|
||||
}
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) TypeSelected() int {
|
||||
if iv.selBox != nil {
|
||||
return iv.selBox.Type
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) SetTypeSelected(typ int) {
|
||||
if iv.selBox != nil {
|
||||
iv.selBox.Type = typ
|
||||
iv.wasModified()
|
||||
}
|
||||
}
|
||||
|
||||
func (ib *ImageViewBox) updateHighlight(x, y int) bool {
|
||||
var changed bool
|
||||
for _, b := range ib.Boxes {
|
||||
under := x >= b.X && y >= b.Y && x <= b.X2 && y <= b.Y2
|
||||
if b.underMouse != under {
|
||||
changed = true
|
||||
}
|
||||
b.underMouse = under
|
||||
/*if sel {
|
||||
break // allow only one to be underMouse
|
||||
}*/
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
func (ib *ImageViewBox) isUnderMouse(x, y int) *ImageBox {
|
||||
for _, b := range ib.Boxes {
|
||||
if x >= b.X && y >= b.Y && x <= b.X2 && y <= b.Y2 {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *ImageViewBox) getCursor(x, y int) uint16 {
|
||||
for _, b := range ib.Boxes {
|
||||
switch d := ib.resizingDirection(b, x, y); d {
|
||||
case DirY, DirY2:
|
||||
return w32.IDC_SIZENS
|
||||
case DirX, DirX2:
|
||||
return w32.IDC_SIZEWE
|
||||
}
|
||||
// w32.IDC_SIZEALL or w32.IDC_SIZE for resize
|
||||
}
|
||||
return w32.IDC_ARROW
|
||||
}
|
||||
|
||||
func (ib *ImageViewBox) resizingDirection(b *ImageBox, x, y int) direction {
|
||||
if b == nil {
|
||||
return DirNone
|
||||
}
|
||||
switch {
|
||||
case b.X == x || b.X == x-1 || b.X == x+1:
|
||||
return DirX
|
||||
case b.X2 == x || b.X2 == x-1 || b.X2 == x+1:
|
||||
return DirX2
|
||||
case b.Y == y || b.Y == y-1 || b.Y == y+1:
|
||||
return DirY
|
||||
case b.Y2 == y || b.Y2 == y-1 || b.Y2 == y+1:
|
||||
return DirY2
|
||||
}
|
||||
return DirNone
|
||||
}
|
||||
|
||||
func (ib *ImageViewBox) resizeToDirection(b *ImageBox, x, y int) {
|
||||
switch ib.resize {
|
||||
case DirX:
|
||||
b.X = x
|
||||
case DirY:
|
||||
b.Y = y
|
||||
case DirX2:
|
||||
b.X2 = x
|
||||
case DirY2:
|
||||
b.Y2 = y
|
||||
}
|
||||
}
|
||||
|
||||
func (ib *ImageViewBox) drag(b *ImageBox, x, y int) {
|
||||
w, h := b.X2-b.X, b.Y2-b.Y
|
||||
|
||||
nx := ib.dragStartX - b.X
|
||||
ny := ib.dragStartY - b.Y
|
||||
|
||||
b.X = x - nx
|
||||
b.Y = y - ny
|
||||
b.X2 = b.X + w
|
||||
b.Y2 = b.Y + h
|
||||
|
||||
ib.dragStartX, ib.dragStartY = x, y
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) DrawImageFile(filepath string) (err error) {
|
||||
iv.bmp, err = NewBitmapFromFile(filepath, RGB(255, 255, 0))
|
||||
iv.selBox = nil
|
||||
iv.modified = false
|
||||
iv.onSelectedChange.Fire(NewEvent(iv, nil))
|
||||
iv.onModify.Fire(NewEvent(iv, nil))
|
||||
return
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) DrawImage(bmp *Bitmap) {
|
||||
iv.bmp = bmp
|
||||
iv.selBox = nil
|
||||
iv.modified = false
|
||||
iv.onSelectedChange.Fire(NewEvent(iv, nil))
|
||||
iv.onModify.Fire(NewEvent(iv, nil))
|
||||
}
|
||||
|
||||
func (iv *ImageViewBox) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_SIZE, w32.WM_SIZING:
|
||||
iv.Invalidate(true)
|
||||
|
||||
case w32.WM_ERASEBKGND:
|
||||
return 1 // important
|
||||
|
||||
case w32.WM_CREATE:
|
||||
internalTrackMouseEvent(iv.hwnd)
|
||||
|
||||
case w32.WM_PAINT:
|
||||
if iv.bmp != nil {
|
||||
canvas := NewCanvasFromHwnd(iv.hwnd)
|
||||
defer canvas.Dispose()
|
||||
iv.SetSize(iv.bmp.Size())
|
||||
canvas.DrawBitmap(iv.bmp, 0, 0)
|
||||
|
||||
for _, b := range iv.Boxes {
|
||||
// old code used NewSystemColorBrush(w32.COLOR_BTNFACE) w32.COLOR_WINDOW
|
||||
pen := ImageBoxPen
|
||||
if b.underMouse {
|
||||
pen = ImageBoxHiPen
|
||||
}
|
||||
canvas.DrawRect(b.Rect(), pen)
|
||||
|
||||
if b == iv.selBox {
|
||||
x1 := []int{b.X, b.X2, b.X2, b.X}
|
||||
y1 := []int{b.Y, b.Y, b.Y2, b.Y2}
|
||||
|
||||
for i := 0; i < len(x1); i++ {
|
||||
r := NewRect(x1[i]-2, y1[i]-2, x1[i]+2, y1[i]+2)
|
||||
canvas.DrawFillRect(r, ImageBoxMarkPen, ImageBoxMarkBrush)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case w32.WM_MOUSEMOVE:
|
||||
x, y := genPoint(lparam)
|
||||
|
||||
if iv.dragBox != nil {
|
||||
if iv.resize == DirNone {
|
||||
iv.drag(iv.dragBox, x, y)
|
||||
iv.wasModified()
|
||||
} else {
|
||||
iv.resizeToDirection(iv.dragBox, x, y)
|
||||
iv.wasModified()
|
||||
}
|
||||
iv.Invalidate(true)
|
||||
|
||||
} else {
|
||||
if !iv.add {
|
||||
w32.SetCursor(w32.LoadCursorWithResourceID(0, iv.getCursor(x, y)))
|
||||
}
|
||||
// do not call repaint if underMouse item did not change.
|
||||
if iv.updateHighlight(x, y) {
|
||||
iv.Invalidate(true)
|
||||
}
|
||||
}
|
||||
|
||||
if iv.mouseLeft {
|
||||
internalTrackMouseEvent(iv.hwnd)
|
||||
iv.mouseLeft = false
|
||||
}
|
||||
|
||||
case w32.WM_MOUSELEAVE:
|
||||
iv.dragBox = nil
|
||||
iv.mouseLeft = true
|
||||
iv.updateHighlight(-1, -1)
|
||||
iv.Invalidate(true)
|
||||
|
||||
case w32.WM_LBUTTONUP:
|
||||
iv.dragBox = nil
|
||||
|
||||
case w32.WM_LBUTTONDOWN:
|
||||
x, y := genPoint(lparam)
|
||||
if iv.add {
|
||||
now := time.Now()
|
||||
s := fmt.Sprintf("field%s", now.Format("020405"))
|
||||
b := &ImageBox{Name: s, underMouse: true, X: x, Y: y, X2: x + 150, Y2: y + 30}
|
||||
iv.Boxes = append(iv.Boxes, b)
|
||||
iv.selBox = b
|
||||
iv.wasModified()
|
||||
iv.onAdd.Fire(NewEvent(iv, nil))
|
||||
} else {
|
||||
iv.dragBox = iv.isUnderMouse(x, y)
|
||||
iv.selBox = iv.dragBox
|
||||
iv.dragStartX, iv.dragStartY = x, y
|
||||
iv.resize = iv.resizingDirection(iv.dragBox, x, y)
|
||||
}
|
||||
iv.Invalidate(true)
|
||||
iv.onSelectedChange.Fire(NewEvent(iv, nil))
|
||||
|
||||
case w32.WM_RBUTTONDOWN:
|
||||
|
||||
}
|
||||
return w32.DefWindowProc(iv.hwnd, msg, wparam, lparam)
|
||||
}
|
||||
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/init.go
generated
vendored
Normal file
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/init.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gControllerRegistry = make(map[w32.HWND]Controller)
|
||||
gRegisteredClasses = make([]string, 0)
|
||||
|
||||
var si w32.GdiplusStartupInput
|
||||
si.GdiplusVersion = 1
|
||||
w32.GdiplusStartup(&si, nil)
|
||||
}
|
||||
440
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/keyboard.go
generated
vendored
Normal file
440
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/keyboard.go
generated
vendored
Normal file
@@ -0,0 +1,440 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type Key uint16
|
||||
|
||||
func (k Key) String() string {
|
||||
return key2string[k]
|
||||
}
|
||||
|
||||
const (
|
||||
KeyLButton Key = w32.VK_LBUTTON
|
||||
KeyRButton Key = w32.VK_RBUTTON
|
||||
KeyCancel Key = w32.VK_CANCEL
|
||||
KeyMButton Key = w32.VK_MBUTTON
|
||||
KeyXButton1 Key = w32.VK_XBUTTON1
|
||||
KeyXButton2 Key = w32.VK_XBUTTON2
|
||||
KeyBack Key = w32.VK_BACK
|
||||
KeyTab Key = w32.VK_TAB
|
||||
KeyClear Key = w32.VK_CLEAR
|
||||
KeyReturn Key = w32.VK_RETURN
|
||||
KeyShift Key = w32.VK_SHIFT
|
||||
KeyControl Key = w32.VK_CONTROL
|
||||
KeyAlt Key = w32.VK_MENU
|
||||
KeyMenu Key = w32.VK_MENU
|
||||
KeyPause Key = w32.VK_PAUSE
|
||||
KeyCapital Key = w32.VK_CAPITAL
|
||||
KeyKana Key = w32.VK_KANA
|
||||
KeyHangul Key = w32.VK_HANGUL
|
||||
KeyJunja Key = w32.VK_JUNJA
|
||||
KeyFinal Key = w32.VK_FINAL
|
||||
KeyHanja Key = w32.VK_HANJA
|
||||
KeyKanji Key = w32.VK_KANJI
|
||||
KeyEscape Key = w32.VK_ESCAPE
|
||||
KeyConvert Key = w32.VK_CONVERT
|
||||
KeyNonconvert Key = w32.VK_NONCONVERT
|
||||
KeyAccept Key = w32.VK_ACCEPT
|
||||
KeyModeChange Key = w32.VK_MODECHANGE
|
||||
KeySpace Key = w32.VK_SPACE
|
||||
KeyPrior Key = w32.VK_PRIOR
|
||||
KeyNext Key = w32.VK_NEXT
|
||||
KeyEnd Key = w32.VK_END
|
||||
KeyHome Key = w32.VK_HOME
|
||||
KeyLeft Key = w32.VK_LEFT
|
||||
KeyUp Key = w32.VK_UP
|
||||
KeyRight Key = w32.VK_RIGHT
|
||||
KeyDown Key = w32.VK_DOWN
|
||||
KeySelect Key = w32.VK_SELECT
|
||||
KeyPrint Key = w32.VK_PRINT
|
||||
KeyExecute Key = w32.VK_EXECUTE
|
||||
KeySnapshot Key = w32.VK_SNAPSHOT
|
||||
KeyInsert Key = w32.VK_INSERT
|
||||
KeyDelete Key = w32.VK_DELETE
|
||||
KeyHelp Key = w32.VK_HELP
|
||||
Key0 Key = 0x30
|
||||
Key1 Key = 0x31
|
||||
Key2 Key = 0x32
|
||||
Key3 Key = 0x33
|
||||
Key4 Key = 0x34
|
||||
Key5 Key = 0x35
|
||||
Key6 Key = 0x36
|
||||
Key7 Key = 0x37
|
||||
Key8 Key = 0x38
|
||||
Key9 Key = 0x39
|
||||
KeyA Key = 0x41
|
||||
KeyB Key = 0x42
|
||||
KeyC Key = 0x43
|
||||
KeyD Key = 0x44
|
||||
KeyE Key = 0x45
|
||||
KeyF Key = 0x46
|
||||
KeyG Key = 0x47
|
||||
KeyH Key = 0x48
|
||||
KeyI Key = 0x49
|
||||
KeyJ Key = 0x4A
|
||||
KeyK Key = 0x4B
|
||||
KeyL Key = 0x4C
|
||||
KeyM Key = 0x4D
|
||||
KeyN Key = 0x4E
|
||||
KeyO Key = 0x4F
|
||||
KeyP Key = 0x50
|
||||
KeyQ Key = 0x51
|
||||
KeyR Key = 0x52
|
||||
KeyS Key = 0x53
|
||||
KeyT Key = 0x54
|
||||
KeyU Key = 0x55
|
||||
KeyV Key = 0x56
|
||||
KeyW Key = 0x57
|
||||
KeyX Key = 0x58
|
||||
KeyY Key = 0x59
|
||||
KeyZ Key = 0x5A
|
||||
KeyLWIN Key = w32.VK_LWIN
|
||||
KeyRWIN Key = w32.VK_RWIN
|
||||
KeyApps Key = w32.VK_APPS
|
||||
KeySleep Key = w32.VK_SLEEP
|
||||
KeyNumpad0 Key = w32.VK_NUMPAD0
|
||||
KeyNumpad1 Key = w32.VK_NUMPAD1
|
||||
KeyNumpad2 Key = w32.VK_NUMPAD2
|
||||
KeyNumpad3 Key = w32.VK_NUMPAD3
|
||||
KeyNumpad4 Key = w32.VK_NUMPAD4
|
||||
KeyNumpad5 Key = w32.VK_NUMPAD5
|
||||
KeyNumpad6 Key = w32.VK_NUMPAD6
|
||||
KeyNumpad7 Key = w32.VK_NUMPAD7
|
||||
KeyNumpad8 Key = w32.VK_NUMPAD8
|
||||
KeyNumpad9 Key = w32.VK_NUMPAD9
|
||||
KeyMultiply Key = w32.VK_MULTIPLY
|
||||
KeyAdd Key = w32.VK_ADD
|
||||
KeySeparator Key = w32.VK_SEPARATOR
|
||||
KeySubtract Key = w32.VK_SUBTRACT
|
||||
KeyDecimal Key = w32.VK_DECIMAL
|
||||
KeyDivide Key = w32.VK_DIVIDE
|
||||
KeyF1 Key = w32.VK_F1
|
||||
KeyF2 Key = w32.VK_F2
|
||||
KeyF3 Key = w32.VK_F3
|
||||
KeyF4 Key = w32.VK_F4
|
||||
KeyF5 Key = w32.VK_F5
|
||||
KeyF6 Key = w32.VK_F6
|
||||
KeyF7 Key = w32.VK_F7
|
||||
KeyF8 Key = w32.VK_F8
|
||||
KeyF9 Key = w32.VK_F9
|
||||
KeyF10 Key = w32.VK_F10
|
||||
KeyF11 Key = w32.VK_F11
|
||||
KeyF12 Key = w32.VK_F12
|
||||
KeyF13 Key = w32.VK_F13
|
||||
KeyF14 Key = w32.VK_F14
|
||||
KeyF15 Key = w32.VK_F15
|
||||
KeyF16 Key = w32.VK_F16
|
||||
KeyF17 Key = w32.VK_F17
|
||||
KeyF18 Key = w32.VK_F18
|
||||
KeyF19 Key = w32.VK_F19
|
||||
KeyF20 Key = w32.VK_F20
|
||||
KeyF21 Key = w32.VK_F21
|
||||
KeyF22 Key = w32.VK_F22
|
||||
KeyF23 Key = w32.VK_F23
|
||||
KeyF24 Key = w32.VK_F24
|
||||
KeyNumlock Key = w32.VK_NUMLOCK
|
||||
KeyScroll Key = w32.VK_SCROLL
|
||||
KeyLShift Key = w32.VK_LSHIFT
|
||||
KeyRShift Key = w32.VK_RSHIFT
|
||||
KeyLControl Key = w32.VK_LCONTROL
|
||||
KeyRControl Key = w32.VK_RCONTROL
|
||||
KeyLAlt Key = w32.VK_LMENU
|
||||
KeyLMenu Key = w32.VK_LMENU
|
||||
KeyRAlt Key = w32.VK_RMENU
|
||||
KeyRMenu Key = w32.VK_RMENU
|
||||
KeyBrowserBack Key = w32.VK_BROWSER_BACK
|
||||
KeyBrowserForward Key = w32.VK_BROWSER_FORWARD
|
||||
KeyBrowserRefresh Key = w32.VK_BROWSER_REFRESH
|
||||
KeyBrowserStop Key = w32.VK_BROWSER_STOP
|
||||
KeyBrowserSearch Key = w32.VK_BROWSER_SEARCH
|
||||
KeyBrowserFavorites Key = w32.VK_BROWSER_FAVORITES
|
||||
KeyBrowserHome Key = w32.VK_BROWSER_HOME
|
||||
KeyVolumeMute Key = w32.VK_VOLUME_MUTE
|
||||
KeyVolumeDown Key = w32.VK_VOLUME_DOWN
|
||||
KeyVolumeUp Key = w32.VK_VOLUME_UP
|
||||
KeyMediaNextTrack Key = w32.VK_MEDIA_NEXT_TRACK
|
||||
KeyMediaPrevTrack Key = w32.VK_MEDIA_PREV_TRACK
|
||||
KeyMediaStop Key = w32.VK_MEDIA_STOP
|
||||
KeyMediaPlayPause Key = w32.VK_MEDIA_PLAY_PAUSE
|
||||
KeyLaunchMail Key = w32.VK_LAUNCH_MAIL
|
||||
KeyLaunchMediaSelect Key = w32.VK_LAUNCH_MEDIA_SELECT
|
||||
KeyLaunchApp1 Key = w32.VK_LAUNCH_APP1
|
||||
KeyLaunchApp2 Key = w32.VK_LAUNCH_APP2
|
||||
KeyOEM1 Key = w32.VK_OEM_1
|
||||
KeyOEMPlus Key = w32.VK_OEM_PLUS
|
||||
KeyOEMComma Key = w32.VK_OEM_COMMA
|
||||
KeyOEMMinus Key = w32.VK_OEM_MINUS
|
||||
KeyOEMPeriod Key = w32.VK_OEM_PERIOD
|
||||
KeyOEM2 Key = w32.VK_OEM_2
|
||||
KeyOEM3 Key = w32.VK_OEM_3
|
||||
KeyOEM4 Key = w32.VK_OEM_4
|
||||
KeyOEM5 Key = w32.VK_OEM_5
|
||||
KeyOEM6 Key = w32.VK_OEM_6
|
||||
KeyOEM7 Key = w32.VK_OEM_7
|
||||
KeyOEM8 Key = w32.VK_OEM_8
|
||||
KeyOEM102 Key = w32.VK_OEM_102
|
||||
KeyProcessKey Key = w32.VK_PROCESSKEY
|
||||
KeyPacket Key = w32.VK_PACKET
|
||||
KeyAttn Key = w32.VK_ATTN
|
||||
KeyCRSel Key = w32.VK_CRSEL
|
||||
KeyEXSel Key = w32.VK_EXSEL
|
||||
KeyErEOF Key = w32.VK_EREOF
|
||||
KeyPlay Key = w32.VK_PLAY
|
||||
KeyZoom Key = w32.VK_ZOOM
|
||||
KeyNoName Key = w32.VK_NONAME
|
||||
KeyPA1 Key = w32.VK_PA1
|
||||
KeyOEMClear Key = w32.VK_OEM_CLEAR
|
||||
)
|
||||
|
||||
var key2string = map[Key]string{
|
||||
KeyLButton: "LButton",
|
||||
KeyRButton: "RButton",
|
||||
KeyCancel: "Cancel",
|
||||
KeyMButton: "MButton",
|
||||
KeyXButton1: "XButton1",
|
||||
KeyXButton2: "XButton2",
|
||||
KeyBack: "Back",
|
||||
KeyTab: "Tab",
|
||||
KeyClear: "Clear",
|
||||
KeyReturn: "Return",
|
||||
KeyShift: "Shift",
|
||||
KeyControl: "Control",
|
||||
KeyAlt: "Alt / Menu",
|
||||
KeyPause: "Pause",
|
||||
KeyCapital: "Capital",
|
||||
KeyKana: "Kana / Hangul",
|
||||
KeyJunja: "Junja",
|
||||
KeyFinal: "Final",
|
||||
KeyHanja: "Hanja / Kanji",
|
||||
KeyEscape: "Escape",
|
||||
KeyConvert: "Convert",
|
||||
KeyNonconvert: "Nonconvert",
|
||||
KeyAccept: "Accept",
|
||||
KeyModeChange: "ModeChange",
|
||||
KeySpace: "Space",
|
||||
KeyPrior: "Prior",
|
||||
KeyNext: "Next",
|
||||
KeyEnd: "End",
|
||||
KeyHome: "Home",
|
||||
KeyLeft: "Left",
|
||||
KeyUp: "Up",
|
||||
KeyRight: "Right",
|
||||
KeyDown: "Down",
|
||||
KeySelect: "Select",
|
||||
KeyPrint: "Print",
|
||||
KeyExecute: "Execute",
|
||||
KeySnapshot: "Snapshot",
|
||||
KeyInsert: "Insert",
|
||||
KeyDelete: "Delete",
|
||||
KeyHelp: "Help",
|
||||
Key0: "0",
|
||||
Key1: "1",
|
||||
Key2: "2",
|
||||
Key3: "3",
|
||||
Key4: "4",
|
||||
Key5: "5",
|
||||
Key6: "6",
|
||||
Key7: "7",
|
||||
Key8: "8",
|
||||
Key9: "9",
|
||||
KeyA: "A",
|
||||
KeyB: "B",
|
||||
KeyC: "C",
|
||||
KeyD: "D",
|
||||
KeyE: "E",
|
||||
KeyF: "F",
|
||||
KeyG: "G",
|
||||
KeyH: "H",
|
||||
KeyI: "I",
|
||||
KeyJ: "J",
|
||||
KeyK: "K",
|
||||
KeyL: "L",
|
||||
KeyM: "M",
|
||||
KeyN: "N",
|
||||
KeyO: "O",
|
||||
KeyP: "P",
|
||||
KeyQ: "Q",
|
||||
KeyR: "R",
|
||||
KeyS: "S",
|
||||
KeyT: "T",
|
||||
KeyU: "U",
|
||||
KeyV: "V",
|
||||
KeyW: "W",
|
||||
KeyX: "X",
|
||||
KeyY: "Y",
|
||||
KeyZ: "Z",
|
||||
KeyLWIN: "LWIN",
|
||||
KeyRWIN: "RWIN",
|
||||
KeyApps: "Apps",
|
||||
KeySleep: "Sleep",
|
||||
KeyNumpad0: "Numpad0",
|
||||
KeyNumpad1: "Numpad1",
|
||||
KeyNumpad2: "Numpad2",
|
||||
KeyNumpad3: "Numpad3",
|
||||
KeyNumpad4: "Numpad4",
|
||||
KeyNumpad5: "Numpad5",
|
||||
KeyNumpad6: "Numpad6",
|
||||
KeyNumpad7: "Numpad7",
|
||||
KeyNumpad8: "Numpad8",
|
||||
KeyNumpad9: "Numpad9",
|
||||
KeyMultiply: "Multiply",
|
||||
KeyAdd: "Add",
|
||||
KeySeparator: "Separator",
|
||||
KeySubtract: "Subtract",
|
||||
KeyDecimal: "Decimal",
|
||||
KeyDivide: "Divide",
|
||||
KeyF1: "F1",
|
||||
KeyF2: "F2",
|
||||
KeyF3: "F3",
|
||||
KeyF4: "F4",
|
||||
KeyF5: "F5",
|
||||
KeyF6: "F6",
|
||||
KeyF7: "F7",
|
||||
KeyF8: "F8",
|
||||
KeyF9: "F9",
|
||||
KeyF10: "F10",
|
||||
KeyF11: "F11",
|
||||
KeyF12: "F12",
|
||||
KeyF13: "F13",
|
||||
KeyF14: "F14",
|
||||
KeyF15: "F15",
|
||||
KeyF16: "F16",
|
||||
KeyF17: "F17",
|
||||
KeyF18: "F18",
|
||||
KeyF19: "F19",
|
||||
KeyF20: "F20",
|
||||
KeyF21: "F21",
|
||||
KeyF22: "F22",
|
||||
KeyF23: "F23",
|
||||
KeyF24: "F24",
|
||||
KeyNumlock: "Numlock",
|
||||
KeyScroll: "Scroll",
|
||||
KeyLShift: "LShift",
|
||||
KeyRShift: "RShift",
|
||||
KeyLControl: "LControl",
|
||||
KeyRControl: "RControl",
|
||||
KeyLMenu: "LMenu",
|
||||
KeyRMenu: "RMenu",
|
||||
KeyBrowserBack: "BrowserBack",
|
||||
KeyBrowserForward: "BrowserForward",
|
||||
KeyBrowserRefresh: "BrowserRefresh",
|
||||
KeyBrowserStop: "BrowserStop",
|
||||
KeyBrowserSearch: "BrowserSearch",
|
||||
KeyBrowserFavorites: "BrowserFavorites",
|
||||
KeyBrowserHome: "BrowserHome",
|
||||
KeyVolumeMute: "VolumeMute",
|
||||
KeyVolumeDown: "VolumeDown",
|
||||
KeyVolumeUp: "VolumeUp",
|
||||
KeyMediaNextTrack: "MediaNextTrack",
|
||||
KeyMediaPrevTrack: "MediaPrevTrack",
|
||||
KeyMediaStop: "MediaStop",
|
||||
KeyMediaPlayPause: "MediaPlayPause",
|
||||
KeyLaunchMail: "LaunchMail",
|
||||
KeyLaunchMediaSelect: "LaunchMediaSelect",
|
||||
KeyLaunchApp1: "LaunchApp1",
|
||||
KeyLaunchApp2: "LaunchApp2",
|
||||
KeyOEM1: "OEM1",
|
||||
KeyOEMPlus: "OEMPlus",
|
||||
KeyOEMComma: "OEMComma",
|
||||
KeyOEMMinus: "OEMMinus",
|
||||
KeyOEMPeriod: "OEMPeriod",
|
||||
KeyOEM2: "OEM2",
|
||||
KeyOEM3: "OEM3",
|
||||
KeyOEM4: "OEM4",
|
||||
KeyOEM5: "OEM5",
|
||||
KeyOEM6: "OEM6",
|
||||
KeyOEM7: "OEM7",
|
||||
KeyOEM8: "OEM8",
|
||||
KeyOEM102: "OEM102",
|
||||
KeyProcessKey: "ProcessKey",
|
||||
KeyPacket: "Packet",
|
||||
KeyAttn: "Attn",
|
||||
KeyCRSel: "CRSel",
|
||||
KeyEXSel: "EXSel",
|
||||
KeyErEOF: "ErEOF",
|
||||
KeyPlay: "Play",
|
||||
KeyZoom: "Zoom",
|
||||
KeyNoName: "NoName",
|
||||
KeyPA1: "PA1",
|
||||
KeyOEMClear: "OEMClear",
|
||||
}
|
||||
|
||||
type Modifiers byte
|
||||
|
||||
func (m Modifiers) String() string {
|
||||
return modifiers2string[m]
|
||||
}
|
||||
|
||||
var modifiers2string = map[Modifiers]string{
|
||||
ModShift: "Shift",
|
||||
ModControl: "Ctrl",
|
||||
ModControl | ModShift: "Ctrl+Shift",
|
||||
ModAlt: "Alt",
|
||||
ModAlt | ModShift: "Alt+Shift",
|
||||
ModAlt | ModControl | ModShift: "Alt+Ctrl+Shift",
|
||||
}
|
||||
|
||||
const (
|
||||
ModShift Modifiers = 1 << iota
|
||||
ModControl
|
||||
ModAlt
|
||||
)
|
||||
|
||||
func ModifiersDown() Modifiers {
|
||||
var m Modifiers
|
||||
|
||||
if ShiftDown() {
|
||||
m |= ModShift
|
||||
}
|
||||
if ControlDown() {
|
||||
m |= ModControl
|
||||
}
|
||||
if AltDown() {
|
||||
m |= ModAlt
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type Shortcut struct {
|
||||
Modifiers Modifiers
|
||||
Key Key
|
||||
}
|
||||
|
||||
func (s Shortcut) String() string {
|
||||
m := s.Modifiers.String()
|
||||
if m == "" {
|
||||
return s.Key.String()
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
b.WriteString(m)
|
||||
b.WriteRune('+')
|
||||
b.WriteString(s.Key.String())
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func AltDown() bool {
|
||||
return w32.GetKeyState(int32(KeyAlt))>>15 != 0
|
||||
}
|
||||
|
||||
func ControlDown() bool {
|
||||
return w32.GetKeyState(int32(KeyControl))>>15 != 0
|
||||
}
|
||||
|
||||
func ShiftDown() bool {
|
||||
return w32.GetKeyState(int32(KeyShift))>>15 != 0
|
||||
}
|
||||
31
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/label.go
generated
vendored
Normal file
31
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/label.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type Label struct {
|
||||
ControlBase
|
||||
}
|
||||
|
||||
func NewLabel(parent Controller) *Label {
|
||||
lb := new(Label)
|
||||
|
||||
lb.InitControl("STATIC", parent, 0, w32.WS_CHILD|w32.WS_VISIBLE|w32.SS_LEFTNOWORDWRAP)
|
||||
RegMsgHandler(lb)
|
||||
|
||||
lb.SetFont(DefaultFont)
|
||||
lb.SetText("Label")
|
||||
lb.SetSize(100, 25)
|
||||
return lb
|
||||
}
|
||||
|
||||
func (lb *Label) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
return w32.DefWindowProc(lb.hwnd, msg, wparam, lparam)
|
||||
}
|
||||
222
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/layout.go
generated
vendored
Normal file
222
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/layout.go
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
// Dockable component must satisfy interface to be docked.
|
||||
type Dockable interface {
|
||||
Handle() w32.HWND
|
||||
|
||||
Pos() (x, y int)
|
||||
Width() int
|
||||
Height() int
|
||||
Visible() bool
|
||||
|
||||
SetPos(x, y int)
|
||||
SetSize(width, height int)
|
||||
|
||||
OnMouseMove() *EventManager
|
||||
OnLBUp() *EventManager
|
||||
}
|
||||
|
||||
// DockAllow is window, panel or other component that satisfies interface.
|
||||
type DockAllow interface {
|
||||
Handle() w32.HWND
|
||||
ClientWidth() int
|
||||
ClientHeight() int
|
||||
SetLayout(mng LayoutManager)
|
||||
}
|
||||
|
||||
// Various layout managers
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
Top Direction = iota
|
||||
Bottom
|
||||
Left
|
||||
Right
|
||||
Fill
|
||||
)
|
||||
|
||||
type LayoutControl struct {
|
||||
child Dockable
|
||||
dir Direction
|
||||
}
|
||||
|
||||
type LayoutControls []*LayoutControl
|
||||
|
||||
type SimpleDock struct {
|
||||
parent DockAllow
|
||||
layoutCtl LayoutControls
|
||||
loadedState bool
|
||||
}
|
||||
|
||||
// CtlState gets saved and loaded from json
|
||||
type CtlState struct {
|
||||
X, Y, Width, Height int
|
||||
}
|
||||
|
||||
type LayoutState struct {
|
||||
WindowState string
|
||||
Controls []*CtlState
|
||||
}
|
||||
|
||||
func (lc LayoutControls) Len() int { return len(lc) }
|
||||
func (lc LayoutControls) Swap(i, j int) { lc[i], lc[j] = lc[j], lc[i] }
|
||||
func (lc LayoutControls) Less(i, j int) bool { return lc[i].dir < lc[j].dir }
|
||||
|
||||
func NewSimpleDock(parent DockAllow) *SimpleDock {
|
||||
d := &SimpleDock{parent: parent}
|
||||
parent.SetLayout(d)
|
||||
return d
|
||||
}
|
||||
|
||||
// Layout management for the child controls.
|
||||
func (sd *SimpleDock) Dock(child Dockable, dir Direction) {
|
||||
sd.layoutCtl = append(sd.layoutCtl, &LayoutControl{child, dir})
|
||||
}
|
||||
|
||||
// SaveState of the layout. Only works for Docks with parent set to main form.
|
||||
func (sd *SimpleDock) SaveState(w io.Writer) error {
|
||||
var ls LayoutState
|
||||
|
||||
var wp w32.WINDOWPLACEMENT
|
||||
wp.Length = uint32(unsafe.Sizeof(wp))
|
||||
if !w32.GetWindowPlacement(sd.parent.Handle(), &wp) {
|
||||
return fmt.Errorf("GetWindowPlacement failed")
|
||||
}
|
||||
|
||||
ls.WindowState = fmt.Sprint(
|
||||
wp.Flags, wp.ShowCmd,
|
||||
wp.PtMinPosition.X, wp.PtMinPosition.Y,
|
||||
wp.PtMaxPosition.X, wp.PtMaxPosition.Y,
|
||||
wp.RcNormalPosition.Left, wp.RcNormalPosition.Top,
|
||||
wp.RcNormalPosition.Right, wp.RcNormalPosition.Bottom)
|
||||
|
||||
for _, c := range sd.layoutCtl {
|
||||
x, y := c.child.Pos()
|
||||
w, h := c.child.Width(), c.child.Height()
|
||||
|
||||
ctl := &CtlState{X: x, Y: y, Width: w, Height: h}
|
||||
ls.Controls = append(ls.Controls, ctl)
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(ls); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadState of the layout. Only works for Docks with parent set to main form.
|
||||
func (sd *SimpleDock) LoadState(r io.Reader) error {
|
||||
var ls LayoutState
|
||||
|
||||
if err := json.NewDecoder(r).Decode(&ls); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var wp w32.WINDOWPLACEMENT
|
||||
if _, err := fmt.Sscan(ls.WindowState,
|
||||
&wp.Flags, &wp.ShowCmd,
|
||||
&wp.PtMinPosition.X, &wp.PtMinPosition.Y,
|
||||
&wp.PtMaxPosition.X, &wp.PtMaxPosition.Y,
|
||||
&wp.RcNormalPosition.Left, &wp.RcNormalPosition.Top,
|
||||
&wp.RcNormalPosition.Right, &wp.RcNormalPosition.Bottom); err != nil {
|
||||
return err
|
||||
}
|
||||
wp.Length = uint32(unsafe.Sizeof(wp))
|
||||
|
||||
if !w32.SetWindowPlacement(sd.parent.Handle(), &wp) {
|
||||
return fmt.Errorf("SetWindowPlacement failed")
|
||||
}
|
||||
|
||||
// if number of controls in the saved layout does not match
|
||||
// current number on screen - something changed and we do not reload
|
||||
// rest of control sizes from json
|
||||
if len(sd.layoutCtl) != len(ls.Controls) {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, c := range sd.layoutCtl {
|
||||
c.child.SetPos(ls.Controls[i].X, ls.Controls[i].Y)
|
||||
c.child.SetSize(ls.Controls[i].Width, ls.Controls[i].Height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveStateFile convenience function.
|
||||
func (sd *SimpleDock) SaveStateFile(file string) error {
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sd.SaveState(f)
|
||||
}
|
||||
|
||||
// LoadStateFile loads state ignores error if file is not found.
|
||||
func (sd *SimpleDock) LoadStateFile(file string) error {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil // if file is not found or not accessible ignore it
|
||||
}
|
||||
return sd.LoadState(f)
|
||||
}
|
||||
|
||||
// Update is called to resize child items based on layout directions.
|
||||
func (sd *SimpleDock) Update() {
|
||||
sort.Stable(sd.layoutCtl)
|
||||
|
||||
x, y := 0, 0
|
||||
w, h := sd.parent.ClientWidth(), sd.parent.ClientHeight()
|
||||
winw, winh := w, h
|
||||
|
||||
for _, c := range sd.layoutCtl {
|
||||
// Non visible controls do not preserve space.
|
||||
if !c.child.Visible() {
|
||||
continue
|
||||
}
|
||||
|
||||
switch c.dir {
|
||||
case Top:
|
||||
c.child.SetPos(x, y)
|
||||
c.child.SetSize(w, c.child.Height())
|
||||
h -= c.child.Height()
|
||||
y += c.child.Height()
|
||||
case Bottom:
|
||||
c.child.SetPos(x, winh-c.child.Height())
|
||||
c.child.SetSize(w, c.child.Height())
|
||||
h -= c.child.Height()
|
||||
winh -= c.child.Height()
|
||||
case Left:
|
||||
c.child.SetPos(x, y)
|
||||
c.child.SetSize(c.child.Width(), h)
|
||||
w -= c.child.Width()
|
||||
x += c.child.Width()
|
||||
case Right:
|
||||
c.child.SetPos(winw-c.child.Width(), y)
|
||||
c.child.SetSize(c.child.Width(), h)
|
||||
w -= c.child.Width()
|
||||
winw -= c.child.Width()
|
||||
case Fill:
|
||||
// fill available space
|
||||
c.child.SetPos(x, y)
|
||||
c.child.SetSize(w, h)
|
||||
}
|
||||
//c.child.Invalidate(true)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user