Compare commits

...

36 Commits

Author SHA1 Message Date
Lea Anthony e65118e962 Fixed and refactored context menu support 2021-01-13 22:51:44 +11:00
Lea Anthony de06fc7dcc Remove a ton of unused code 2021-01-13 21:29:20 +11:00
Lea Anthony a86fbbb440 Removal of menu IDs. WARNING: context menus are currently broken 2021-01-13 21:14:44 +11:00
Lea Anthony 3045ec107f attempt at preventing menu flicker when removing an icon 2021-01-13 20:47:08 +11:00
Lea Anthony 3a9557ad30 Support removal of tray icons 2021-01-13 20:38:46 +11:00
Lea Anthony 583153383a Refactor, tidy up and trim the fat! 2021-01-13 20:19:01 +11:00
Lea Anthony 3f53e8fd5f Refactor, tidy up and trim the fat! 2021-01-13 11:47:54 +11:00
Lea Anthony 5c9402323a Initial support for multiple traymenus 2021-01-13 10:28:32 +11:00
Lea Anthony 1921862b53 Partially introduce context menu changes back 2021-01-12 21:20:08 +11:00
Lea Anthony 0f7acd39fc [WIP] Fix the madness 2021-01-12 20:39:19 +11:00
Lea Anthony 1a7507f524 [WIP] Add tray menu store + refactor 2021-01-12 16:55:30 +11:00
Lea Anthony db6dde3e50 [WIP] Support tray in menu manager 2021-01-12 15:55:28 +11:00
Lea Anthony 4e58b7697a Add context menu to menumanager. Slight refactor. 2021-01-11 14:19:01 +11:00
Lea Anthony 55d7d9693f Removed Menu GetByID / RemoveByID 2021-01-11 13:41:07 +11:00
Lea Anthony b4b7c9d306 Implemented MenuItem.Remove() to remove from the menu structures 2021-01-11 11:57:49 +11:00
Lea Anthony a4153fae57 Remove Menu.On() 2021-01-11 11:25:48 +11:00
Lea Anthony 8053357d99 Support Menu updates! 2021-01-11 11:21:28 +11:00
Lea Anthony 7347d2caa2 Add wails debug command 2021-01-11 11:20:25 +11:00
Lea Anthony e6491bcbb7 Fix bad deallocation of context menus 2021-01-11 07:03:59 +11:00
Lea Anthony 26a291dbee [WIP] Use json payload for click message 2021-01-11 06:40:00 +11:00
Lea Anthony 8ee8c9b07c [WIP] New menu processor 2021-01-10 21:31:13 +11:00
Lea Anthony 3a2d01813a Don't inline functions in debug builds 2021-01-10 20:32:25 +11:00
Lea Anthony d2dadc386f Add combo modifier to kitchen sink 2021-01-08 11:39:18 +11:00
Lea Anthony faa8f02b08 Bugfix for message dialog icons 2021-01-08 11:39:02 +11:00
Lea Anthony fbee9ba240 Support UpdateContextMenus. Submenus are now *menu.Menu. Tidy up++ 2021-01-08 06:28:51 +11:00
Lea Anthony 2a69786d7e Remove old code (woohoo!) 2021-01-07 21:36:39 +11:00
Lea Anthony f460bf91ef [WIP] Normalisation of context menus creation/callbacks.
TODO: UpdateContextMenu()
2021-01-07 21:34:04 +11:00
Lea Anthony bd74d45a91 Normalisation of callbacks for menus. App menu converted to new Menus. 2021-01-06 20:50:41 +11:00
Lea Anthony c65522f0b6 Huge refactor of menus. Start of normalisation of callbacks. 2021-01-06 17:36:59 +11:00
Lea Anthony 5f2c437136 Bugfix dealloc contextmenus. Create common.h. WIP: new menu handling 2021-01-06 12:53:11 +11:00
Lea Anthony 87e974e080 Refactor/Tidy up Ffenestri darwin 2021-01-06 11:56:01 +11:00
Lea Anthony f77729fc0b v2.0.0-alpha.6 2021-01-05 14:28:50 +11:00
Lea Anthony 2a8ce96830 Remove custom asset bundling. Use Go's embed instead! 2021-01-05 14:28:18 +11:00
Lea Anthony 9be539cfb8 Force rebuild each time to pick up .h changes Tidy up go.mod. Bump version. 2021-01-05 14:01:53 +11:00
Lea Anthony e44f2fe06d Moved version out of main. 2021-01-05 13:26:31 +11:00
Lea Anthony ad65d55abd Misc fixes for module build 2021-01-05 13:20:44 +11:00
60 changed files with 3215 additions and 2773 deletions
@@ -0,0 +1,123 @@
package debug
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/shell"
"io"
"os"
"runtime"
"strings"
"time"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
// AddSubcommand adds the `debug` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
outputType := "desktop"
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
command := app.NewSubCommand("debug", "Builds the application then runs delve on the binary")
// Setup target type flag
description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
quiet := false
command.BoolFlag("q", "Suppress output to console", &quiet)
// ldflags to pass to `go`
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
// Log to file
logFile := ""
command.StringFlag("l", "Log to file", &logFile)
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
// Validate output type
if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType)
}
if !quiet {
app.PrintBanner()
}
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
// Setup mode
mode := build.Debug
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Mode: mode,
Pack: false,
Platform: runtime.GOOS,
LDFlags: ldflags,
Compiler: compilerCommand,
KeepAssets: false,
}
outputFilename, err := doDebugBuild(buildOptions)
if err != nil {
return err
}
// Check delve exists
delveExists := shell.CommandExists("dlv")
if !delveExists {
return fmt.Errorf("cannot launch delve (Is it installed?)")
}
// Get cwd
cwd, err := os.Getwd()
if err != nil {
return err
}
// Launch delve
println("Launching Delve on port 2345...")
command := shell.CreateCommand(cwd, "dlv", "--listen=:2345", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", outputFilename)
return command.Run()
})
return nil
}
// doDebugBuild is our main build command
func doDebugBuild(buildOptions *build.Options) (string, error) {
// Start Time
start := time.Now()
outputFilename, err := build.Build(buildOptions)
if err != nil {
return "", err
}
// Output stats
elapsed := time.Since(start)
buildOptions.Logger.Println("")
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Println("")
return outputFilename, nil
}
+12 -3
View File
@@ -85,7 +85,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// If this is a folder, add it to our watch list
if fs.DirExists(event.Name) {
if !strings.Contains(event.Name, "node_modules") {
watcher.Add(event.Name)
err := watcher.Add(event.Name)
if err != nil {
logger.Fatal("%s", err.Error())
}
logger.Println("Watching directory: %s", event.Name)
}
}
@@ -173,7 +176,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Kill the current program if running
if debugBinaryProcess != nil {
debugBinaryProcess.Kill()
err := debugBinaryProcess.Kill()
if err != nil {
return err
}
}
logger.Println("Development mode exited")
@@ -231,7 +237,10 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
err = newProcess.Start()
if err != nil {
// Remove binary
fs.DeleteFile(appBinary)
deleteError := fs.DeleteFile(appBinary)
if deleteError != nil {
logger.Fatal("Unable to delete app binary: " + appBinary)
}
logger.Fatal("Unable to start application: %s", err.Error())
}
+6 -1
View File
@@ -5,6 +5,7 @@ import (
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
@@ -19,7 +20,6 @@ func fatal(message string) {
func main() {
var err error
version := "v2.0.0-alpha"
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
@@ -28,6 +28,11 @@ func main() {
if err != nil {
fatal(err.Error())
}
err = debug.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = doctor.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
+3
View File
@@ -0,0 +1,3 @@
package main
var version = "v2.0.0-alpha.6"
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/wailsapp/wails/v2
go 1.13
go 1.15
require (
github.com/davecgh/go-spew v1.1.1
+3
View File
@@ -8,6 +8,7 @@ package app
// will be unknown and the application will not work as expected.
import (
"github.com/wailsapp/wails/v2/internal/logger"
"os"
"github.com/wailsapp/wails/v2/pkg/options"
@@ -22,6 +23,8 @@ type App struct {
// Indicates if the app is running in debug mode
debug bool
logger *logger.Logger
}
// CreateApp returns a null application
+43 -56
View File
@@ -6,6 +6,7 @@ import (
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
@@ -23,15 +24,15 @@ type App struct {
options *options.App
// Subsystems
log *subsystem.Log
runtime *subsystem.Runtime
event *subsystem.Event
binding *subsystem.Binding
call *subsystem.Call
menu *subsystem.Menu
tray *subsystem.Tray
contextmenus *subsystem.ContextMenus
dispatcher *messagedispatcher.Dispatcher
log *subsystem.Log
runtime *subsystem.Runtime
event *subsystem.Event
binding *subsystem.Binding
call *subsystem.Call
menu *subsystem.Menu
dispatcher *messagedispatcher.Dispatcher
menuManager *menumanager.Manager
// Indicates if the app is in debug mode
debug bool
@@ -54,13 +55,32 @@ func CreateApp(appoptions *options.App) (*App, error) {
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger)
// Create the menu manager
menuManager := menumanager.NewManager()
// Process the application menu
menuManager.SetApplicationMenu(options.GetApplicationMenu(appoptions))
// Process context menus
contextMenus := options.GetContextMenus(appoptions)
for _, contextMenu := range contextMenus {
menuManager.AddContextMenu(contextMenu)
}
// Process tray menus
trayMenus := options.GetTrayMenus(appoptions)
for _, trayMenu := range trayMenus {
menuManager.AddTrayMenu(trayMenu)
}
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
result := &App{
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
menuManager: menuManager,
}
result.options = appoptions
@@ -92,12 +112,7 @@ func (a *App) Run() error {
return err
}
// Start the runtime
applicationMenu := options.GetApplicationMenu(a.options)
trayMenu := options.GetTray(a.options)
contextMenus := options.GetContextMenus(a.options)
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, applicationMenu, trayMenu, contextMenus)
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger)
if err != nil {
return err
}
@@ -155,43 +170,15 @@ func (a *App) Run() error {
return err
}
// Optionally start the menu subsystem
if applicationMenu != nil {
menusubsystem, err := subsystem.NewMenu(applicationMenu, a.servicebus, a.logger)
if err != nil {
return err
}
a.menu = menusubsystem
err = a.menu.Start()
if err != nil {
return err
}
// Start the menu subsystem
menusubsystem, err := subsystem.NewMenu(a.servicebus, a.logger, a.menuManager)
if err != nil {
return err
}
// Optionally start the tray subsystem
if trayMenu != nil {
traysubsystem, err := subsystem.NewTray(trayMenu, a.servicebus, a.logger)
if err != nil {
return err
}
a.tray = traysubsystem
err = a.tray.Start()
if err != nil {
return err
}
}
// Optionally start the context menu subsystem
if contextMenus != nil {
contextmenussubsystem, err := subsystem.NewContextMenus(contextMenus, a.servicebus, a.logger)
if err != nil {
return err
}
a.contextmenus = contextmenussubsystem
err = a.contextmenus.Start()
if err != nil {
return err
}
a.menu = menusubsystem
err = a.menu.Start()
if err != nil {
return err
}
// Start the call subsystem
+88
View File
@@ -0,0 +1,88 @@
//
// Created by Lea Anthony on 6/1/21.
//
#include "common.h"
// Credit: https://stackoverflow.com/a/8465083
char* concat(const char *string1, const char *string2)
{
const size_t len1 = strlen(string1);
const size_t len2 = strlen(string2);
char *result = malloc(len1 + len2 + 1);
strcpy(result, string1);
memcpy(result + len1, string2, len2 + 1);
return result;
}
// 10k is more than enough for a log message
#define MAXMESSAGE 1024*10
char abortbuffer[MAXMESSAGE];
void ABORT(const char *message, ...) {
const char *temp = concat("FATAL: ", message);
va_list args;
va_start(args, message);
vsnprintf(abortbuffer, MAXMESSAGE, temp, args);
printf("%s\n", &abortbuffer[0]);
MEMFREE(temp);
va_end(args);
exit(1);
}
int freeHashmapItem(void *const context, struct hashmap_element_s *const e) {
free(e->data);
return -1;
}
const char* getJSONString(JsonNode *item, const char* key) {
// Get key
JsonNode *node = json_find_member(item, key);
const char *result = "";
if ( node != NULL && node->tag == JSON_STRING) {
result = node->string_;
}
return result;
}
void ABORT_JSON(JsonNode *node, const char* key) {
ABORT("Unable to read required key '%s' from JSON: %s\n", key, json_encode(node));
}
const char* mustJSONString(JsonNode *node, const char* key) {
const char* result = getJSONString(node, key);
if ( result == NULL ) {
ABORT_JSON(node, key);
}
return result;
}
JsonNode* mustJSONObject(JsonNode *node, const char* key) {
struct JsonNode* result = getJSONObject(node, key);
if ( result == NULL ) {
ABORT_JSON(node, key);
}
return result;
}
JsonNode* getJSONObject(JsonNode* node, const char* key) {
return json_find_member(node, key);
}
bool getJSONBool(JsonNode *item, const char* key, bool *result) {
JsonNode *node = json_find_member(item, key);
if ( node != NULL && node->tag == JSON_BOOL) {
*result = node->bool_;
return true;
}
return false;
}
bool getJSONInt(JsonNode *item, const char* key, int *result) {
JsonNode *node = json_find_member(item, key);
if ( node != NULL && node->tag == JSON_NUMBER) {
*result = (int) node->number_;
return true;
}
return false;
}
+38
View File
@@ -0,0 +1,38 @@
//
// Created by Lea Anthony on 6/1/21.
//
#ifndef COMMON_H
#define COMMON_H
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
#include <objc/objc-runtime.h>
#include <CoreGraphics/CoreGraphics.h>
#include <stdio.h>
#include <stdarg.h>
#include "string.h"
#include "hashmap.h"
#include "vec.h"
#include "json.h"
#define STREQ(a,b) strcmp(a, b) == 0
#define STREMPTY(string) strlen(string) == 0
#define STRCOPY(a) concat(a, "")
#define STR_HAS_CHARS(input) input != NULL && strlen(input) > 0
#define MEMFREE(input) free((void*)input); input = NULL;
#define FREE_AND_SET(variable, value) if( variable != NULL ) { MEMFREE(variable); } variable = value
// Credit: https://stackoverflow.com/a/8465083
char* concat(const char *string1, const char *string2);
void ABORT(const char *message, ...);
int freeHashmapItem(void *const context, struct hashmap_element_s *const e);
const char* getJSONString(JsonNode *item, const char* key);
const char* mustJSONString(JsonNode *node, const char* key);
JsonNode* getJSONObject(JsonNode* node, const char* key);
JsonNode* mustJSONObject(JsonNode *node, const char* key);
bool getJSONBool(JsonNode *item, const char* key, bool *result);
bool getJSONInt(JsonNode *item, const char* key, int *result);
#endif //ASSETS_C_COMMON_H
@@ -0,0 +1,99 @@
////
//// Created by Lea Anthony on 6/1/21.
////
//
#include "ffenestri_darwin.h"
#include "common.h"
#include "contextmenus_darwin.h"
#include "menu_darwin.h"
ContextMenu* NewContextMenu(const char* contextMenuJSON) {
ContextMenu* result = malloc(sizeof(ContextMenu));
JsonNode* processedJSON = json_decode(contextMenuJSON);
if( processedJSON == NULL ) {
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
}
// Save reference to this json
result->processedJSON = processedJSON;
result->ID = mustJSONString(processedJSON, "ID");
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
result->menu = NewMenu(processedMenu);
result->nsmenu = NULL;
result->menu->menuType = ContextMenuType;
result->menu->parentData = result;
result->contextMenuData = NULL;
return result;
}
ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
}
void DeleteContextMenu(ContextMenu* contextMenu) {
// Free Menu
DeleteMenu(contextMenu->menu);
// Delete any context menu data we may have stored
if( contextMenu->contextMenuData != NULL ) {
MEMFREE(contextMenu->contextMenuData);
}
// Free JSON
if (contextMenu->processedJSON != NULL ) {
json_delete(contextMenu->processedJSON);
contextMenu->processedJSON = NULL;
}
// Free context menu
free(contextMenu);
}
int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
DeleteContextMenu(e->data);
return -1;
}
void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData) {
// If no context menu ID was given, abort
if( contextMenuID == NULL ) {
return;
}
ContextMenu* contextMenu = GetContextMenuByID(store, contextMenuID);
// We don't need the ID now
MEMFREE(contextMenuID);
if( contextMenu == NULL ) {
// Free context menu data
if( contextMenuData != NULL ) {
MEMFREE(contextMenuData);
return;
}
}
// We need to store the context menu data. Free existing data if we have it
// and set to the new value.
FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
// Grab the content view and show the menu
id contentView = msg(mainWindow, s("contentView"));
// Get the triggering event
id menuEvent = msg(mainWindow, s("currentEvent"));
if( contextMenu->nsmenu == NULL ) {
// GetMenu creates the NSMenu
contextMenu->nsmenu = GetMenu(contextMenu->menu);
}
// Show popup
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
}
@@ -0,0 +1,33 @@
////
//// Created by Lea Anthony on 6/1/21.
////
//
#ifndef CONTEXTMENU_DARWIN_H
#define CONTEXTMENU_DARWIN_H
#include "json.h"
#include "menu_darwin.h"
#include "contextmenustore_darwin.h"
typedef struct {
const char* ID;
id nsmenu;
Menu* menu;
JsonNode* processedJSON;
// Context menu data is given by the frontend when clicking a context menu.
// We send this to the backend when an item is selected
const char* contextMenuData;
} ContextMenu;
ContextMenu* NewContextMenu(const char* contextMenuJSON);
ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID);
void DeleteContextMenu(ContextMenu* contextMenu);
int freeContextMenu(void *const context, struct hashmap_element_s *const e);
void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData);
#endif //CONTEXTMENU_DARWIN_H
@@ -0,0 +1,65 @@
#include "contextmenus_darwin.h"
#include "contextmenustore_darwin.h"
ContextMenuStore* NewContextMenuStore() {
ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
// Allocate Context Menu Store
if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
}
return result;
}
void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
//TODO: check if there is already an entry for this menu
hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
}
ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
// Get the current menu
return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
}
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
ContextMenu* newContextMenu = NewContextMenu(menuJSON);
// Get the current menu
ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
if ( currentMenu == NULL ) {
ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
}
hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
// Save the status bar reference
DeleteContextMenu(currentMenu);
hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
}
void DeleteContextMenuStore(ContextMenuStore* store) {
// Guard against NULLs
if( store == NULL ) {
return;
}
// Delete context menus
if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
}
}
// Free context menu hashmap
hashmap_destroy(&store->contextMenuMap);
}
@@ -0,0 +1,27 @@
//
// Created by Lea Anthony on 7/1/21.
//
#ifndef CONTEXTMENUSTORE_DARWIN_H
#define CONTEXTMENUSTORE_DARWIN_H
#include "common.h"
typedef struct {
int dummy;
// This is our context menu store which keeps track
// of all instances of ContextMenus
struct hashmap_s contextMenuMap;
} ContextMenuStore;
ContextMenuStore* NewContextMenuStore();
void DeleteContextMenuStore(ContextMenuStore* store);
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON);
#endif //CONTEXTMENUSTORE_DARWIN_H
+14 -6
View File
@@ -1,6 +1,7 @@
package ffenestri
import (
"github.com/wailsapp/wails/v2/internal/menumanager"
"runtime"
"strings"
"unsafe"
@@ -31,7 +32,10 @@ type Application struct {
memory []unsafe.Pointer
// This is the main app pointer
app unsafe.Pointer
app *C.struct_Application
// Manages menus
menuManager *menumanager.Manager
// Logger
logger logger.CustomLogger
@@ -52,10 +56,11 @@ func init() {
}
// NewApplicationWithConfig creates a new application based on the given config
func NewApplicationWithConfig(config *options.App, logger *logger.Logger) *Application {
func NewApplicationWithConfig(config *options.App, logger *logger.Logger, menuManager *menumanager.Manager) *Application {
return &Application{
config: config,
logger: logger.CustomLogger("Ffenestri"),
config: config,
logger: logger.CustomLogger("Ffenestri"),
menuManager: menuManager,
}
}
@@ -116,7 +121,7 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel)
// Save app reference
a.app = unsafe.Pointer(app)
a.app = (*C.struct_Application)(app)
// Set Min Window Size
minWidth := C.int(a.config.MinWidth)
@@ -152,7 +157,10 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
dispatcher = incomingDispatcher.RegisterClient(newClient(a))
// Process platform settings
a.processPlatformSettings()
err := a.processPlatformSettings()
if err != nil {
return err
}
// Check we could initialise the application
if app != nil {
+36 -35
View File
@@ -2,41 +2,42 @@
#define __FFENESTRI_H__
#include <stdio.h>
struct Application;
extern void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel);
extern void SetMinWindowSize(void *app, int minWidth, int minHeight);
extern void SetMaxWindowSize(void *app, int maxWidth, int maxHeight);
extern void Run(void *app, int argc, char **argv);
extern void DestroyApplication(void *app);
extern void SetDebug(void *app, int flag);
extern void SetBindings(void *app, const char *bindings);
extern void ExecJS(void *app, const char *script);
extern void Hide(void *app);
extern void Show(void *app);
extern void Center(void *app);
extern void Maximise(void *app);
extern void Unmaximise(void *app);
extern void ToggleMaximise(void *app);
extern void Minimise(void *app);
extern void Unminimise(void *app);
extern void ToggleMinimise(void *app);
extern void SetColour(void *app, int red, int green, int blue, int alpha);
extern void SetSize(void *app, int width, int height);
extern void SetPosition(void *app, int x, int y);
extern void Quit(void *app);
extern void SetTitle(void *app, const char *title);
extern void Fullscreen(void *app);
extern void UnFullscreen(void *app);
extern void ToggleFullscreen(void *app);
extern void DisableFrame(void *app);
extern void OpenDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
extern void SaveDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
extern void MessageDialog(void *appPointer, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton);
extern void DarkModeEnabled(void *appPointer, char *callbackID);
extern void UpdateMenu(void *app, char *menuAsJSON);
extern void UpdateTray(void *app, char *menuAsJSON);
extern void UpdateContextMenus(void *app, char *contextMenusAsJSON);
extern void UpdateTrayLabel(void *app, const char *label);
extern void UpdateTrayIcon(void *app, const char *label);
extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel);
extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight);
extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight);
extern void Run(struct Application*, int argc, char **argv);
extern void DestroyApplication(struct Application*);
extern void SetDebug(struct Application*, int flag);
extern void SetBindings(struct Application*, const char *bindings);
extern void ExecJS(struct Application*, const char *script);
extern void Hide(struct Application*);
extern void Show(struct Application*);
extern void Center(struct Application*);
extern void Maximise(struct Application*);
extern void Unmaximise(struct Application*);
extern void ToggleMaximise(struct Application*);
extern void Minimise(struct Application*);
extern void Unminimise(struct Application*);
extern void ToggleMinimise(struct Application*);
extern void SetColour(struct Application*, int red, int green, int blue, int alpha);
extern void SetSize(struct Application*, int width, int height);
extern void SetPosition(struct Application*, int x, int y);
extern void Quit(struct Application*);
extern void SetTitle(struct Application*, const char *title);
extern void Fullscreen(struct Application*);
extern void UnFullscreen(struct Application*);
extern void ToggleFullscreen(struct Application*);
extern void DisableFrame(struct Application*);
extern void OpenDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
extern void SaveDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
extern void MessageDialog(struct Application*, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton);
extern void DarkModeEnabled(struct Application*, char *callbackID);
extern void SetApplicationMenu(struct Application*, const char *);
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
extern void UpdateTrayMenu(struct Application*, const char *menuTrayJSON);
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
#endif
+6 -51
View File
@@ -12,8 +12,6 @@ package ffenestri
import "C"
import (
"encoding/json"
"github.com/wailsapp/wails/v2/pkg/menu"
"strconv"
"github.com/wailsapp/wails/v2/internal/logger"
@@ -186,57 +184,14 @@ func (c *Client) DarkModeEnabled(callbackID string) {
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
}
func (c *Client) UpdateMenu(menu *menu.Menu) {
// Guard against nil menus
if menu == nil {
return
}
// Process the menu
processedMenu := NewProcessedMenu(menu)
menuJSON, err := json.Marshal(processedMenu)
if err != nil {
c.app.logger.Error("Error processing updated Menu: %s", err.Error())
return
}
C.UpdateMenu(c.app.app, c.app.string2CString(string(menuJSON)))
func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
}
func (c *Client) UpdateTray(menu *menu.Menu) {
// Guard against nil menus
if menu == nil {
return
}
// Process the menu
processedMenu := NewProcessedMenu(menu)
trayMenuJSON, err := json.Marshal(processedMenu)
if err != nil {
c.app.logger.Error("Error processing updated Tray: %s", err.Error())
return
}
C.UpdateTray(c.app.app, c.app.string2CString(string(trayMenuJSON)))
func (c *Client) UpdateTrayMenu(trayMenuJSON string) {
C.UpdateTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
}
func (c *Client) UpdateTrayLabel(label string) {
C.UpdateTrayLabel(c.app.app, c.app.string2CString(label))
}
func (c *Client) UpdateTrayIcon(name string) {
C.UpdateTrayIcon(c.app.app, c.app.string2CString(name))
}
func (c *Client) UpdateContextMenus(contextMenus *menu.ContextMenus) {
// Guard against nil contextMenus
if contextMenus == nil {
return
}
// Process the menu
contextMenusJSON, err := json.Marshal(contextMenus)
if err != nil {
c.app.logger.Error("Error processing updated Context Menus: %s", err.Error())
return
}
C.UpdateContextMenus(c.app.app, c.app.string2CString(string(contextMenusJSON)))
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
}
File diff suppressed because it is too large Load Diff
+18 -44
View File
@@ -4,15 +4,11 @@ package ffenestri
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -lobjc
#include "ffenestri.h"
#include "ffenestri_darwin.h"
*/
import "C"
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/options"
)
func (a *Application) processPlatformSettings() error {
@@ -63,54 +59,32 @@ func (a *Application) processPlatformSettings() error {
}
// Process menu
applicationMenu := options.GetApplicationMenu(a.config)
if applicationMenu != nil {
/*
As radio groups need to be manually managed on OSX,
we preprocess the menu to determine the radio groups.
This is defined as any adjacent menu item of type "RadioType".
We keep a record of every radio group member we discover by saving
a list of all members of the group and the number of members
in the group (this last one is for optimisation at the C layer).
*/
processedMenu := NewProcessedMenu(applicationMenu)
applicationMenuJSON, err := json.Marshal(processedMenu)
if err != nil {
return err
}
C.SetMenu(a.app, a.string2CString(string(applicationMenuJSON)))
//applicationMenu := options.GetApplicationMenu(a.config)
applicationMenu := a.menuManager.GetApplicationMenuJSON()
if applicationMenu != "" {
C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
}
// Process tray
tray := options.GetTray(a.config)
if tray != nil {
/*
As radio groups need to be manually managed on OSX,
we preprocess the menu to determine the radio groups.
This is defined as any adjacent menu item of type "RadioType".
We keep a record of every radio group member we discover by saving
a list of all members of the group and the number of members
in the group (this last one is for optimisation at the C layer).
*/
processedMenu := NewProcessedMenu(tray.Menu)
trayMenuJSON, err := json.Marshal(processedMenu)
if err != nil {
return err
trays, err := a.menuManager.GetTrayMenus()
if err != nil {
return err
}
if trays != nil {
for _, tray := range trays {
C.AddTrayMenu(a.app, a.string2CString(tray))
}
C.SetTray(a.app, a.string2CString(string(trayMenuJSON)), a.string2CString(tray.Label), a.string2CString(tray.Icon))
}
// Process context menus
contextMenus := options.GetContextMenus(a.config)
contextMenus, err := a.menuManager.GetContextMenus()
if err != nil {
return err
}
if contextMenus != nil {
contextMenusJSON, err := json.Marshal(contextMenus)
fmt.Printf("\n\nCONTEXT MENUS:\n %+v\n\n", string(contextMenusJSON))
if err != nil {
return err
for _, contextMenu := range contextMenus {
C.AddContextMenu(a.app, a.string2CString(contextMenu))
}
C.SetContextMenus(a.app, a.string2CString(string(contextMenusJSON)))
}
return nil
+107 -13
View File
@@ -2,18 +2,112 @@
#ifndef FFENESTRI_DARWIN_H
#define FFENESTRI_DARWIN_H
extern void TitlebarAppearsTransparent(void *);
extern void HideTitle(void *);
extern void HideTitleBar(void *);
extern void FullSizeContent(void *);
extern void UseToolbar(void *);
extern void HideToolbarSeparator(void *);
extern void DisableFrame(void *);
extern void SetAppearance(void *, const char *);
extern void WebviewIsTransparent(void *);
extern void WindowBackgroundIsTranslucent(void *);
extern void SetMenu(void *, const char *);
extern void SetTray(void *, const char *, const char *, const char *);
extern void SetContextMenus(void *, const char *);
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
#include <objc/objc-runtime.h>
#include <CoreGraphics/CoreGraphics.h>
#include "json.h"
#include "hashmap.h"
#include "stdlib.h"
// Macros to make it slightly more sane
#define msg objc_msgSend
#define c(str) (id)objc_getClass(str)
#define s(str) sel_registerName(str)
#define u(str) sel_getUid(str)
#define str(input) msg(c("NSString"), s("stringWithUTF8String:"), input)
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
#define cstr(input) (const char *)msg(input, s("UTF8String"))
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
#define ALLOC(classname) msg(c(classname), s("alloc"))
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))msg)(receiver, s("backingScaleFactor"))
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
#define NSBackingStoreBuffered 2
#define NSWindowStyleMaskBorderless 0
#define NSWindowStyleMaskTitled 1
#define NSWindowStyleMaskClosable 2
#define NSWindowStyleMaskMiniaturizable 4
#define NSWindowStyleMaskResizable 8
#define NSWindowStyleMaskFullscreen 1 << 14
#define NSVisualEffectMaterialWindowBackground 12
#define NSVisualEffectBlendingModeBehindWindow 0
#define NSVisualEffectStateFollowsWindowActiveState 0
#define NSVisualEffectStateActive 1
#define NSVisualEffectStateInactive 2
#define NSViewWidthSizable 2
#define NSViewHeightSizable 16
#define NSWindowBelow -1
#define NSWindowAbove 1
#define NSSquareStatusItemLength -2.0
#define NSVariableStatusItemLength -1.0
#define NSWindowTitleHidden 1
#define NSWindowStyleMaskFullSizeContentView 1 << 15
#define NSEventModifierFlagCommand 1 << 20
#define NSEventModifierFlagOption 1 << 19
#define NSEventModifierFlagControl 1 << 18
#define NSEventModifierFlagShift 1 << 17
#define NSControlStateValueMixed -1
#define NSControlStateValueOff 0
#define NSControlStateValueOn 1
// Unbelievably, if the user swaps their button preference
// then right buttons are reported as left buttons
#define NSEventMaskLeftMouseDown 1 << 1
#define NSEventMaskLeftMouseUp 1 << 2
#define NSEventMaskRightMouseDown 1 << 3
#define NSEventMaskRightMouseUp 1 << 4
#define NSEventTypeLeftMouseDown 1
#define NSEventTypeLeftMouseUp 2
#define NSEventTypeRightMouseDown 3
#define NSEventTypeRightMouseUp 4
#define NSNoImage 0
#define NSImageOnly 1
#define NSImageLeft 2
#define NSImageRight 3
#define NSImageBelow 4
#define NSImageAbove 5
#define NSImageOverlaps 6
#define NSAlertStyleWarning 0
#define NSAlertStyleInformational 1
#define NSAlertStyleCritical 2
#define NSAlertFirstButtonReturn 1000
#define NSAlertSecondButtonReturn 1001
#define NSAlertThirdButtonReturn 1002
struct Application;
int releaseNSObject(void *const context, struct hashmap_element_s *const e);
void TitlebarAppearsTransparent(struct Application* app);
void HideTitle(struct Application* app);
void HideTitleBar(struct Application* app);
void FullSizeContent(struct Application* app);
void UseToolbar(struct Application* app);
void HideToolbarSeparator(struct Application* app);
void DisableFrame(struct Application* app);
void SetAppearance(struct Application* app, const char *);
void WebviewIsTransparent(struct Application* app);
void WindowBackgroundIsTranslucent(struct Application* app);
void SetTray(struct Application* app, const char *, const char *, const char *);
void SetContextMenus(struct Application* app, const char *);
void AddTrayMenu(struct Application* app, const char *);
#endif
-80
View File
@@ -1,80 +0,0 @@
package ffenestri
import "github.com/wailsapp/wails/v2/pkg/menu"
// ProcessedMenu is the original menu with the addition
// of radio groups extracted from the menu data
type ProcessedMenu struct {
Menu *menu.Menu
RadioGroups []*RadioGroup
currentRadioGroup []string
}
// RadioGroup holds all the members of the same radio group
type RadioGroup struct {
Members []string
Length int
}
// NewProcessedMenu processed the given menu and returns
// the original menu with the extracted radio groups
func NewProcessedMenu(menu *menu.Menu) *ProcessedMenu {
result := &ProcessedMenu{
Menu: menu,
RadioGroups: []*RadioGroup{},
currentRadioGroup: []string{},
}
result.processMenu()
return result
}
func (p *ProcessedMenu) processMenu() {
// Loop over top level menus
for _, item := range p.Menu.Items {
// Process MenuItem
p.processMenuItem(item)
}
p.finaliseRadioGroup()
}
func (p *ProcessedMenu) processMenuItem(item *menu.MenuItem) {
switch item.Type {
// We need to recurse submenus
case menu.SubmenuType:
// Finalise any current radio groups as they don't trickle down to submenus
p.finaliseRadioGroup()
// Process each submenu item
for _, subitem := range item.SubMenu {
p.processMenuItem(subitem)
}
case menu.RadioType:
// Add the item to the radio group
p.currentRadioGroup = append(p.currentRadioGroup, item.ID)
default:
p.finaliseRadioGroup()
}
}
func (p *ProcessedMenu) finaliseRadioGroup() {
// If we were processing a radio group, fix up the references
if len(p.currentRadioGroup) > 0 {
// Create new radiogroup
group := &RadioGroup{
Members: p.currentRadioGroup,
Length: len(p.currentRadioGroup),
}
p.RadioGroups = append(p.RadioGroups, group)
// Empty the radio group
p.currentRadioGroup = []string{}
}
}

Some files were not shown because too many files have changed in this diff Show More