Compare commits

..

3 Commits

Author SHA1 Message Date
Lea Anthony
02d6fe609c Remove windows message 2020-10-28 21:16:33 +11:00
Lea Anthony
93e7432ba9 Merge branch 'develop' into firebug 2020-10-28 21:14:39 +11:00
Lea Anthony
cb933cc987 Initial support for firebug 2020-10-28 21:11:13 +11:00
485 changed files with 65 additions and 49599 deletions

9
.gitignore vendored
View File

@@ -17,13 +17,8 @@ cmd/wails/wails
.DS_Store
tmp
node_modules/
package.json.md5
v2/test/**/frontend/dist
v2/test/**/build/
v2/test/frameless/icon.png
v2/test/hidden/icon.png
v2/test/kitchensink/frontend/public/bundle.*
v2/pkg/parser/testproject/frontend/wails
v2/test/kitchensink/frontend/public
v2/internal/ffenestri/runtime.c
v2/internal/runtime/assets/desktop.js
v2/test/kitchensink/build/darwin/desktop/kitchensink
v2/test/kitchensink/frontend/package.json.md5

View File

@@ -1,8 +1,4 @@
{
"go.formatTool": "goimports",
"eslint.alwaysShowStatus": true,
"files.associations": {
"__locale": "c",
"ios": "c"
}
"eslint.alwaysShowStatus": true
}

View File

@@ -39,5 +39,3 @@ Wails is what it is because of the time and effort given by these great people.
* [Kyle](https://github.com/kmuchmore)
* [Balakrishna Prasad Ganne](https://github.com/aayush420)
* [Charaf Rezrazi](https://github.com/Rezrazi)
* [misitebao](https://github.com/misitebao)
* [Elie Grenon](https://github.com/DrunkenPoney)

View File

@@ -147,12 +147,7 @@ This project was mainly coded to the following albums:
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large)
## Special Thanks
<p align="center" style="text-align: center">
A *huge* thanks to <a href="https://pace.dev"><img src="pace.jpeg"/> Pace</a> for sponsoring the project and helping the efforts to get Wails ported to Apple Silicon!<br/><br/>
If you are looking for a Project Management tool that's powerful but quick and easy to use, check them out!<br/><br/>
</p>
## Special Thank You
<p align="center" style="text-align: center">
A special thank you to JetBrains for donating licenses to us!<br/><br/>

File diff suppressed because one or more lines are too long

View File

@@ -65,8 +65,6 @@ const (
Solus
// Ctlos Linux distribution
Ctlos
// EndeavourOS linux distribution
EndeavourOS
)
// DistroInfo contains all the information relating to a linux distribution
@@ -134,7 +132,7 @@ func parseOsRelease(osRelease string) *DistroInfo {
case "archlabs":
result.Distribution = ArchLabs
case "ctlos":
result.Distribution = Ctlos
result.Distribution = Ctlos
case "debian":
result.Distribution = Debian
case "ubuntu":
@@ -173,8 +171,6 @@ func parseOsRelease(osRelease string) *DistroInfo {
result.Distribution = PopOS
case "solus":
result.Distribution = Solus
case "endeavouros":
result.Distribution = EndeavourOS
default:
result.Distribution = Unknown
}

View File

@@ -202,16 +202,7 @@ distributions:
name: Ctlos Linux
gccversioncommand: *gccdumpversion
programs: *archdefaultprograms
libraries: *archdefaultlibraries
endeavouros:
id: endeavouros
releases:
default:
version: default
name: EndeavourOS
gccversioncommand: *gccdumpversion
programs: *archdefaultprograms
libraries: *archdefaultlibraries
libraries: *archdefaultlibraries
manjaro:
id: manjaro
releases:

View File

@@ -24,19 +24,11 @@ func NewSemanticVersion(version string) (*SemanticVersion, error) {
// IsRelease returns true if it's a release version
func (s *SemanticVersion) IsRelease() bool {
// Limit to v1
if s.Version.Major() != 1 {
return false
}
return len(s.Version.Prerelease()) == 0 && len(s.Version.Metadata()) == 0
}
// IsPreRelease returns true if it's a prerelease version
func (s *SemanticVersion) IsPreRelease() bool {
// Limit to v1
if s.Version.Major() != 1 {
return false
}
return len(s.Version.Prerelease()) > 0
}

View File

@@ -1,65 +0,0 @@
package cmd
import (
"testing"
)
func TestSemanticVersion_IsPreRelease(t *testing.T) {
tests := []struct {
name string
version string
want bool
}{
{"v1.6.7-pre0", "v1.6.7-pre0", true},
{"v2.6.7+pre0", "v2.6.7+pre0", false},
{"v2.6.7", "v2.6.7", false},
{"v2.0.0+alpha.1", "v2.0.0+alpha.1", false},
{"v2.0.0-alpha.1", "v2.0.0-alpha.1", false},
{"v1.6.7", "v1.6.7", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
semanticversion, err := NewSemanticVersion(tt.version)
if err != nil {
t.Errorf("Invalid semantic version: %s", semanticversion)
return
}
s := &SemanticVersion{
Version: semanticversion.Version,
}
if got := s.IsPreRelease(); got != tt.want {
t.Errorf("IsPreRelease() = %v, want %v", got, tt.want)
}
})
}
}
func TestSemanticVersion_IsRelease(t *testing.T) {
tests := []struct {
name string
version string
want bool
}{
{"v1.6.7", "v1.6.7", true},
{"v2.6.7-pre0", "v2.6.7-pre0", false},
{"v2.6.7", "v2.6.7", false},
{"v2.6.7+release", "v2.6.7+release", false},
{"v2.0.0-alpha.1", "v2.0.0-alpha.1", false},
{"v1.6.7-pre0", "v1.6.7-pre0", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
semanticversion, err := NewSemanticVersion(tt.version)
if err != nil {
t.Errorf("Invalid semantic version: %s", semanticversion)
return
}
s := &SemanticVersion{
Version: semanticversion.Version,
}
if got := s.IsRelease(); got != tt.want {
t.Errorf("IsRelease() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -276,7 +276,7 @@ func CheckDependencies(logger *Logger) (bool, error) {
switch distroInfo.Distribution {
case Ubuntu, Debian, Zorin, Parrot, Linuxmint, Elementary, Kali, Neon, Deepin, Raspbian, PopOS:
libraryChecker = DpkgInstalled
case Arch, ArcoLinux, ArchLabs, Ctlos, Manjaro, ManjaroARM, EndeavourOS:
case Arch, ArcoLinux, ArchLabs, Ctlos, Manjaro, ManjaroARM:
libraryChecker = PacmanInstalled
case CentOS, Fedora, Tumbleweed, Leap:
libraryChecker = RpmInstalled

View File

@@ -19,16 +19,16 @@
"@types/mocha": "^8.0.3",
"@typescript-eslint/eslint-plugin": "^4.3.0",
"@typescript-eslint/parser": "^4.3.0",
"@vue/cli-plugin-eslint": "~4.5.9",
"@vue/cli-plugin-router": "~4.5.9",
"@vue/cli-plugin-typescript": "~4.5.9",
"@vue/cli-plugin-unit-mocha": "~4.5.9",
"@vue/cli-service": "~4.5.9",
"@vue/cli-plugin-eslint": "~4.5.6",
"@vue/cli-plugin-router": "~4.5.6",
"@vue/cli-plugin-typescript": "~4.5.6",
"@vue/cli-plugin-unit-mocha": "~4.5.6",
"@vue/cli-service": "~4.5.6",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/eslint-config-typescript": "^5.1.0",
"@vue/test-utils": "^2.0.0-0",
"chai": "^4.2.0",
"eslint": "<7.0.0",
"eslint": "^7.10.0",
"eslint-plugin-vue": "^7.0.0",
"node-sass": "^4.14.1",
"sass-loader": "^10.0.2",

View File

@@ -1,4 +1,4 @@
package cmd
// Version - Wails version
const Version = "v1.11.0"
const Version = "v1.8.1-pre6"

View File

@@ -83,9 +83,6 @@ func init() {
return fmt.Errorf("Unable to find 'project.json'. Please check you are in a Wails project directory")
}
// Set firebug flag
projectOptions.UseFirebug = usefirebug
// Check that this platform is supported
if !projectOptions.PlatformSupported() {
logger.Yellow("WARNING: This project is unsupported on %s - it probably won't work!\n Valid platforms: %s\n", runtime.GOOS, strings.Join(projectOptions.Platforms, ", "))

View File

@@ -1,37 +1,20 @@
package wails
import (
"net/url"
"strings"
"github.com/leaanthony/mewn"
"github.com/wailsapp/wails/runtime"
)
// AppConfig is the configuration structure used when creating a Wails App object
type AppConfig struct {
// The width and height of your application in pixels
Width, Height int
// The title to put in the title bar
Title string
// The HTML your app should use. If you leave it blank, a default will be used:
// <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="IE=edge" content="IE=edge"></head><body><div id="app"></div><script type="text/javascript"></script></body></html>
HTML string
// The Javascript your app should use. Normally this should be generated by a bundler.
JS string
// The CSS your app should use. Normally this should be generated by a bundler.
CSS string
// The colour of your window. Can take "#fff", "rgb(255,255,255)", "rgba(255,255,255,1)" formats
Colour string
// Indicates whether your app should be resizable
Resizable bool
// Indicated if the devtools should be disabled
Width, Height int
Title string
defaultHTML string
HTML string
JS string
CSS string
Colour string
Resizable bool
DisableInspector bool
}
@@ -50,14 +33,9 @@ func (a *AppConfig) GetTitle() string {
return a.Title
}
// GetHTML returns the default HTML
func (a *AppConfig) GetHTML() string {
if len(a.HTML) > 0 {
a.HTML = url.QueryEscape(a.HTML)
a.HTML = "data:text/html," + strings.ReplaceAll(a.HTML, "+", "%20")
a.HTML = strings.ReplaceAll(a.HTML, "%3D", "=")
}
return a.HTML
// GetDefaultHTML returns the default HTML
func (a *AppConfig) GetDefaultHTML() string {
return a.defaultHTML
}
// GetResizable returns true if the window should be resizable
@@ -97,18 +75,10 @@ func (a *AppConfig) merge(in *AppConfig) error {
a.Colour = in.Colour
}
if in.HTML != "" {
a.HTML = in.HTML
}
if in.JS != "" {
a.JS = in.JS
}
if in.HTML != "" {
a.HTML = in.HTML
}
if in.Width != 0 {
a.Width = in.Width
}
@@ -129,7 +99,7 @@ func newConfig(userConfig *AppConfig) (*AppConfig, error) {
Resizable: true,
Title: "My Wails App",
Colour: "#FFF", // White by default
HTML: defaultHTML,
HTML: mewn.String("./runtime/assets/default.html"),
}
if userConfig != nil {
@@ -141,17 +111,3 @@ func newConfig(userConfig *AppConfig) (*AppConfig, error) {
return result, nil
}
var defaultHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
</head>
<body>
<div id="app"></div>
</body>
</html>`

2
go.mod
View File

@@ -22,7 +22,7 @@ require (
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba
golang.org/x/image v0.0.0-20200430140353-33d19683fad8
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
golang.org/x/text v0.3.0
gopkg.in/AlecAivazis/survey.v1 v1.8.4
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22

2
go.sum
View File

@@ -81,8 +81,6 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/AlecAivazis/survey.v1 v1.8.4 h1:10xXXN3wgIhPheb5NI58zFgZv32Ana7P3Tl4shW+0Qc=

View File

@@ -6,9 +6,9 @@ type AppConfig interface {
GetHeight() int
GetTitle() string
GetResizable() bool
GetHTML() string
GetDefaultHTML() string
GetDisableInspector() bool
GetColour() string
GetCSS() string
GetJS() string
}
}

File diff suppressed because one or more lines are too long

View File

@@ -58,7 +58,7 @@ func (w *WebView) Initialise(config interfaces.AppConfig, ipc interfaces.IPCMana
Height: config.GetHeight(),
Title: config.GetTitle(),
Resizable: config.GetResizable(),
URL: config.GetHTML(),
URL: config.GetDefaultHTML(),
Debug: !config.GetDisableInspector(),
ExternalInvokeCallback: func(_ wv.WebView, message string) {
w.ipc.Dispatch(message, w.callback)

View File

@@ -364,7 +364,6 @@ struct webview_priv
webkit_web_view_get_settings(WEBKIT_WEB_VIEW(w->priv.webview));
webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
webkit_settings_set_enable_developer_extras(settings, true);
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS);
}
else
{

BIN
pace.jpeg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
</head>
<body>
<div id="app"></div>
<script type="text/javascript">function AddScript(js, callbackID) {
var script = document.createElement('script');
script.text = js;
document.body.appendChild(script);
}</script>
</body>
</html>

View File

@@ -3,12 +3,12 @@
"browser": true,
"es6": true,
"amd": true,
"node": true
"node": true,
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2016,
"sourceType": "module"
"sourceType": "module",
},
"rules": {
"indent": [

View File

@@ -26,7 +26,7 @@ export function OpenURL(url) {
* Opens the given filename using the system's default file handler
*
* @export
* @param {string} filename
* @param {sting} filename
* @returns
*/
export function OpenFile(filename) {

View File

@@ -62,7 +62,7 @@ if (window.crypto) {
export function Call(bindingName, data, timeout) {
// Timeout infinite by default
if (timeout == null) {
if (timeout == null || timeout == undefined) {
timeout = 0;
}

View File

@@ -45,7 +45,7 @@ function Invoke(message) {
*
* @export
* @param {string} type
* @param {Object} payload
* @param {string} payload
* @param {string=} callbackID
*/
export function SendMessage(type, payload, callbackID) {

View File

@@ -3959,9 +3959,9 @@
"dev": true
},
"ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
},
"interpret": {

View File

@@ -1,6 +1,6 @@
{
"name": "@wailsapp/runtime",
"version": "1.1.1",
"version": "1.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -130,9 +130,9 @@
"dev": true
},
"ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
},
"invert-kv": {

View File

@@ -1,13 +0,0 @@
{
"files.associations": {
"ios": "c",
"typeinfo": "c",
"sstream": "c",
"__functional_03": "c",
"functional": "c",
"__locale": "c",
"locale": "c",
"chrono": "c",
"system_error": "c"
}
}

View File

@@ -1,40 +0,0 @@
# Packing linux
* create app, app.desktop, app.png (512x512)
* chmod +x app!
* ./linuxdeploy-x86_64.AppImage --appdir AppDir -i react.png -d react.desktop -e react --output appimage
# Wails Doctor
Tested on:
* Debian 8
* Ubuntu 20.04
* Ubuntu 19.10
* Solus 4.1
* Centos 8
* Gentoo
* OpenSUSE/leap
* Fedora 31
### Development
Add a new package manager processor here: `v2/internal/system/packagemanager/`. IsAvailable should work even if the package is installed.
Add your new package manager to the list of package managers in `v2/internal/system/packagemanager/packagemanager.go`:
```
var db = map[string]PackageManager{
"eopkg": NewEopkg(),
"apt": NewApt(),
"yum": NewYum(),
"pacman": NewPacman(),
"emerge": NewEmerge(),
"zypper": NewZypper(),
}
```
## Gentoo
* Setup docker image using: emerge-webrsync -x -v

View File

@@ -1,5 +0,0 @@
# Wails v2 ALPHA
This branch contains WORK IN PROGRESS! There are no guarantees. Use at your peril!
This document will be updated as progress is made.

View File

@@ -1,119 +0,0 @@
package build
import (
"fmt"
"io"
"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"
)
// AddBuildSubcommand adds the `build` command for the Wails application
func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
outputType := "desktop"
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
command := app.NewSubCommand("build", "Builds the application")
// Setup target type flag
description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
// Setup production flag
production := false
command.BoolFlag("production", "Build in production mode", &production)
// Setup pack flag
pack := false
command.BoolFlag("package", "Create a platform specific package", &pack)
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
// Setup Platform flag
platform := runtime.GOOS
command.StringFlag("platform", "Platform to target", &platform)
// Quiet Build
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)
// Retain assets
keepAssets := false
command.BoolFlag("k", "Keep generated assets", &keepAssets)
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
if production {
mode = build.Production
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Mode: mode,
Pack: pack,
Platform: platform,
LDFlags: ldflags,
Compiler: compilerCommand,
KeepAssets: keepAssets,
}
return doBuild(buildOptions)
})
}
// doBuild is our main build command
func doBuild(buildOptions *build.Options) 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 nil
}

View File

@@ -1,123 +0,0 @@
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
}

View File

@@ -1,259 +0,0 @@
package dev
import (
"fmt"
"io"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"time"
"github.com/pkg/errors"
"github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("dev", "Development mode")
// Passthrough ldflags
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
// compiler command
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
// extensions to trigger rebuilds
extensions := "go"
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
app.PrintBanner()
// TODO: Check you are in a project directory
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()
var debugBinaryProcess *process.Process = nil
var buildFrontend bool = false
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
// Setup signal handler
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
debounceQuit := make(chan bool, 1)
// Do initial build
logger.Println("Building application for development...")
debugBinaryProcess, err = restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess)
if err != nil {
return err
}
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Println("event: %+v", event)
// Check for new directories
if event.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list
if fs.DirExists(event.Name) {
if !strings.Contains(event.Name, "node_modules") {
err := watcher.Add(event.Name)
if err != nil {
logger.Fatal("%s", err.Error())
}
logger.Println("Watching directory: %s", event.Name)
}
}
return
}
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
logger.Println("modified file: %s", event.Name)
var rebuild bool = false
// Iterate all file patterns
for _, pattern := range extensionsThatTriggerARebuild {
rebuild = strings.HasSuffix(event.Name, pattern)
if err != nil {
logger.Fatal(err.Error())
}
if rebuild {
// Only build frontend when the file isn't a Go file
buildFrontend = !strings.HasSuffix(event.Name, "go")
break
}
}
if !rebuild {
logger.Println("Filename change: %s did not match extension list %s", event.Name, extensions)
return
}
logger.Println("Partial build triggered: %s updated", event.Name)
// Do a rebuild
// Try and build the app
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess)
if err != nil {
fmt.Printf("Error during build: %s", err.Error())
return
}
// If we have a new process, save it
if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess
}
}
})
// Get project dir
dir, err := os.Getwd()
if err != nil {
return err
}
// Get all subdirectories
dirs, err := fs.GetSubdirectories(dir)
if err != nil {
return err
}
// Setup a watcher for non-node_modules directories
dirs.Each(func(dir string) {
if strings.Contains(dir, "node_modules") {
return
}
logger.Println("Watching directory: %s", dir)
err = watcher.Add(dir)
if err != nil {
logger.Fatal(err.Error())
}
})
// Wait until we get a quit signal
quit := false
for quit == false {
select {
case <-quitChannel:
println("Caught quit")
// Notify debouncer to quit
debounceQuit <- true
quit = true
}
}
// Kill the current program if running
if debugBinaryProcess != nil {
err := debugBinaryProcess.Kill()
if err != nil {
return err
}
}
logger.Println("Development mode exited")
return nil
})
return nil
}
// Credit: https://drailing.net/2018/01/debounce-function-for-golang/
func debounce(interval time.Duration, input chan fsnotify.Event, quitChannel chan bool, cb func(arg fsnotify.Event)) {
var item fsnotify.Event
timer := time.NewTimer(interval)
exit:
for {
select {
case item = <-input:
timer.Reset(interval)
case <-timer.C:
if item.Name != "" {
cb(item)
}
case <-quitChannel:
break exit
}
}
}
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process) (*process.Process, error) {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
println()
if err != nil {
logger.Fatal(err.Error())
return nil, errors.Wrap(err, "Build Failed:")
}
logger.Println("Build new binary: %s", appBinary)
// Kill existing binary if need be
if debugBinaryProcess != nil {
killError := debugBinaryProcess.Kill()
if killError != nil {
logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
}
debugBinaryProcess = nil
}
// TODO: Generate `backend.js`
// Start up new binary
newProcess := process.NewProcess(logger, appBinary)
err = newProcess.Start()
if err != nil {
// Remove binary
deleteError := fs.DeleteFile(appBinary)
if deleteError != nil {
logger.Fatal("Unable to delete app binary: " + appBinary)
}
logger.Fatal("Unable to start application: %s", err.Error())
}
return newProcess, nil
}
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
// Create random output file
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Mode: build.Debug,
Pack: false,
Platform: runtime.GOOS,
LDFlags: ldflags,
Compiler: compilerCommand,
OutputFile: outputFile,
IgnoreFrontend: true,
}
return build.Build(buildOptions)
}

View File

@@ -1,154 +0,0 @@
package doctor
import (
"fmt"
"io"
"log"
"os"
"runtime"
"strings"
"text/tabwriter"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// AddSubcommand adds the `doctor` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("doctor", "Diagnose your environment")
command.Action(func() error {
logger := clilogger.New(w)
app.PrintBanner()
logger.Print("Scanning system - please wait...")
// Get system info
info, err := system.GetInfo()
if err != nil {
return err
}
logger.Println("Done.")
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
// Write out the system information
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "System\n")
fmt.Fprintf(w, "------\n")
fmt.Fprintf(w, "%s\t%s\n", "OS:", info.OS.Name)
fmt.Fprintf(w, "%s\t%s\n", "Version: ", info.OS.Version)
fmt.Fprintf(w, "%s\t%s\n", "ID:", info.OS.ID)
// Exit early if PM not found
if info.PM == nil {
fmt.Fprintf(w, "\n%s\t%s", "Package Manager:", "Not Found")
w.Flush()
println()
return nil
}
fmt.Fprintf(w, "%s\t%s\n", "Package Manager: ", info.PM.Name())
// Output Go Information
fmt.Fprintf(w, "%s\t%s\n", "Go Version:", runtime.Version())
fmt.Fprintf(w, "%s\t%s\n", "Platform:", runtime.GOOS)
fmt.Fprintf(w, "%s\t%s\n", "Architecture:", runtime.GOARCH)
// Output Dependencies Status
var dependenciesMissing = []string{}
var externalPackages = []*packagemanager.Dependancy{}
var dependenciesAvailableRequired = 0
var dependenciesAvailableOptional = 0
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Dependency\tPackage Name\tStatus\tVersion\n")
fmt.Fprintf(w, "----------\t------------\t------\t-------\n")
// Loop over dependencies
for _, dependency := range info.Dependencies {
name := dependency.Name
if dependency.Optional {
name += "*"
}
packageName := "Unknown"
status := "Not Found"
// If we found the package
if dependency.PackageName != "" {
packageName = dependency.PackageName
// If it's installed, update the status
if dependency.Installed {
status = "Installed"
} else {
// Generate meaningful status text
status = "Available"
if dependency.Optional {
dependenciesAvailableOptional++
} else {
dependenciesAvailableRequired++
}
}
} else {
if !dependency.Optional {
dependenciesMissing = append(dependenciesMissing, dependency.Name)
}
if dependency.External {
externalPackages = append(externalPackages, dependency)
}
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, packageName, status, dependency.Version)
}
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "* - Optional Dependency\n")
w.Flush()
logger.Println("")
logger.Println("Diagnosis")
logger.Println("---------\n")
// Generate an appropriate diagnosis
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
logger.Println("Your system is ready for Wails development!")
}
if dependenciesAvailableRequired != 0 {
log.Println("Install required packages using: " + info.Dependencies.InstallAllRequiredCommand())
}
if dependenciesAvailableOptional != 0 {
log.Println("Install optional packages using: " + info.Dependencies.InstallAllOptionalCommand())
}
if len(externalPackages) > 0 {
for _, p := range externalPackages {
if p.Optional {
print("[Optional] ")
}
log.Println("Install " + p.Name + ": " + p.InstallCommand)
}
}
if len(dependenciesMissing) != 0 {
// TODO: Check if apps are available locally and if so, adjust the diagnosis
log.Println("Fatal:")
log.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
log.Println("Please read this article on how to resolve this: https://wails.app/guides/resolving-missing-packages")
}
log.Println("")
return nil
})
return nil
}

View File

@@ -1,91 +0,0 @@
package generate
import (
"io"
"time"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/parser"
)
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("generate", "Code Generation Tools")
// Backend API
backendAPI := command.NewSubCommand("module", "Generates a JS module for the frontend to interface with the backend")
// Quiet Init
quiet := false
backendAPI.BoolFlag("q", "Supress output to console", &quiet)
backendAPI.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
app.PrintBanner()
logger.Print("Generating Javascript module for Go code...")
// Start Time
start := time.Now()
p, err := parser.GenerateWailsFrontendPackage()
if err != nil {
return err
}
logger.Println("done.")
logger.Println("")
elapsed := time.Since(start)
packages := p.Packages
// Print report
for _, pkg := range p.Packages {
if pkg.ShouldGenerate() {
logPackage(pkg, logger)
}
}
logger.Println("%d packages parsed in %s.", len(packages), elapsed)
return nil
})
return nil
}
func logPackage(pkg *parser.Package, logger *clilogger.CLILogger) {
logger.Println("Processed Go package '" + pkg.Gopackage.Name + "' as '" + pkg.Name + "'")
for _, strct := range pkg.Structs() {
logger.Println("")
logger.Println(" Processed struct '" + strct.Name + "'")
if strct.IsBound {
for _, method := range strct.Methods {
logger.Println(" Bound method '" + method.Name + "'")
}
}
if strct.IsUsedAsData {
for _, field := range strct.Fields {
if !field.Ignored {
logger.Print(" Processed ")
if field.IsOptional {
logger.Print("optional ")
}
logger.Println("field '" + field.Name + "' as '" + field.JSName() + "'")
}
}
}
}
logger.Println("")
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
}

View File

@@ -1,182 +0,0 @@
package initialise
import (
"fmt"
"io"
"strings"
"time"
"github.com/leaanthony/clir"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/internal/templates"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/git"
)
// AddSubcommand adds the `init` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Load the template shortnames
validShortNames, err := templates.TemplateShortNames()
if err != nil {
return err
}
command := app.NewSubCommand("init", "Initialise a new Wails project")
// Setup template name flag
templateName := "vanilla"
description := "Name of template to use. Valid tempates: " + validShortNames.Join(" ")
command.StringFlag("t", description, &templateName)
// Setup project name
projectName := ""
command.StringFlag("n", "Name of project", &projectName)
// Setup project directory
projectDirectory := ""
command.StringFlag("d", "Project directory", &projectDirectory)
// Quiet Init
quiet := false
command.BoolFlag("q", "Supress output to console", &quiet)
initGit := false
gitInstalled := git.IsInstalled()
if gitInstalled {
// Git Init
command.BoolFlag("g", "Initialise git repository", &initGit)
}
// VSCode project files
vscode := false
command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
// List templates
list := false
command.BoolFlag("l", "List templates", &list)
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
// Are we listing templates?
if list {
app.PrintBanner()
err := templates.OutputList(logger)
logger.Println("")
return err
}
// Validate output type
if !validShortNames.Contains(templateName) {
logger.Print(fmt.Sprintf("[ERROR] Template '%s' is not valid", templateName))
logger.Println("")
command.PrintHelp()
return nil
}
// Validate name
if len(projectName) == 0 {
logger.Println("ERROR: Project name required")
logger.Println("")
command.PrintHelp()
return nil
}
if !quiet {
app.PrintBanner()
}
task := fmt.Sprintf("Initialising Project %s", strings.Title(projectName))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
// Create Template Options
options := &templates.Options{
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
GenerateVSCode: vscode,
InitGit: initGit,
}
// Try to discover author details from git config
err := findAuthorDetails(options)
if err != nil {
return err
}
return initProject(options)
})
return nil
}
// initProject is our main init command
func initProject(options *templates.Options) error {
// Start Time
start := time.Now()
// Install the template
err := templates.Install(options)
if err != nil {
return err
}
if options.InitGit {
err = initGit(options)
if err != nil {
return err
}
}
// Output stats
elapsed := time.Since(start)
options.Logger.Println("")
options.Logger.Println("Project Name: " + options.ProjectName)
options.Logger.Println("Project Directory: " + options.TargetDir)
options.Logger.Println("Project Template: " + options.TemplateName)
if options.GenerateVSCode {
options.Logger.Println("VSCode config files generated.")
}
if options.InitGit {
options.Logger.Println("Git repository initialised.")
}
options.Logger.Println("")
options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Println("")
return nil
}
func initGit(options *templates.Options) error {
err := git.InitRepo(options.TargetDir)
if err != nil {
return errors.Wrap(err, "Unable to initialise git repository:")
}
return nil
}
func findAuthorDetails(options *templates.Options) error {
if git.IsInstalled() {
name, err := git.Name()
if err != nil {
return err
}
options.AuthorName = strings.TrimSpace(name)
email, err := git.Email()
if err != nil {
return err
}
options.AuthorEmail = strings.TrimSpace(email)
}
return nil
}

View File

@@ -1,164 +0,0 @@
package update
import (
"fmt"
"io"
"log"
"os"
"github.com/wailsapp/wails/v2/internal/shell"
"github.com/wailsapp/wails/v2/internal/github"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// AddSubcommand adds the `init` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error {
command := app.NewSubCommand("update", "Update the Wails CLI")
command.LongDescription(`This command allows you to update your version of Wails.`)
// Setup flags
var prereleaseRequired bool
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired)
var specificVersion string
command.StringFlag("version", "Install a specific version (Overrides other flags)", &specificVersion)
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
// Print banner
app.PrintBanner()
logger.Println("Checking for updates...")
var desiredVersion *github.SemanticVersion
var err error
var valid bool
if len(specificVersion) > 0 {
// Check if this is a valid version
valid, err = github.IsValidTag(specificVersion)
if err == nil {
if !valid {
err = fmt.Errorf("version '%s' is invalid", specificVersion)
} else {
desiredVersion, err = github.NewSemanticVersion(specificVersion)
}
}
} else {
if prereleaseRequired {
desiredVersion, err = github.GetLatestPreRelease()
} else {
desiredVersion, err = github.GetLatestStableRelease()
}
}
if err != nil {
return err
}
fmt.Println()
fmt.Println(" Current Version : " + currentVersion)
if len(specificVersion) > 0 {
fmt.Printf(" Desired Version : v%s\n", desiredVersion)
} else {
if prereleaseRequired {
fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion)
} else {
fmt.Printf(" Latest Release : v%s\n", desiredVersion)
}
}
return updateToVersion(logger, desiredVersion, len(specificVersion) > 0, currentVersion)
})
return nil
}
func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.SemanticVersion, force bool, currentVersion string) error {
var targetVersionString = "v" + targetVersion.String()
// Early exit
if targetVersionString == currentVersion {
logger.Println("Looks like you're up to date!")
return nil
}
var desiredVersion string
if !force {
compareVersion := currentVersion
currentVersion, err := github.NewSemanticVersion(compareVersion)
if err != nil {
return err
}
var success bool
// Release -> Pre-Release = Massage current version to prerelease format
if targetVersion.IsPreRelease() && currentVersion.IsRelease() {
testVersion, err := github.NewSemanticVersion(compareVersion + "-0")
if err != nil {
return err
}
success, _ = targetVersion.IsGreaterThan(testVersion)
}
// Pre-Release -> Release = Massage target version to prerelease format
if targetVersion.IsRelease() && currentVersion.IsPreRelease() {
// We are ok with greater than or equal
mainversion := currentVersion.MainVersion()
targetVersion, err = github.NewSemanticVersion(targetVersion.String())
if err != nil {
return err
}
success, _ = targetVersion.IsGreaterThanOrEqual(mainversion)
}
// Release -> Release = Standard check
if (targetVersion.IsRelease() && currentVersion.IsRelease()) ||
(targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) {
success, _ = targetVersion.IsGreaterThan(currentVersion)
}
// Compare
if !success {
logger.Println("Error: The requested version is lower than the current version.")
logger.Println("If this is what you really want to do, use `wails update -version %s`", targetVersionString)
return nil
}
desiredVersion = "v" + targetVersion.String()
} else {
desiredVersion = "v" + targetVersion.String()
}
fmt.Println()
logger.Print("Installing Wails " + desiredVersion + "...")
// Run command in non module directory
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatal("Cannot find home directory! Please file a bug report!")
}
sout, serr, err := shell.RunCommand(homeDir, "go", "get", "github.com/wailsapp/wails/v2/cmd/wails@"+desiredVersion)
if err != nil {
logger.Println("Failed.")
logger.Println(sout + `\n` + serr)
return err
}
fmt.Println()
logger.Println("Wails updated to " + desiredVersion)
return nil
}

View File

@@ -1,62 +0,0 @@
package main
import (
"os"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
"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"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
)
func fatal(message string) {
println(message)
os.Exit(1)
}
func main() {
var err error
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
build.AddBuildSubcommand(app, os.Stdout)
err = initialise.AddSubcommand(app, os.Stdout)
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())
}
err = dev.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = generate.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = update.AddSubcommand(app, os.Stdout, version)
if err != nil {
fatal(err.Error())
}
err = app.Run()
if err != nil {
println("\n\nERROR: " + err.Error())
}
}

View File

@@ -1,3 +0,0 @@
package main
var version = "v2.0.0-alpha.22"

View File

@@ -1,30 +0,0 @@
module github.com/wailsapp/wails/v2
go 1.15
require (
github.com/Masterminds/semver v1.5.0
github.com/davecgh/go-spew v1.1.1
github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gorilla/websocket v1.4.1
github.com/imdario/mergo v0.3.11
github.com/jackmordaunt/icns v1.0.0
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/gosod v0.0.4
github.com/leaanthony/slicer v1.5.0
github.com/matryer/is v1.4.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/olekukonko/tablewriter v0.0.4
github.com/pkg/errors v0.9.1
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.6 // indirect
github.com/xyproto/xpm v1.2.1
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
nhooyr.io/websocket v1.8.6
)

136
v2/go.sum
View File

@@ -1,136 +0,0 @@
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82 h1:shxDsb9Dz27xzk3A0DxP0JuJnZMpKrdg8+E14eiUAX4=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=

View File

@@ -1,40 +0,0 @@
// +build debug
package app
import (
"flag"
"github.com/wailsapp/wails/v2/pkg/logger"
"strings"
)
// Init initialises the application for a debug environment
func (a *App) Init() error {
// Indicate debug mode
a.debug = true
if a.appType == "desktop" {
// Enable dev tools
a.options.DevTools = true
}
// Set log levels
greeting := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
flag.Parse()
if len(*greeting) > 0 {
switch strings.ToLower(*greeting) {
case "trace":
a.logger.SetLogLevel(logger.TRACE)
case "info":
a.logger.SetLogLevel(logger.INFO)
case "warning":
a.logger.SetLogLevel(logger.WARNING)
case "error":
a.logger.SetLogLevel(logger.ERROR)
default:
a.logger.SetLogLevel(logger.DEBUG)
}
}
return nil
}

View File

@@ -1,41 +0,0 @@
// +build !desktop,!hybrid,!server,!dev
package app
// This is the default application that will get run if the user compiles using `go build`.
// The reason we want to prevent that is that the `wails build` command does a lot of behind
// the scenes work such as asset compilation. If we allow `go build`, the state of these assets
// will be unknown and the application will not work as expected.
import (
"os"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
// App defines a Wails application structure
type App struct {
Title string
Width int
Height int
Resizable bool
// Indicates if the app is running in debug mode
debug bool
logger *logger.Logger
}
// CreateApp returns a null application
func CreateApp(_ *options.App) (*App, error) {
return &App{}, nil
}
// Run the application
func (a *App) Run() error {
println(`FATAL: This application was built using "go build". This is unsupported. Please compile using "wails build".`)
os.Exit(1)
return nil
}

View File

@@ -1,235 +0,0 @@
// +build desktop,!server
package app
import (
"context"
"sync"
"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"
"github.com/wailsapp/wails/v2/internal/signal"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/pkg/options"
)
// App defines a Wails application structure
type App struct {
appType string
window *ffenestri.Application
servicebus *servicebus.ServiceBus
logger *logger.Logger
signal *signal.Manager
options *options.App
// Subsystems
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
// This is our binding DB
bindings *binding.Bindings
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
}
// Create App
func CreateApp(appoptions *options.App) (*App, error) {
// Merge default options
options.MergeDefaults(appoptions)
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
// 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)
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
result := &App{
appType: "desktop",
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
menuManager: menuManager,
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
}
result.options = appoptions
// Initialise the app
err := result.Init()
return result, err
}
// Run the application
func (a *App) Run() error {
var err error
// Setup a context
var subsystemWaitGroup sync.WaitGroup
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
// Start the service bus
a.servicebus.Debug()
err = a.servicebus.Start()
if err != nil {
return err
}
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil {
return err
}
a.runtime = runtimesubsystem
err = a.runtime.Start()
if err != nil {
return err
}
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
// Start the logging subsystem
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
return err
}
a.log = log
err = a.log.Start()
if err != nil {
return err
}
// create the dispatcher
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
if err != nil {
return err
}
a.dispatcher = dispatcher
err = dispatcher.Start()
if err != nil {
return err
}
// Start the eventing subsystem
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil {
return err
}
a.event = eventsubsystem
err = a.event.Start()
if err != nil {
return err
}
// Start the menu subsystem
menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
if err != nil {
return err
}
a.menu = menusubsystem
err = a.menu.Start()
if err != nil {
return err
}
// Start the call subsystem
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
a.call = callSubsystem
err = a.call.Start()
if err != nil {
return err
}
// Dump bindings as a debug
bindingDump, err := a.bindings.ToJSON()
if err != nil {
return err
}
err = a.window.Run(dispatcher, bindingDump, a.debug)
a.logger.Trace("Ffenestri.Run() exited")
if err != nil {
return err
}
// Close down all the subsystems
a.logger.Trace("Cancelling subsystems")
cancel()
subsystemWaitGroup.Wait()
a.logger.Trace("Cancelling dispatcher")
dispatcher.Close()
// Close log
a.logger.Trace("Stopping log")
log.Close()
a.logger.Trace("Stopping Service bus")
err = a.servicebus.Stop()
if err != nil {
return err
}
return nil
}

View File

@@ -1,237 +0,0 @@
// +build dev
package app
import (
"context"
"sync"
"github.com/wailsapp/wails/v2/internal/bridge"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/signal"
"github.com/wailsapp/wails/v2/internal/subsystem"
)
// App defines a Wails application structure
type App struct {
appType string
servicebus *servicebus.ServiceBus
logger *logger.Logger
signal *signal.Manager
options *options.App
// Subsystems
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
// This is our binding DB
bindings *binding.Bindings
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
// Bridge
bridge *bridge.Bridge
}
// Create App
func CreateApp(appoptions *options.App) (*App, error) {
// Merge default options
options.MergeDefaults(appoptions)
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
// 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)
}
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
result := &App{
appType: "dev",
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
logger: myLogger,
servicebus: servicebus.New(myLogger),
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
bridge: bridge.NewBridge(myLogger),
}
result.options = appoptions
// Initialise the app
err := result.Init()
return result, err
}
// Run the application
func (a *App) Run() error {
var err error
// Setup a context
var subsystemWaitGroup sync.WaitGroup
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
// Start the service bus
a.servicebus.Debug()
err = a.servicebus.Start()
if err != nil {
return err
}
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil {
return err
}
a.runtime = runtimesubsystem
err = a.runtime.Start()
if err != nil {
return err
}
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
// Start the logging subsystem
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
return err
}
a.log = log
err = a.log.Start()
if err != nil {
return err
}
// create the dispatcher
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
if err != nil {
return err
}
a.dispatcher = dispatcher
err = dispatcher.Start()
if err != nil {
return err
}
// Start the eventing subsystem
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil {
return err
}
a.event = eventsubsystem
err = a.event.Start()
if err != nil {
return err
}
// Start the menu subsystem
menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
if err != nil {
return err
}
a.menu = menusubsystem
err = a.menu.Start()
if err != nil {
return err
}
// Start the call subsystem
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
a.call = callSubsystem
err = a.call.Start()
if err != nil {
return err
}
// Dump bindings as a debug
bindingDump, err := a.bindings.ToJSON()
if err != nil {
return err
}
err = a.bridge.Run(dispatcher, bindingDump, a.debug)
a.logger.Trace("Bridge.Run() exited")
if err != nil {
return err
}
// Close down all the subsystems
a.logger.Trace("Cancelling subsystems")
cancel()
subsystemWaitGroup.Wait()
a.logger.Trace("Cancelling dispatcher")
dispatcher.Close()
// Close log
a.logger.Trace("Stopping log")
log.Close()
a.logger.Trace("Stopping Service bus")
err = a.servicebus.Stop()
if err != nil {
return err
}
return nil
}

View File

@@ -1,194 +0,0 @@
// +build !server,!desktop,hybrid
package app
import (
"os"
"path/filepath"
"github.com/leaanthony/clir"
"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/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/internal/webserver"
)
// Config defines the Application's configuration
type Config struct {
Title string // Title is the value to be displayed in the title bar
Width int // Width is the desired window width
Height int // Height is the desired window height
DevTools bool // DevTools enables or disables the browser development tools
Resizable bool // Resizable when False prevents window resizing
ServerEnabled bool // ServerEnabled when True allows remote connections
}
// App defines a Wails application structure
type App struct {
config Config
window *ffenestri.Application
webserver *webserver.WebServer
binding *subsystem.Binding
call *subsystem.Call
event *subsystem.Event
log *subsystem.Log
runtime *subsystem.Runtime
bindings *binding.Bindings
logger *logger.Logger
dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus
debug bool
}
// Create App
func CreateApp(options *Options) *App {
// Merge default options
options.mergeDefaults()
// Set up logger
myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.INFO)
window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
Title: options.Title,
Width: options.Width,
Height: options.Height,
MinWidth: options.MinWidth,
MinHeight: options.MinHeight,
MaxWidth: options.MaxWidth,
MaxHeight: options.MaxHeight,
StartHidden: options.StartHidden,
DevTools: options.DevTools,
Resizable: !options.DisableResize,
Fullscreen: options.Fullscreen,
}, myLogger)
app := &App{
window: window,
webserver: webserver.NewWebServer(myLogger),
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger, options.Bind),
}
// Initialise the app
app.Init()
return app
}
// Run the application
func (a *App) Run() error {
// Default app options
var port = 8080
var ip = "localhost"
var suppressLogging = false
// Create CLI
cli := clir.NewCli(filepath.Base(os.Args[0]), "Desktop/Server Build", "")
// Setup flags
cli.IntFlag("p", "Port to serve on", &port)
cli.StringFlag("i", "IP to serve on", &ip)
cli.BoolFlag("q", "Suppress logging", &suppressLogging)
// Setup main action
cli.Action(func() error {
// Set IP + Port
a.webserver.SetPort(port)
a.webserver.SetIP(ip)
a.webserver.SetBindings(a.bindings)
// Log information (if we aren't suppressing it)
if !suppressLogging {
cli.PrintBanner()
a.logger.Info("Running server at %s", a.webserver.URL())
}
a.servicebus.Start()
log, err := subsystem.NewLog(a.servicebus, a.logger)
if err != nil {
return err
}
a.log = log
a.log.Start()
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
if err != nil {
return err
}
a.dispatcher = dispatcher
a.dispatcher.Start()
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
if err != nil {
return err
}
a.binding = binding
a.binding.Start()
// Start the eventing subsystem
event, err := subsystem.NewEvent(a.servicebus, a.logger)
if err != nil {
return err
}
a.event = event
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
if err != nil {
return err
}
a.call = call
a.call.Start()
// Required so that the WailsInit functions are fired!
runtime.GoRuntime().Events.Emit("wails:loaded")
// Set IP + Port
a.webserver.SetPort(port)
a.webserver.SetIP(ip)
// Log information (if we aren't suppressing it)
if !suppressLogging {
cli.PrintBanner()
println("Running server at " + a.webserver.URL())
}
// Dump bindings as a debug
bindingDump, err := a.bindings.ToJSON()
if err != nil {
return err
}
go func() {
if err := a.webserver.Start(dispatcher, event); err != nil {
a.logger.Error("Webserver failed to start %s", err)
}
}()
result := a.window.Run(dispatcher, bindingDump)
a.servicebus.Stop()
return result
})
return cli.Run()
}

View File

@@ -1,11 +0,0 @@
// +build !debug
package app
import "github.com/wailsapp/wails/v2/pkg/logger"
// Init initialises the application for a production environment
func (a *App) Init() error {
a.logger.SetLogLevel(logger.ERROR)
return nil
}

View File

@@ -1,173 +0,0 @@
// +build server,!desktop
package app
import (
"os"
"path/filepath"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/internal/webserver"
)
// App defines a Wails application structure
type App struct {
appType string
binding *subsystem.Binding
call *subsystem.Call
event *subsystem.Event
log *subsystem.Log
runtime *subsystem.Runtime
options *options.App
bindings *binding.Bindings
logger *logger.Logger
dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus
webserver *webserver.WebServer
debug bool
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
}
// Create App
func CreateApp(appoptions *options.App) (*App, error) {
// Merge default options
options.MergeDefaults(appoptions)
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
result := &App{
appType: "server",
bindings: binding.NewBindings(myLogger, options.Bind),
logger: myLogger,
servicebus: servicebus.New(myLogger),
webserver: webserver.NewWebServer(myLogger),
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
}
// Initialise app
result.Init()
return result, nil
}
// Run the application
func (a *App) Run() error {
// Default app options
var port = 8080
var ip = "localhost"
var supressLogging = false
var debugMode = false
// Create CLI
cli := clir.NewCli(filepath.Base(os.Args[0]), "Server Build", "")
// Setup flags
cli.IntFlag("p", "Port to serve on", &port)
cli.StringFlag("i", "IP to serve on", &ip)
cli.BoolFlag("d", "Debug mode", &debugMode)
cli.BoolFlag("q", "Supress logging", &supressLogging)
// Setup main action
cli.Action(func() error {
// Set IP + Port
a.webserver.SetPort(port)
a.webserver.SetIP(ip)
a.webserver.SetBindings(a.bindings)
// Log information (if we aren't supressing it)
if !supressLogging {
cli.PrintBanner()
a.logger.Info("Running server at %s", a.webserver.URL())
}
if debugMode {
a.servicebus.Debug()
}
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
a.servicebus.Start()
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
return err
}
a.log = log
a.log.Start()
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
if err != nil {
return err
}
a.dispatcher = dispatcher
a.dispatcher.Start()
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
if err != nil {
return err
}
a.binding = binding
a.binding.Start()
// Start the eventing subsystem
event, err := subsystem.NewEvent(a.servicebus, a.logger)
if err != nil {
return err
}
a.event = event
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
a.call = call
a.call.Start()
// Required so that the WailsInit functions are fired!
runtime.GoRuntime().Events.Emit("wails:loaded")
if err := a.webserver.Start(dispatcher, event); err != nil {
a.logger.Error("Webserver failed to start %s", err)
return err
}
return nil
})
return cli.Run()
}

View File

@@ -1,112 +0,0 @@
package assetdb
import (
"fmt"
"strings"
"unsafe"
)
// AssetDB is a database for assets encoded as byte slices
type AssetDB struct {
db map[string][]byte
}
// NewAssetDB creates a new AssetDB and initialises a blank db
func NewAssetDB() *AssetDB {
return &AssetDB{
db: make(map[string][]byte),
}
}
// AddAsset saves the given byte slice under the given name
func (a *AssetDB) AddAsset(name string, data []byte) {
a.db[name] = data
}
// Remove removes the named asset
func (a *AssetDB) Remove(name string) {
delete(a.db, name)
}
// Asset retrieves the byte slice for the given name
func (a *AssetDB) Read(name string) ([]byte, error) {
result := a.db[name]
if result == nil {
return nil, fmt.Errorf("asset '%s' not found", name)
}
return result, nil
}
// AssetAsString returns the asset as a string.
// It also returns a boolean indicating whether the asset existed or not.
func (a *AssetDB) String(name string) (string, error) {
asset, err := a.Read(name)
if err != nil {
return "", err
}
return *(*string)(unsafe.Pointer(&asset)), nil
}
func (a *AssetDB) Dump() {
fmt.Printf("Assets:\n")
for k, _ := range a.db {
fmt.Println(k)
}
}
// Serialize converts the entire database to a file that when compiled will
// reconstruct the AssetDB during init()
// name: name of the asset.AssetDB instance
// pkg: package name placed at the top of the file
func (a *AssetDB) Serialize(name, pkg string) string {
var cdata strings.Builder
// Set buffer size to 4k
cdata.Grow(4096)
// Write header
header := `// DO NOT EDIT - Generated automatically
package %s
import "github.com/wailsapp/wails/v2/internal/assetdb"
var (
%s *assetdb.AssetDB = assetdb.NewAssetDB()
)
// AssetsDB is a clean interface to the assetdb.AssetDB struct
type AssetsDB interface {
Read(string) ([]byte, error)
String(string) (string, error)
}
// Assets returns the asset database
func Assets() AssetsDB {
return %s
}
func init() {
`
cdata.WriteString(fmt.Sprintf(header, pkg, name, name))
for aname, bytes := range a.db {
cdata.WriteString(fmt.Sprintf("\t%s.AddAsset(\"%s\", []byte{",
name,
aname))
l := len(bytes)
if l == 0 {
cdata.WriteString("0x00})\n")
continue
}
// Convert each byte to hex
for _, b := range bytes[:l-1] {
cdata.WriteString(fmt.Sprintf("0x%x, ", b))
}
cdata.WriteString(fmt.Sprintf("0x%x})\n", bytes[l-1]))
}
cdata.WriteString(`}`)
return cdata.String()
}

View File

@@ -1,70 +0,0 @@
package assetdb
import "testing"
import "github.com/matryer/is"
func TestExistsAsBytes(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("hello", helloworld)
result, err := db.Read("hello")
is.True(err == nil)
is.Equal(result, helloworld)
}
func TestNotExistsAsBytes(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("hello4", helloworld)
result, err := db.Read("hello")
is.True(err != nil)
is.True(result == nil)
}
func TestExistsAsString(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("hello", helloworld)
result, err := db.String("hello")
// Ensure it exists
is.True(err == nil)
// Ensure the string is the same as the byte slice
is.Equal(result, "Hello, World!")
}
func TestNotExistsAsString(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("hello", helloworld)
result, err := db.String("help")
// Ensure it doesn't exist
is.True(err != nil)
// Ensure the string is blank
is.Equal(result, "")
}

View File

@@ -1,176 +0,0 @@
// +build !desktop
package assetdb
import (
"errors"
"io"
"net/http"
"os"
"path"
"sort"
"strings"
"time"
)
var errWhence = errors.New("Seek: invalid whence")
var errOffset = errors.New("Seek: invalid offset")
// Open implements the http.FileSystem interface for the AssetDB
func (a *AssetDB) Open(name string) (http.File, error) {
if name == "/" || name == "" {
return &Entry{name: "/", dir: true, db: a}, nil
}
if data, ok := a.db[name]; ok {
return &Entry{name: name, data: data, size: len(data)}, nil
} else {
for n, _ := range a.db {
if strings.HasPrefix(n, name+"/") {
return &Entry{name: name, db: a, dir: true}, nil
}
}
}
return &Entry{}, os.ErrNotExist
}
// readdir returns the directory entries for the requested directory
func (a *AssetDB) readdir(name string) ([]os.FileInfo, error) {
dir := name
var ents []string
fim := make(map[string]os.FileInfo)
for fn, data := range a.db {
if strings.HasPrefix(fn, dir) {
pieces := strings.Split(fn[len(dir)+1:], "/")
if len(pieces) == 1 {
fim[pieces[0]] = FI{name: pieces[0], dir: false, size: len(data)}
ents = append(ents, pieces[0])
} else {
fim[pieces[0]] = FI{name: pieces[0], dir: true, size: -1}
ents = append(ents, pieces[0])
}
}
}
if len(ents) == 0 {
return nil, os.ErrNotExist
}
sort.Strings(ents)
var list []os.FileInfo
for _, dir := range ents {
list = append(list, fim[dir])
}
return list, nil
}
// Entry implements the http.File interface to allow for
// use in the http.FileSystem implementation of AssetDB
type Entry struct {
name string
data []byte
dir bool
size int
db *AssetDB
off int
}
// Close is a noop
func (e Entry) Close() error {
return nil
}
// Read fills the slice provided returning how many bytes were written
// and any errors encountered
func (e *Entry) Read(p []byte) (n int, err error) {
if e.off >= e.size {
return 0, io.EOF
}
numout := len(p)
if numout > e.size {
numout = e.size
}
for i := 0; i < numout; i++ {
p[i] = e.data[e.off+i]
}
e.off += numout
n = int(numout)
err = nil
return
}
// Seek seeks to the specified offset from the location specified by whence
func (e *Entry) Seek(offset int64, whence int) (int64, error) {
switch whence {
default:
return 0, errWhence
case io.SeekStart:
offset += 0
case io.SeekCurrent:
offset += int64(e.off)
case io.SeekEnd:
offset += int64(e.size)
}
if offset < 0 {
return 0, errOffset
}
e.off = int(offset)
return offset, nil
}
// Readdir returns the directory entries inside this entry if it is a directory
func (e Entry) Readdir(count int) ([]os.FileInfo, error) {
ents := []os.FileInfo{}
if !e.dir {
return ents, errors.New("Not a directory")
}
return e.db.readdir(e.name)
}
// Stat returns information about this directory entry
func (e Entry) Stat() (os.FileInfo, error) {
return FI{e.name, e.size, e.dir}, nil
}
// FI is the AssetDB implementation of os.FileInfo.
type FI struct {
name string
size int
dir bool
}
// IsDir returns true if this is a directory
func (fi FI) IsDir() bool {
return fi.dir
}
// ModTime always returns now
func (fi FI) ModTime() time.Time {
return time.Time{}
}
// Mode returns the file as readonly and directories
// as world writeable and executable
func (fi FI) Mode() os.FileMode {
if fi.IsDir() {
return 0755 | os.ModeDir
}
return 0444
}
// Name returns the name of this object without
// any leading slashes
func (fi FI) Name() string {
return path.Base(fi.name)
}
// Size returns the size of this item
func (fi FI) Size() int64 {
return int64(fi.size)
}
// Sys returns nil
func (fi FI) Sys() interface{} {
return nil
}

View File

@@ -1,108 +0,0 @@
package assetdb
import (
"fmt"
"os"
"testing"
"github.com/matryer/is"
)
func TestOpenLeadingSlash(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("/hello", helloworld)
file, err := db.Open("/hello")
// Ensure it does exist
is.True(err == nil)
buff := make([]byte, len(helloworld))
n, err := file.Read(buff)
fmt.Printf("Error %v\n", err)
is.True(err == nil)
is.Equal(n, len(helloworld))
result := string(buff)
// Ensure the string is blank
is.Equal(result, string(helloworld))
}
func TestOpen(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("/hello", helloworld)
file, err := db.Open("/hello")
// Ensure it does exist
is.True(err == nil)
buff := make([]byte, len(helloworld))
n, err := file.Read(buff)
is.True(err == nil)
is.Equal(n, len(helloworld))
result := string(buff)
// Ensure the string is blank
is.Equal(result, string(helloworld))
}
func TestReaddir(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("/hello", helloworld)
db.AddAsset("/directory/hello", helloworld)
db.AddAsset("/directory/subdirectory/hello", helloworld)
dir, err := db.Open("/doesntexist")
is.True(err == os.ErrNotExist)
ents, err := dir.Readdir(-1)
is.Equal([]os.FileInfo{}, ents)
dir, err = db.Open("/")
is.True(dir != nil)
is.True(err == nil)
ents, err = dir.Readdir(-1)
is.True(err == nil)
is.Equal(3, len(ents))
}
func TestReaddirSubdirectory(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("/hello", helloworld)
db.AddAsset("/directory/hello", helloworld)
db.AddAsset("/directory/subdirectory/hello", helloworld)
expected := []os.FileInfo{
FI{name: "hello", dir: false, size: len(helloworld)},
FI{name: "subdirectory", dir: true, size: -1},
}
dir, err := db.Open("/directory")
is.True(dir != nil)
is.True(err == nil)
ents, err := dir.Readdir(-1)
is.Equal(expected, ents)
// Check sub-subdirectory
dir, err = db.Open("/directory/subdirectory")
is.True(dir != nil)
is.True(err == nil)
ents, err = dir.Readdir(-1)
is.True(err == nil)
is.Equal([]os.FileInfo{FI{name: "hello", size: len(helloworld)}}, ents)
}

View File

@@ -1,9 +0,0 @@
package bind
func IsStructPointer(value interface{}) bool {
switch t := value.(type) {
default:
println(t)
}
return false
}

View File

@@ -1,74 +0,0 @@
package binding
import (
"fmt"
"reflect"
"runtime"
"strings"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger"
)
type Bindings struct {
db *DB
logger logger.CustomLogger
exemptions slicer.StringSlicer
}
// NewBindings returns a new Bindings object
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
result := &Bindings{
db: newDB(),
logger: logger.CustomLogger("Bindings"),
}
for _, exemption := range exemptions {
if exemptions == nil {
continue
}
name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
// Yuk yuk yuk! Is there a better way?
name = strings.TrimSuffix(name, "-fm")
result.exemptions.Add(name)
}
// Add the structs to bind
for _, ptr := range structPointersToBind {
err := result.Add(ptr)
if err != nil {
logger.Fatal("Error during binding: " + err.Error())
}
}
return result
}
// Add the given struct methods to the Bindings
func (b *Bindings) Add(structPtr interface{}) error {
methods, err := b.getMethods(structPtr)
if err != nil {
return fmt.Errorf("cannot bind value to app: %s", err.Error())
}
for _, method := range methods {
splitName := strings.Split(method.Name, ".")
packageName := splitName[0]
structName := splitName[1]
methodName := splitName[2]
// Add it as a regular method
b.db.AddMethod(packageName, structName, methodName, method)
}
return nil
}
func (b *Bindings) DB() *DB {
return b.db
}
func (b *Bindings) ToJSON() (string, error) {
return b.db.ToJSON()
}

View File

@@ -1,100 +0,0 @@
package binding
import (
"encoding/json"
"fmt"
"reflect"
)
// BoundMethod defines all the data related to a Go method that is
// bound to the Wails application
type BoundMethod struct {
Name string `json:"name"`
Inputs []*Parameter `json:"inputs,omitempty"`
Outputs []*Parameter `json:"outputs,omitempty"`
Comments string `json:"comments,omitempty"`
Method reflect.Value `json:"-"`
}
// InputCount returns the number of inputs this bound method has
func (b *BoundMethod) InputCount() int {
return len(b.Inputs)
}
// OutputCount returns the number of outputs this bound method has
func (b *BoundMethod) OutputCount() int {
return len(b.Outputs)
}
// ParseArgs method converts the input json into the types expected by the method
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
result := make([]interface{}, b.InputCount())
if len(args) != b.InputCount() {
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
}
for index, arg := range args {
typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface()
err := json.Unmarshal(arg, inputValue)
if err != nil {
return nil, err
}
if inputValue == nil {
result[index] = reflect.Zero(typ).Interface()
} else {
result[index] = reflect.ValueOf(inputValue).Elem().Interface()
}
}
return result, nil
}
// Call will attempt to call this bound method with the given args
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Check inputs
expectedInputLength := len(b.Inputs)
actualInputLength := len(args)
if expectedInputLength != actualInputLength {
return nil, fmt.Errorf("%s takes %d inputs. Received %d", b.Name, expectedInputLength, actualInputLength)
}
/** Convert inputs to reflect values **/
// Create slice for the input arguments to the method call
callArgs := make([]reflect.Value, expectedInputLength)
// Iterate over given arguments
for index, arg := range args {
// Save the converted argument
callArgs[index] = reflect.ValueOf(arg)
}
// Do the call
callResults := b.Method.Call(callArgs)
//** Check results **//
var returnValue interface{}
var err error
switch b.OutputCount() {
case 1:
// Loop over results and determine if the result
// is an error or not
for _, result := range callResults {
interfac := result.Interface()
temp, ok := interfac.(error)
if ok {
err = temp
} else {
returnValue = interfac
}
}
case 2:
returnValue = callResults[0].Interface()
if temp, ok := callResults[1].Interface().(error); ok {
err = temp
}
}
return returnValue, err
}

View File

@@ -1,104 +0,0 @@
package binding
import (
"encoding/json"
"sync"
"unsafe"
)
// DB is our database of method bindings
type DB struct {
// map[packagename] -> map[structname] -> map[methodname]*method
store map[string]map[string]map[string]*BoundMethod
// This uses fully qualified method names as a shortcut for store traversal.
// It used for performance gains at runtime
methodMap map[string]*BoundMethod
// Lock to ensure sync access to the data
lock sync.RWMutex
}
func newDB() *DB {
return &DB{
store: make(map[string]map[string]map[string]*BoundMethod),
methodMap: make(map[string]*BoundMethod),
}
}
// GetMethodFromStore returns the method for the given package/struct/method names
// nil is returned if any one of those does not exist
func (d *DB) GetMethodFromStore(packageName string, structName string, methodName string) *BoundMethod {
// Lock the db whilst processing and unlock on return
d.lock.RLock()
defer d.lock.RUnlock()
structMap, exists := d.store[packageName]
if !exists {
return nil
}
methodMap, exists := structMap[structName]
if !exists {
return nil
}
return methodMap[methodName]
}
// GetMethod returns the method for the given qualified method name
// qualifiedMethodName is "packagename.structname.methodname"
func (d *DB) GetMethod(qualifiedMethodName string) *BoundMethod {
// Lock the db whilst processing and unlock on return
d.lock.RLock()
defer d.lock.RUnlock()
return d.methodMap[qualifiedMethodName]
}
// AddMethod adds the given method definition to the db using the given qualified path: packageName.structName.methodName
func (d *DB) AddMethod(packageName string, structName string, methodName string, methodDefinition *BoundMethod) {
// TODO: Validate inputs?
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
// Get the map associated with the package name
structMap, exists := d.store[packageName]
if !exists {
// Create a new map for this packagename
d.store[packageName] = make(map[string]map[string]*BoundMethod)
structMap = d.store[packageName]
}
// Get the map associated with the struct name
methodMap, exists := structMap[structName]
if !exists {
// Create a new map for this packagename
structMap[structName] = make(map[string]*BoundMethod)
methodMap = structMap[structName]
}
// Store the method definition
methodMap[methodName] = methodDefinition
// Store in the methodMap
key := packageName + "." + structName + "." + methodName
d.methodMap[key] = methodDefinition
}
// ToJSON converts the method map to JSON
func (d *DB) ToJSON() (string, error) {
// Lock the db whilst processing and unlock on return
d.lock.RLock()
defer d.lock.RUnlock()
bytes, err := json.Marshal(&d.store)
// Return zero copy string as this string will be read only
return *(*string)(unsafe.Pointer(&bytes)), err
}

View File

@@ -1,28 +0,0 @@
package binding
import "reflect"
// Parameter defines a Go method parameter
type Parameter struct {
Name string `json:"name,omitempty"`
TypeName string `json:"type"`
reflectType reflect.Type
}
func newParameter(Name string, Type reflect.Type) *Parameter {
return &Parameter{
Name: Name,
TypeName: Type.String(),
reflectType: Type,
}
}
// IsType returns true if the given
func (p *Parameter) IsType(typename string) bool {
return p.TypeName == typename
}
// IsError returns true if the parameter type is an error
func (p *Parameter) IsError() bool {
return p.IsType("error")
}

View File

@@ -1,102 +0,0 @@
package binding
import (
"fmt"
"reflect"
"runtime"
)
// isStructPtr returns true if the value given is a
// pointer to a struct
func isStructPtr(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Ptr &&
reflect.ValueOf(value).Elem().Kind() == reflect.Struct
}
// isFunction returns true if the given value is a function
func isFunction(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Func
}
// isStructPtr returns true if the value given is a struct
func isStruct(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Struct
}
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
// Create result placeholder
var result []*BoundMethod
// Check type
if !isStructPtr(value) {
if isStruct(value) {
name := reflect.ValueOf(value).Type().Name()
return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name)
}
if isFunction(value) {
name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name()
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name)
}
return nil, fmt.Errorf("not a pointer to a struct.")
}
// Process Struct
structType := reflect.TypeOf(value)
structValue := reflect.ValueOf(value)
baseName := structType.String()[1:]
// Process Methods
for i := 0; i < structType.NumMethod(); i++ {
methodDef := structType.Method(i)
methodName := methodDef.Name
fullMethodName := baseName + "." + methodName
method := structValue.MethodByName(methodName)
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
if b.exemptions.Contains(methodReflectName) {
continue
}
// Create new method
boundMethod := &BoundMethod{
Name: fullMethodName,
Inputs: nil,
Outputs: nil,
Comments: "",
Method: method,
}
// Iterate inputs
methodType := method.Type()
inputParamCount := methodType.NumIn()
var inputs []*Parameter
for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ {
input := methodType.In(inputIndex)
thisParam := newParameter("", input)
inputs = append(inputs, thisParam)
}
boundMethod.Inputs = inputs
// Iterate outputs
// TODO: Determine what to do about limiting return types
// especially around errors.
outputParamCount := methodType.NumOut()
var outputs []*Parameter
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
output := methodType.Out(outputIndex)
thisParam := newParameter("", output)
outputs = append(outputs, thisParam)
}
boundMethod.Outputs = outputs
// Save method in result
result = append(result, boundMethod)
}
return result, nil
}

View File

@@ -1,99 +0,0 @@
package bridge
import (
"context"
"log"
"net/http"
"sync"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/gorilla/websocket"
"github.com/wailsapp/wails/v2/internal/logger"
)
type Bridge struct {
upgrader websocket.Upgrader
server *http.Server
myLogger *logger.Logger
bindings string
dispatcher *messagedispatcher.Dispatcher
mu sync.Mutex
sessions map[string]*session
ctx context.Context
cancel context.CancelFunc
}
func NewBridge(myLogger *logger.Logger) *Bridge {
result := &Bridge{
myLogger: myLogger,
upgrader: websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }},
sessions: make(map[string]*session),
}
myLogger.SetLogLevel(1)
ctx, cancel := context.WithCancel(context.Background())
result.ctx = ctx
result.cancel = cancel
result.server = &http.Server{Addr: ":34115"}
http.HandleFunc("/bridge", result.wsBridgeHandler)
return result
}
func (b *Bridge) Run(dispatcher *messagedispatcher.Dispatcher, bindings string, debug bool) error {
// Ensure we cancel the context when we shutdown
defer b.cancel()
b.bindings = bindings
b.dispatcher = dispatcher
b.myLogger.Info("Bridge mode started.")
err := b.server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
return err
}
return nil
}
func (b *Bridge) wsBridgeHandler(w http.ResponseWriter, r *http.Request) {
c, err := b.upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
if err != nil {
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
}
b.myLogger.Info("Connection from frontend accepted [%s].", c.RemoteAddr().String())
b.startSession(c)
}
func (b *Bridge) startSession(conn *websocket.Conn) {
// Create a new session for this connection
s := newSession(conn, b.bindings, b.dispatcher, b.myLogger, b.ctx)
// Setup the close handler
conn.SetCloseHandler(func(int, string) error {
b.myLogger.Info("Connection dropped [%s].", s.Identifier())
b.mu.Lock()
delete(b.sessions, s.Identifier())
b.mu.Unlock()
return nil
})
b.mu.Lock()
go s.start(len(b.sessions) == 0)
b.sessions[s.Identifier()] = s
b.mu.Unlock()
}

View File

@@ -1,121 +0,0 @@
package bridge
import (
"github.com/wailsapp/wails/v2/pkg/options/dialog"
)
type BridgeClient struct {
session *session
}
func (b BridgeClient) Quit() {
b.session.log.Info("Quit unsupported in Bridge mode")
}
func (b BridgeClient) NotifyEvent(message string) {
//b.session.sendMessage("n" + message)
b.session.log.Info("NotifyEvent: %s", message)
b.session.log.Info("NotifyEvent unsupported in Bridge mode")
}
func (b BridgeClient) CallResult(message string) {
b.session.sendMessage("c" + message)
}
func (b BridgeClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
b.session.log.Info("OpenDialog unsupported in Bridge mode")
}
func (b BridgeClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
b.session.log.Info("SaveDialog unsupported in Bridge mode")
}
func (b BridgeClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
b.session.log.Info("MessageDialog unsupported in Bridge mode")
}
func (b BridgeClient) WindowSetTitle(title string) {
b.session.log.Info("WindowSetTitle unsupported in Bridge mode")
}
func (b BridgeClient) WindowShow() {
b.session.log.Info("WindowShow unsupported in Bridge mode")
}
func (b BridgeClient) WindowHide() {
b.session.log.Info("WindowHide unsupported in Bridge mode")
}
func (b BridgeClient) WindowCenter() {
b.session.log.Info("WindowCenter unsupported in Bridge mode")
}
func (b BridgeClient) WindowMaximise() {
b.session.log.Info("WindowMaximise unsupported in Bridge mode")
}
func (b BridgeClient) WindowUnmaximise() {
b.session.log.Info("WindowUnmaximise unsupported in Bridge mode")
}
func (b BridgeClient) WindowMinimise() {
b.session.log.Info("WindowMinimise unsupported in Bridge mode")
}
func (b BridgeClient) WindowUnminimise() {
b.session.log.Info("WindowUnminimise unsupported in Bridge mode")
}
func (b BridgeClient) WindowPosition(x int, y int) {
b.session.log.Info("WindowPosition unsupported in Bridge mode")
}
func (b BridgeClient) WindowSize(width int, height int) {
b.session.log.Info("WindowSize unsupported in Bridge mode")
}
func (b BridgeClient) WindowSetMinSize(width int, height int) {
b.session.log.Info("WindowSetMinSize unsupported in Bridge mode")
}
func (b BridgeClient) WindowSetMaxSize(width int, height int) {
b.session.log.Info("WindowSetMaxSize unsupported in Bridge mode")
}
func (b BridgeClient) WindowFullscreen() {
b.session.log.Info("WindowFullscreen unsupported in Bridge mode")
}
func (b BridgeClient) WindowUnFullscreen() {
b.session.log.Info("WindowUnFullscreen unsupported in Bridge mode")
}
func (b BridgeClient) WindowSetColour(colour int) {
b.session.log.Info("WindowSetColour unsupported in Bridge mode")
}
func (b BridgeClient) DarkModeEnabled(callbackID string) {
b.session.log.Info("DarkModeEnabled unsupported in Bridge mode")
}
func (b BridgeClient) SetApplicationMenu(menuJSON string) {
b.session.log.Info("SetApplicationMenu unsupported in Bridge mode")
}
func (b BridgeClient) SetTrayMenu(trayMenuJSON string) {
b.session.log.Info("SetTrayMenu unsupported in Bridge mode")
}
func (b BridgeClient) UpdateTrayMenuLabel(JSON string) {
b.session.log.Info("UpdateTrayMenuLabel unsupported in Bridge mode")
}
func (b BridgeClient) UpdateContextMenu(contextMenuJSON string) {
b.session.log.Info("UpdateContextMenu unsupported in Bridge mode")
}
func newBridgeClient(session *session) *BridgeClient {
return &BridgeClient{
session: session,
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,145 +0,0 @@
package bridge
import (
"context"
_ "embed"
"log"
"runtime"
"time"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/gorilla/websocket"
"github.com/wailsapp/wails/v2/internal/logger"
)
//go:embed darwin.js
var darwinRuntime string
// session represents a single websocket session
type session struct {
bindings string
conn *websocket.Conn
//eventManager interfaces.EventManager
log *logger.Logger
//ipc interfaces.IPCManager
// Mutex for writing to the socket
shutdown chan bool
writeChan chan []byte
done bool
// context
ctx context.Context
// client
client *messagedispatcher.DispatchClient
}
func newSession(conn *websocket.Conn, bindings string, dispatcher *messagedispatcher.Dispatcher, logger *logger.Logger, ctx context.Context) *session {
result := &session{
conn: conn,
bindings: bindings,
log: logger,
shutdown: make(chan bool),
writeChan: make(chan []byte, 100),
ctx: ctx,
}
result.client = dispatcher.RegisterClient(newBridgeClient(result))
return result
}
// Identifier returns a string identifier for the remote connection.
// Taking the form of the client's <ip address>:<port>.
func (s *session) Identifier() string {
if s.conn != nil {
return s.conn.RemoteAddr().String()
}
return ""
}
func (s *session) sendMessage(msg string) error {
if !s.done {
s.writeChan <- []byte(msg)
}
return nil
}
func (s *session) start(firstSession bool) {
s.log.SetLogLevel(1)
s.log.Info("Connected to frontend.")
go s.writePump()
var wailsRuntime string
switch runtime.GOOS {
case "darwin":
wailsRuntime = darwinRuntime
default:
log.Fatal("platform not supported")
}
bindingsMessage := "window.wailsbindings = `" + s.bindings + "`;"
s.log.Info(bindingsMessage)
bootstrapMessage := bindingsMessage + wailsRuntime
s.sendMessage("b" + bootstrapMessage)
for {
messageType, buffer, err := s.conn.ReadMessage()
if messageType == -1 {
return
}
if err != nil {
s.log.Error("Error reading message: %v", err)
continue
}
message := string(buffer)
s.log.Debug("Got message: %#v\n", message)
// Dispatch message as normal
s.client.DispatchMessage(message)
if s.done {
break
}
}
}
// Shutdown
func (s *session) Shutdown() {
s.conn.Close()
s.done = true
s.log.Info("session %v exit", s.Identifier())
}
// writePump pulls messages from the writeChan and sends them to the client
// since it uses a channel to read the messages the socket is protected without locks
func (s *session) writePump() {
s.log.Debug("Session %v - writePump start", s.Identifier())
defer s.log.Debug("Session %v - writePump shutdown", s.Identifier())
for {
select {
case <-s.ctx.Done():
s.Shutdown()
return
case msg, ok := <-s.writeChan:
s.conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
if !ok {
s.log.Debug("writeChan was closed!")
s.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
if err := s.conn.WriteMessage(websocket.TextMessage, msg); err != nil {
s.log.Debug(err.Error())
return
}
}
}
}

View File

@@ -1,17 +0,0 @@
package crypto
import (
"crypto/rand"
"fmt"
"log"
)
// RandomID returns a random ID as a string
func RandomID() string {
b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("%x", b)
}

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Joel
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.

View File

@@ -1,125 +0,0 @@
// deepcopy makes deep copies of things. A standard copy will copy the
// pointers: deep copy copies the values pointed to. Unexported field
// values are not copied.
//
// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved.
// License: MIT, for more details check the included LICENSE file.
package deepcopy
import (
"reflect"
"time"
)
// Interface for delegating copy process to type
type Interface interface {
DeepCopy() interface{}
}
// Iface is an alias to Copy; this exists for backwards compatibility reasons.
func Iface(iface interface{}) interface{} {
return Copy(iface)
}
// Copy creates a deep copy of whatever is passed to it and returns the copy
// in an interface{}. The returned value will need to be asserted to the
// correct type.
func Copy(src interface{}) interface{} {
if src == nil {
return nil
}
// Make the interface a reflect.Value
original := reflect.ValueOf(src)
// Make a copy of the same type as the original.
cpy := reflect.New(original.Type()).Elem()
// Recursively copy the original.
copyRecursive(original, cpy)
// Return the copy as an interface.
return cpy.Interface()
}
// copyRecursive does the actual copying of the interface. It currently has
// limited support for what it can handle. Add as needed.
func copyRecursive(original, cpy reflect.Value) {
// check for implement deepcopy.Interface
if original.CanInterface() {
if copier, ok := original.Interface().(Interface); ok {
cpy.Set(reflect.ValueOf(copier.DeepCopy()))
return
}
}
// handle according to original's Kind
switch original.Kind() {
case reflect.Ptr:
// Get the actual value being pointed to.
originalValue := original.Elem()
// if it isn't valid, return.
if !originalValue.IsValid() {
return
}
cpy.Set(reflect.New(originalValue.Type()))
copyRecursive(originalValue, cpy.Elem())
case reflect.Interface:
// If this is a nil, don't do anything
if original.IsNil() {
return
}
// Get the value for the interface, not the pointer.
originalValue := original.Elem()
// Get the value by calling Elem().
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
cpy.Set(copyValue)
case reflect.Struct:
t, ok := original.Interface().(time.Time)
if ok {
cpy.Set(reflect.ValueOf(t))
return
}
// Go through each field of the struct and copy it.
for i := 0; i < original.NumField(); i++ {
// The Type's StructField for a given field is checked to see if StructField.PkgPath
// is set to determine if the field is exported or not because CanSet() returns false
// for settable fields. I'm not sure why. -mohae
if original.Type().Field(i).PkgPath != "" {
continue
}
copyRecursive(original.Field(i), cpy.Field(i))
}
case reflect.Slice:
if original.IsNil() {
return
}
// Make a new slice and copy each element.
cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
for i := 0; i < original.Len(); i++ {
copyRecursive(original.Index(i), cpy.Index(i))
}
case reflect.Map:
if original.IsNil() {
return
}
cpy.Set(reflect.MakeMap(original.Type()))
for _, key := range original.MapKeys() {
originalValue := original.MapIndex(key)
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
copyKey := Copy(key.Interface())
cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
}
default:
cpy.Set(original)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
# 3rd Party Licenses
## vec
Homepage: https://github.com/rxi/vec
License: https://github.com/rxi/vec/blob/master/LICENSE
## json
Homepage: http://git.ozlabs.org/?p=ccan;a=tree;f=ccan/json;hb=HEAD
License: http://git.ozlabs.org/?p=ccan;a=blob;f=licenses/BSD-MIT;h=89de354795ec7a7cdab07c091029653d3618540d;hb=HEAD
## hashmap
Homepage: https://github.com/sheredom/hashmap.h
License: https://github.com/sheredom/hashmap.h/blob/master/LICENSE

View File

@@ -1,95 +0,0 @@
//
// 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;
}
JsonNode* mustParseJSON(const char* JSON) {
JsonNode* parsedUpdate = json_decode(JSON);
if ( parsedUpdate == NULL ) {
ABORT("Unable to decode JSON: %s\n", JSON);
}
return parsedUpdate;
}

View File

@@ -1,40 +0,0 @@
//
// 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);
JsonNode* mustParseJSON(const char* JSON);
#endif //ASSETS_C_COMMON_H

View File

@@ -1,99 +0,0 @@
////
//// 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);
}

View File

@@ -1,33 +0,0 @@
////
//// 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

View File

@@ -1,65 +0,0 @@
#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);
}

View File

@@ -1,27 +0,0 @@
//
// 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

File diff suppressed because one or more lines are too long

View File

@@ -1,187 +0,0 @@
package ffenestri
import (
"runtime"
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/pkg/options"
)
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -lobjc
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
// Application is our main application object
type Application struct {
config *options.App
memory []unsafe.Pointer
// This is the main app pointer
app *C.struct_Application
// Manages menus
menuManager *menumanager.Manager
// Logger
logger logger.CustomLogger
}
func (a *Application) saveMemoryReference(mem unsafe.Pointer) {
a.memory = append(a.memory, mem)
}
func (a *Application) string2CString(str string) *C.char {
result := C.CString(str)
a.saveMemoryReference(unsafe.Pointer(result))
return result
}
func init() {
runtime.LockOSThread()
}
// NewApplicationWithConfig creates a new application based on the given config
func NewApplicationWithConfig(config *options.App, logger *logger.Logger, menuManager *menumanager.Manager) *Application {
return &Application{
config: config,
logger: logger.CustomLogger("Ffenestri"),
menuManager: menuManager,
}
}
// NewApplication creates a new Application with the default config
func NewApplication(logger *logger.Logger) *Application {
return &Application{
config: options.Default,
logger: logger.CustomLogger("Ffenestri"),
}
}
func (a *Application) freeMemory() {
for _, mem := range a.memory {
// fmt.Printf("Freeing memory: %+v\n", mem)
C.free(mem)
}
}
// bool2Cint converts a Go boolean to a C integer
func (a *Application) bool2Cint(value bool) C.int {
if value {
return C.int(1)
}
return C.int(0)
}
// dispatcher is the interface to send messages to
var dispatcher *messagedispatcher.DispatchClient
// Dispatcher is what we register out client with
type Dispatcher interface {
RegisterClient(client messagedispatcher.Client) *messagedispatcher.DispatchClient
}
// DispatchClient is the means for passing messages to the backend
type DispatchClient interface {
SendMessage(string)
}
func intToColour(colour int) (C.int, C.int, C.int, C.int) {
var alpha = C.int(colour & 0xFF)
var blue = C.int((colour >> 8) & 0xFF)
var green = C.int((colour >> 16) & 0xFF)
var red = C.int((colour >> 24) & 0xFF)
return red, green, blue, alpha
}
// Run the application
func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug bool) error {
title := a.string2CString(a.config.Title)
width := C.int(a.config.Width)
height := C.int(a.config.Height)
resizable := a.bool2Cint(!a.config.DisableResize)
devtools := a.bool2Cint(a.config.DevTools)
fullscreen := a.bool2Cint(a.config.Fullscreen)
startHidden := a.bool2Cint(a.config.StartHidden)
logLevel := C.int(a.config.LogLevel)
hideWindowOnClose := a.bool2Cint(a.config.HideWindowOnClose)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel, hideWindowOnClose)
// Save app reference
a.app = (*C.struct_Application)(app)
// Set Min Window Size
minWidth := C.int(a.config.MinWidth)
minHeight := C.int(a.config.MinHeight)
C.SetMinWindowSize(a.app, minWidth, minHeight)
// Set Max Window Size
maxWidth := C.int(a.config.MaxWidth)
maxHeight := C.int(a.config.MaxHeight)
C.SetMaxWindowSize(a.app, maxWidth, maxHeight)
// Set debug if needed
C.SetDebug(app, a.bool2Cint(debug))
// TODO: Move frameless to Linux options
// if a.config.Frameless {
// C.DisableFrame(a.app)
// }
if a.config.RGBA != 0 {
r, g, b, alpha := intToColour(a.config.RGBA)
C.SetColour(a.app, r, g, b, alpha)
}
// Escape bindings so C doesn't freak out
bindings = strings.ReplaceAll(bindings, `"`, `\"`)
// Set bindings
C.SetBindings(app, a.string2CString(bindings))
// save the dispatcher in a package variable so that the C callbacks
// can access it
dispatcher = incomingDispatcher.RegisterClient(newClient(a))
// Process platform settings
err := a.processPlatformSettings()
if err != nil {
return err
}
// Check we could initialise the application
if app != nil {
// Yes - Save memory reference and run app, cleaning up afterwards
a.saveMemoryReference(unsafe.Pointer(app))
C.Run(app, 0, nil)
} else {
// Oh no! We couldn't initialise the application
a.logger.Fatal("Cannot initialise Application.")
}
a.freeMemory()
return nil
}
// messageFromWindowCallback is called by any messages sent in
// webkit to window.external.invoke. It relays the message on to
// the dispatcher.
//export messageFromWindowCallback
func messageFromWindowCallback(data *C.char) {
dispatcher.DispatchMessage(C.GoString(data))
}

View File

@@ -1,44 +0,0 @@
#ifndef __FFENESTRI_H__
#define __FFENESTRI_H__
#include <stdio.h>
struct Application;
extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose);
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 SetTrayMenu(struct Application*, const char *menuTrayJSON);
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
#endif

View File

@@ -1,210 +0,0 @@
package ffenestri
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
import (
"strconv"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger"
)
// Client is our implentation of messageDispatcher.Client
type Client struct {
app *Application
logger logger.CustomLogger
}
func newClient(app *Application) *Client {
return &Client{
app: app,
logger: app.logger,
}
}
// Quit the application
func (c *Client) Quit() {
c.app.logger.Trace("Got shutdown message")
C.Quit(c.app.app)
}
// NotifyEvent will pass on the event message to the frontend
func (c *Client) NotifyEvent(message string) {
eventMessage := `window.wails._.Notify(` + strconv.Quote(message) + `);`
c.app.logger.Trace("eventMessage = %+v", eventMessage)
C.ExecJS(c.app.app, c.app.string2CString(eventMessage))
}
// CallResult contains the result of the call from JS
func (c *Client) CallResult(message string) {
callbackMessage := `window.wails._.Callback(` + strconv.Quote(message) + `);`
c.app.logger.Trace("callbackMessage = %+v", callbackMessage)
C.ExecJS(c.app.app, c.app.string2CString(callbackMessage))
}
// WindowSetTitle sets the window title to the given string
func (c *Client) WindowSetTitle(title string) {
C.SetTitle(c.app.app, c.app.string2CString(title))
}
// WindowFullscreen will set the window to be fullscreen
func (c *Client) WindowFullscreen() {
C.Fullscreen(c.app.app)
}
// WindowUnFullscreen will unfullscreen the window
func (c *Client) WindowUnFullscreen() {
C.UnFullscreen(c.app.app)
}
// WindowShow will show the window
func (c *Client) WindowShow() {
C.Show(c.app.app)
}
// WindowHide will hide the window
func (c *Client) WindowHide() {
C.Hide(c.app.app)
}
// WindowCenter will hide the window
func (c *Client) WindowCenter() {
C.Center(c.app.app)
}
// WindowMaximise will maximise the window
func (c *Client) WindowMaximise() {
C.Maximise(c.app.app)
}
// WindowMinimise will minimise the window
func (c *Client) WindowMinimise() {
C.Minimise(c.app.app)
}
// WindowUnmaximise will unmaximise the window
func (c *Client) WindowUnmaximise() {
C.Unmaximise(c.app.app)
}
// WindowUnminimise will unminimise the window
func (c *Client) WindowUnminimise() {
C.Unminimise(c.app.app)
}
// WindowPosition will position the window to x,y on the
// monitor that the window is mostly on
func (c *Client) WindowPosition(x int, y int) {
C.SetPosition(c.app.app, C.int(x), C.int(y))
}
// WindowSize will resize the window to the given
// width and height
func (c *Client) WindowSize(width int, height int) {
C.SetSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMinSize(width int, height int) {
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMaxSize(width int, height int) {
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour int) {
r, g, b, a := intToColour(colour)
C.SetColour(c.app.app, r, g, b, a)
}
// OpenDialog will open a dialog with the given title and filter
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.AllowFiles),
c.app.bool2Cint(dialogOptions.AllowDirectories),
c.app.bool2Cint(dialogOptions.AllowMultiple),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.ResolvesAliases),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
C.SaveDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// MessageDialog will open a message dialog with the given options
func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
// Sanity check button length
if len(dialogOptions.Buttons) > 4 {
c.app.logger.Error("Given %d message dialog buttons. Maximum is 4", len(dialogOptions.Buttons))
return
}
// Process buttons
buttons := []string{"", "", "", ""}
for i, button := range dialogOptions.Buttons {
buttons[i] = button
}
C.MessageDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(string(dialogOptions.Type)),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Message),
c.app.string2CString(dialogOptions.Icon),
c.app.string2CString(buttons[0]),
c.app.string2CString(buttons[1]),
c.app.string2CString(buttons[2]),
c.app.string2CString(buttons[3]),
c.app.string2CString(dialogOptions.DefaultButton),
c.app.string2CString(dialogOptions.CancelButton))
}
func (c *Client) DarkModeEnabled(callbackID string) {
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
}
func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
}
func (c *Client) SetTrayMenu(trayMenuJSON string) {
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
}
func (c *Client) UpdateTrayMenuLabel(JSON string) {
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
}
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

View File

@@ -1,91 +0,0 @@
package ffenestri
/*
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -framework CoreFoundation -lobjc
#include "ffenestri.h"
#include "ffenestri_darwin.h"
*/
import "C"
func (a *Application) processPlatformSettings() error {
mac := a.config.Mac
titlebar := mac.TitleBar
// HideTitle
if titlebar.HideTitle {
C.HideTitle(a.app)
}
// HideTitleBar
if titlebar.HideTitleBar {
C.HideTitleBar(a.app)
}
// Full Size Content
if titlebar.FullSizeContent {
C.FullSizeContent(a.app)
}
// Toolbar
if titlebar.UseToolbar {
C.UseToolbar(a.app)
}
if titlebar.HideToolbarSeparator {
C.HideToolbarSeparator(a.app)
}
if titlebar.TitlebarAppearsTransparent {
C.TitlebarAppearsTransparent(a.app)
}
// Process window Appearance
if mac.Appearance != "" {
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
}
// Check if the webview should be transparent
if mac.WebviewIsTransparent {
C.WebviewIsTransparent(a.app)
}
// Check if window should be translucent
if mac.WindowBackgroundIsTranslucent {
C.WindowBackgroundIsTranslucent(a.app)
}
// Process menu
//applicationMenu := options.GetApplicationMenu(a.config)
applicationMenu := a.menuManager.GetApplicationMenuJSON()
if applicationMenu != "" {
C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
}
// Process tray
trays, err := a.menuManager.GetTrayMenus()
if err != nil {
return err
}
if trays != nil {
for _, tray := range trays {
C.AddTrayMenu(a.app, a.string2CString(tray))
}
}
// Process context menus
contextMenus, err := a.menuManager.GetContextMenus()
if err != nil {
return err
}
if contextMenus != nil {
for _, contextMenu := range contextMenus {
C.AddContextMenu(a.app, a.string2CString(contextMenu))
}
}
return nil
}

View File

@@ -1,115 +0,0 @@
#ifndef FFENESTRI_DARWIN_H
#define FFENESTRI_DARWIN_H
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
#include <objc/objc-runtime.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.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 *);
void* lookupStringConstant(id constantName);
#endif

View File

@@ -1,984 +0,0 @@
#ifndef __FFENESTRI_LINUX_H__
#define __FFENESTRI_LINUX_H__
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
// References to assets
extern const unsigned char *assets[];
extern const unsigned char runtime;
extern const char *icon[];
// Constants
#define PRIMARY_MOUSE_BUTTON 1
#define MIDDLE_MOUSE_BUTTON 2
#define SECONDARY_MOUSE_BUTTON 3
// MAIN DEBUG FLAG
int debug;
// Credit: https://stackoverflow.com/a/8465083
char *concat(const char *s1, const char *s2)
{
const size_t len1 = strlen(s1);
const size_t len2 = strlen(s2);
char *result = malloc(len1 + len2 + 1);
memcpy(result, s1, len1);
memcpy(result + len1, s2, len2 + 1);
return result;
}
// Debug works like sprintf but mutes if the global debug flag is true
// Credit: https://stackoverflow.com/a/20639708
void Debug(char *message, ...)
{
if (debug)
{
char *temp = concat("TRACE | Ffenestri (C) | ", message);
message = concat(temp, "\n");
free(temp);
va_list args;
va_start(args, message);
vprintf(message, args);
free(message);
va_end(args);
}
}
extern void messageFromWindowCallback(const char *);
typedef void (*ffenestriCallback)(const char *);
struct Application
{
// Gtk Data
GtkApplication *application;
GtkWindow *mainWindow;
GtkWidget *webView;
int signalInvoke;
int signalWindowDrag;
int signalButtonPressed;
int signalButtonReleased;
int signalLoadChanged;
// Saves the events for the drag mouse button
GdkEventButton *dragButtonEvent;
// The number of the default drag button
int dragButton;
// Window Data
const char *title;
char *id;
int width;
int height;
int resizable;
int devtools;
int startHidden;
int fullscreen;
int minWidth;
int minHeight;
int maxWidth;
int maxHeight;
int frame;
// User Data
char *HTML;
// Callback
ffenestriCallback sendMessageToBackend;
// Bindings
const char *bindings;
// Lock - used for sync operations (Should we be using g_mutex?)
int lock;
};
void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden)
{
// Setup main application struct
struct Application *result = malloc(sizeof(struct Application));
result->title = title;
result->width = width;
result->height = height;
result->resizable = resizable;
result->devtools = devtools;
result->fullscreen = fullscreen;
result->minWidth = 0;
result->minHeight = 0;
result->maxWidth = 0;
result->maxHeight = 0;
result->frame = 1;
result->startHidden = startHidden;
// Default drag button is PRIMARY
result->dragButton = PRIMARY_MOUSE_BUTTON;
result->sendMessageToBackend = (ffenestriCallback)messageFromWindowCallback;
// Create a unique ID based on the current unix timestamp
char temp[11];
sprintf(&temp[0], "%d", (int)time(NULL));
result->id = concat("wails.app", &temp[0]);
// Create the main GTK application
GApplicationFlags flags = G_APPLICATION_FLAGS_NONE;
result->application = gtk_application_new(result->id, flags);
// Return the application struct
return (void *)result;
}
void DestroyApplication(struct Application *app)
{
Debug("Destroying Application");
g_application_quit(G_APPLICATION(app->application));
// Release the GTK ID string
if (app->id != NULL)
{
free(app->id);
app->id = NULL;
}
else
{
Debug("Almost a double free for app->id");
}
// Free the bindings
if (app->bindings != NULL)
{
free((void *)app->bindings);
app->bindings = NULL;
}
else
{
Debug("Almost a double free for app->bindings");
}
// Disconnect signal handlers
WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager((WebKitWebView *)app->webView);
g_signal_handler_disconnect(manager, app->signalInvoke);
if( app->frame == 0) {
g_signal_handler_disconnect(manager, app->signalWindowDrag);
g_signal_handler_disconnect(app->webView, app->signalButtonPressed);
g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
}
g_signal_handler_disconnect(app->webView, app->signalLoadChanged);
// Release the main GTK Application
if (app->application != NULL)
{
g_object_unref(app->application);
app->application = NULL;
}
else
{
Debug("Almost a double free for app->application");
}
Debug("Finished Destroying Application");
}
// Quit will stop the gtk application and free up all the memory
// used by the application
void Quit(struct Application *app)
{
Debug("Quit Called");
gtk_window_close((GtkWindow *)app->mainWindow);
g_application_quit((GApplication *)app->application);
DestroyApplication(app);
}
// SetTitle sets the main window title to the given string
void SetTitle(struct Application *app, const char *title)
{
gtk_window_set_title(app->mainWindow, title);
}
// Fullscreen sets the main window to be fullscreen
void Fullscreen(struct Application *app)
{
gtk_window_fullscreen(app->mainWindow);
}
// UnFullscreen resets the main window after a fullscreen
void UnFullscreen(struct Application *app)
{
gtk_window_unfullscreen(app->mainWindow);
}
void setMinMaxSize(struct Application *app)
{
GdkGeometry size;
size.min_width = size.min_height = size.max_width = size.max_height = 0;
int flags = 0;
if (app->maxHeight > 0 && app->maxWidth > 0)
{
size.max_height = app->maxHeight;
size.max_width = app->maxWidth;
flags |= GDK_HINT_MAX_SIZE;
}
if (app->minHeight > 0 && app->minWidth > 0)
{
size.min_height = app->minHeight;
size.min_width = app->minWidth;
flags |= GDK_HINT_MIN_SIZE;
}
gtk_window_set_geometry_hints(app->mainWindow, NULL, &size, flags);
}
char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAction, char **args) {
GtkFileChooserNative *native;
GtkFileChooserAction action = chooserAction;
gint res;
char *filename;
char *title = args[0];
char *filter = args[1];
native = gtk_file_chooser_native_new(title,
app->mainWindow,
action,
"_Open",
"_Cancel");
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
// If we have filters, process them
if (filter[0] != '\0') {
GtkFileFilter *file_filter = gtk_file_filter_new();
gchar **filters = g_strsplit(filter, ",", -1);
gint i;
for(i = 0; filters && filters[i]; i++) {
gtk_file_filter_add_pattern(file_filter, filters[i]);
// Debug("Adding filter pattern: %s\n", filters[i]);
}
gtk_file_filter_set_name(file_filter, filter);
gtk_file_chooser_add_filter(chooser, file_filter);
g_strfreev(filters);
}
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT)
{
filename = gtk_file_chooser_get_filename(chooser);
}
g_object_unref(native);
return filename;
}
// openFileDialogInternal opens a dialog to select a file
// NOTE: The result is a string that will need to be freed!
char *openFileDialogInternal(struct Application *app, char **args)
{
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_OPEN, args);
}
// saveFileDialogInternal opens a dialog to select a file
// NOTE: The result is a string that will need to be freed!
char *saveFileDialogInternal(struct Application *app, char **args)
{
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SAVE, args);
}
// openDirectoryDialogInternal opens a dialog to select a directory
// NOTE: The result is a string that will need to be freed!
char *openDirectoryDialogInternal(struct Application *app, char **args)
{
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, args);
}
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
{
app->minWidth = minWidth;
app->minHeight = minHeight;
}
void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
{
app->maxWidth = maxWidth;
app->maxHeight = maxHeight;
}
// SetColour sets the colour of the webview to the given colour string
int SetColour(struct Application *app, const char *colourString)
{
GdkRGBA rgba;
gboolean result = gdk_rgba_parse(&rgba, colourString);
if (result == FALSE)
{
return 0;
}
// Debug("Setting webview colour to: %s", colourString);
webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
return 1;
}
// DisableFrame disables the window frame
void DisableFrame(struct Application *app)
{
app->frame = 0;
}
void syncCallback(GObject *source_object,
GAsyncResult *res,
void *data)
{
struct Application *app = (struct Application *)data;
app->lock = 0;
}
void syncEval(struct Application *app, const gchar *script)
{
WebKitWebView *webView = (WebKitWebView *)(app->webView);
// wait for lock to free
while (app->lock == 1)
{
g_main_context_iteration(0, true);
}
// Set lock
app->lock = 1;
webkit_web_view_run_javascript(
webView,
script,
NULL, syncCallback, (void*)app);
while (app->lock == 1)
{
g_main_context_iteration(0, true);
}
}
void asyncEval(WebKitWebView *webView, const gchar *script)
{
webkit_web_view_run_javascript(
webView,
script,
NULL, NULL, NULL);
}
typedef void (*dispatchMethod)(struct Application *app, void *);
struct dispatchData
{
struct Application *app;
dispatchMethod method;
void *args;
};
gboolean executeMethod(gpointer data)
{
struct dispatchData *d = (struct dispatchData *)data;
(d->method)(d->app, d->args);
g_free(d);
return FALSE;
}
void ExecJS(struct Application *app, char *js)
{
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)syncEval;
data->args = js;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
typedef char *(*dialogMethod)(struct Application *app, void *);
struct dialogCall
{
struct Application *app;
dialogMethod method;
void *args;
void *filter;
char *result;
int done;
};
gboolean executeMethodWithReturn(gpointer data)
{
struct dialogCall *d = (struct dialogCall *)data;
d->result = (d->method)(d->app, d->args);
d->done = 1;
return FALSE;
}
char *OpenFileDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)openFileDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
usleep(100000);
}
g_free(data);
return data->result;
}
char *SaveFileDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)saveFileDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
usleep(100000);
}
Debug("Dialog done");
Debug("Result = %s\n", data->result);
g_free(data);
// Fingers crossed this wasn't freed by g_free above
return data->result;
}
char *OpenDirectoryDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)openDirectoryDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
usleep(100000);
}
Debug("Directory Dialog done");
Debug("Result = %s\n", data->result);
g_free(data);
// Fingers crossed this wasn't freed by g_free above
return data->result;
}
// Sets the icon to the XPM stored in icon
void setIcon(struct Application *app)
{
GdkPixbuf *appIcon = gdk_pixbuf_new_from_xpm_data((const char **)icon);
gtk_window_set_icon(app->mainWindow, appIcon);
}
static void load_finished_cb(WebKitWebView *webView,
WebKitLoadEvent load_event,
struct Application *app)
{
switch (load_event)
{
// case WEBKIT_LOAD_STARTED:
// /* New load, we have now a provisional URI */
// // printf("Start downloading %s\n", webkit_web_view_get_uri(web_view));
// /* Here we could start a spinner or update the
// * location bar with the provisional URI */
// break;
// case WEBKIT_LOAD_REDIRECTED:
// // printf("Redirected to: %s\n", webkit_web_view_get_uri(web_view));
// break;
// case WEBKIT_LOAD_COMMITTED:
// /* The load is being performed. Current URI is
// * the final one and it won't change unless a new
// * load is requested or a navigation within the
// * same page is performed */
// // printf("Loading: %s\n", webkit_web_view_get_uri(web_view));
// break;
case WEBKIT_LOAD_FINISHED:
/* Load finished, we can now stop the spinner */
// printf("Finished loading: %s\n", webkit_web_view_get_uri(web_view));
// Bindings
Debug("Binding Methods");
syncEval(app, app->bindings);
// Runtime
Debug("Setting up Wails runtime");
syncEval(app, &runtime);
// Loop over assets
int index = 1;
while (1)
{
// Get next asset pointer
const char *asset = assets[index];
// If we have no more assets, break
if (asset == 0x00)
{
break;
}
// sync eval the asset
syncEval(app, asset);
index++;
};
// Set the icon
setIcon(app);
// Setup fullscreen
if (app->fullscreen)
{
Debug("Going fullscreen");
Fullscreen(app);
}
// Setup resize
gtk_window_resize(GTK_WINDOW(app->mainWindow), app->width, app->height);
if (app->resizable)
{
gtk_window_set_default_size(GTK_WINDOW(app->mainWindow), app->width, app->height);
}
else
{
gtk_widget_set_size_request(GTK_WIDGET(app->mainWindow), app->width, app->height);
gtk_window_resize(GTK_WINDOW(app->mainWindow), app->width, app->height);
// Fix the min/max to the window size for good measure
app->minHeight = app->maxHeight = app->height;
app->minWidth = app->maxWidth = app->width;
}
gtk_window_set_resizable(GTK_WINDOW(app->mainWindow), app->resizable ? TRUE : FALSE);
setMinMaxSize(app);
// Centre by default
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
// Show window and focus
if( app->startHidden == 0) {
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_grab_focus(app->webView);
}
break;
}
}
static gboolean disable_context_menu_cb(
WebKitWebView *web_view,
WebKitContextMenu *context_menu,
GdkEvent *event,
WebKitHitTestResult *hit_test_result,
gpointer user_data)
{
return TRUE;
}
static void printEvent(const char *message, GdkEventButton *event)
{
Debug("%s: [button:%d] [x:%f] [y:%f] [time:%d]",
message,
event->button,
event->x_root,
event->y_root,
event->time);
}
static void dragWindow(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result,
struct Application *app)
{
// If we get this message erroneously, ignore
if (app->dragButtonEvent == NULL)
{
return;
}
// Ignore non-toplevel widgets
GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(app->webView));
if (!GTK_IS_WINDOW(window))
{
return;
}
// Initiate the drag
printEvent("Starting drag with event", app->dragButtonEvent);
gtk_window_begin_move_drag(app->mainWindow,
app->dragButton,
app->dragButtonEvent->x_root,
app->dragButtonEvent->y_root,
app->dragButtonEvent->time);
// Clear the event
app->dragButtonEvent = NULL;
return;
}
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, struct Application *app)
{
if (event->type == GDK_BUTTON_PRESS && event->button == app->dragButton)
{
app->dragButtonEvent = event;
}
return FALSE;
}
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Application *app)
{
if (event->type == GDK_BUTTON_RELEASE && event->button == app->dragButton)
{
app->dragButtonEvent = NULL;
}
return FALSE;
}
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result,
struct Application *app)
{
#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
app->sendMessageToBackend(message);
g_free(message);
}
void SetDebug(struct Application *app, int flag)
{
debug = flag;
}
// getCurrentMonitorGeometry gets the geometry of the monitor
// that the window is mostly on.
GdkRectangle getCurrentMonitorGeometry(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));
GdkMonitor *monitor = gdk_display_get_monitor_at_window (display, gdk_window);
// Get the geometry of the monitor
GdkRectangle result;
gdk_monitor_get_geometry (monitor,&result);
return result;
}
/*******************
* Window Position *
*******************/
// Position holds an x/y corrdinate
struct Position {
int x;
int y;
};
// Internal call for setting the position of the window.
void setPositionInternal(struct Application *app, struct Position *pos) {
// Get the monitor geometry
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
// Move the window relative to the monitor
gtk_window_move(app->mainWindow, m.x + pos->x, m.y + pos->y);
// Free memory
free(pos);
}
// SetPosition sets the position of the window to the given x/y
// coordinates. The x/y values are relative to the monitor
// the window is mostly on.
void SetPosition(struct Application *app, int x, int y) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)setPositionInternal;
struct Position *pos = malloc(sizeof(struct Position));
pos->x = x;
pos->y = y;
data->args = pos;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
/***************
* Window Size *
***************/
// Size holds a width/height
struct Size {
int width;
int height;
};
// Internal call for setting the size of the window.
void setSizeInternal(struct Application *app, struct Size *size) {
gtk_window_resize(app->mainWindow, size->width, size->height);
free(size);
}
// SetSize sets the size of the window to the given width/height
void SetSize(struct Application *app, int width, int height) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)setSizeInternal;
struct Size *size = malloc(sizeof(struct Size));
size->width = width;
size->height = height;
data->args = size;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
// centerInternal will center the main window on the monitor it is mostly in
void centerInternal(struct Application *app)
{
// Get the geometry of the monitor
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
// Get the window width/height
int windowWidth, windowHeight;
gtk_window_get_size(app->mainWindow, &windowWidth, &windowHeight);
// Place the window at the center of the monitor
gtk_window_move(app->mainWindow, ((m.width - windowWidth) / 2) + m.x, ((m.height - windowHeight) / 2) + m.y);
}
// Center the window
void Center(struct Application *app) {
// Setup a call to centerInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)centerInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// hideInternal hides the main window
void hideInternal(struct Application *app) {
gtk_widget_hide (GTK_WIDGET(app->mainWindow));
}
// Hide places the hideInternal method onto the main thread for execution
void Hide(struct Application *app) {
// Setup a call to hideInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)hideInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// showInternal shows the main window
void showInternal(struct Application *app) {
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_grab_focus(app->webView);
}
// Show places the showInternal method onto the main thread for execution
void Show(struct Application *app) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)showInternal;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
// maximiseInternal maximises the main window
void maximiseInternal(struct Application *app) {
gtk_window_maximize(GTK_WIDGET(app->mainWindow));
}
// Maximise places the maximiseInternal method onto the main thread for execution
void Maximise(struct Application *app) {
// Setup a call to maximiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)maximiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// unmaximiseInternal unmaximises the main window
void unmaximiseInternal(struct Application *app) {
gtk_window_unmaximize(GTK_WIDGET(app->mainWindow));
}
// Unmaximise places the unmaximiseInternal method onto the main thread for execution
void Unmaximise(struct Application *app) {
// Setup a call to unmaximiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)unmaximiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// minimiseInternal minimises the main window
void minimiseInternal(struct Application *app) {
gtk_window_iconify(app->mainWindow);
}
// Minimise places the minimiseInternal method onto the main thread for execution
void Minimise(struct Application *app) {
// Setup a call to minimiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)minimiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// unminimiseInternal unminimises the main window
void unminimiseInternal(struct Application *app) {
gtk_window_present(app->mainWindow);
}
// Unminimise places the unminimiseInternal method onto the main thread for execution
void Unminimise(struct Application *app) {
// Setup a call to unminimiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)unminimiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
void SetBindings(struct Application *app, const char *bindings)
{
const char *temp = concat("window.wailsbindings = \"", bindings);
const char *jscall = concat(temp, "\";");
free((void *)temp);
app->bindings = jscall;
}
// This is called when the close button on the window is pressed
gboolean close_button_pressed(GtkWidget *widget,
GdkEvent *event,
struct Application *app)
{
app->sendMessageToBackend("WC"); // Window Close
return TRUE;
}
static void setupWindow(struct Application *app)
{
// Create the window
GtkWidget *mainWindow = gtk_application_window_new(app->application);
// Save reference
app->mainWindow = GTK_WINDOW(mainWindow);
// Setup frame
gtk_window_set_decorated((GtkWindow *)mainWindow, app->frame);
// Setup title
gtk_window_set_title(GTK_WINDOW(mainWindow), app->title);
// Setup script handler
WebKitUserContentManager *contentManager = webkit_user_content_manager_new();
// Setup the invoke handler
webkit_user_content_manager_register_script_message_handler(contentManager, "external");
app->signalInvoke = g_signal_connect(contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), app);
// Setup the window drag handler if this is a frameless app
if ( app->frame == 0 ) {
webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
// Setup the mouse handlers
app->signalButtonPressed = g_signal_connect(app->webView, "button-press-event", G_CALLBACK(buttonPress), app);
app->signalButtonReleased = g_signal_connect(app->webView, "button-release-event", G_CALLBACK(buttonRelease), app);
}
GtkWidget *webView = webkit_web_view_new_with_user_content_manager(contentManager);
// Save reference
app->webView = webView;
// Add the webview to the window
gtk_container_add(GTK_CONTAINER(mainWindow), webView);
// Load default HTML
app->signalLoadChanged = g_signal_connect(G_OBJECT(webView), "load-changed", G_CALLBACK(load_finished_cb), app);
// Load the user's HTML
// assets[0] is the HTML because the asset array is bundled like that by convention
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webView), assets[0]);
// Check if we want to enable the dev tools
if (app->devtools)
{
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webView));
// webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
webkit_settings_set_enable_developer_extras(settings, true);
}
else
{
g_signal_connect(G_OBJECT(webView), "context-menu", G_CALLBACK(disable_context_menu_cb), app);
}
// Listen for close button signal
g_signal_connect(GTK_WIDGET(mainWindow), "delete-event", G_CALLBACK(close_button_pressed), app);
}
static void activate(GtkApplication* _, struct Application *app)
{
setupWindow(app);
}
void Run(struct Application *app, int argc, char **argv)
{
g_signal_connect(app->application, "activate", G_CALLBACK(activate), app);
g_application_run(G_APPLICATION(app->application), argc, argv);
}
#endif

View File

@@ -1,518 +0,0 @@
/*
The latest version of this library is available on GitHub;
https://github.com/sheredom/hashmap.h
*/
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
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 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.
For more information, please refer to <http://unlicense.org/>
*/
#ifndef SHEREDOM_HASHMAP_H_INCLUDED
#define SHEREDOM_HASHMAP_H_INCLUDED
#if defined(_MSC_VER)
// Workaround a bug in the MSVC runtime where it uses __cplusplus when not
// defined.
#pragma warning(push, 0)
#pragma warning(disable : 4668)
#endif
#include <stdlib.h>
#include <string.h>
#if (defined(_MSC_VER) && defined(__AVX__)) || \
(!defined(_MSC_VER) && defined(__SSE4_2__))
#define HASHMAP_SSE42
#endif
#if defined(HASHMAP_SSE42)
#include <nmmintrin.h>
#endif
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#if defined(_MSC_VER)
#pragma warning(push)
// Stop MSVC complaining about not inlining functions.
#pragma warning(disable : 4710)
// Stop MSVC complaining about inlining functions!
#pragma warning(disable : 4711)
#elif defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
#endif
#if defined(_MSC_VER)
#define HASHMAP_USED
#elif defined(__GNUC__)
#define HASHMAP_USED __attribute__((used))
#else
#define HASHMAP_USED
#endif
/* We need to keep keys and values. */
struct hashmap_element_s {
const char *key;
unsigned key_len;
int in_use;
void *data;
};
/* A hashmap has some maximum size and current size, as well as the data to
* hold. */
struct hashmap_s {
unsigned table_size;
unsigned size;
struct hashmap_element_s *data;
};
#define HASHMAP_MAX_CHAIN_LENGTH (8)
#if defined(__cplusplus)
extern "C" {
#endif
/// @brief Create a hashmap.
/// @param initial_size The initial size of the hashmap. Must be a power of two.
/// @param out_hashmap The storage for the created hashmap.
/// @return On success 0 is returned.
///
/// Note that the initial size of the hashmap must be a power of two, and
/// creation of the hashmap will fail if this is not the case.
static int hashmap_create(const unsigned initial_size,
struct hashmap_s *const out_hashmap) HASHMAP_USED;
/// @brief Put an element into the hashmap.
/// @param hashmap The hashmap to insert into.
/// @param key The string key to use.
/// @param len The length of the string key.
/// @param value The value to insert.
/// @return On success 0 is returned.
///
/// The key string slice is not copied when creating the hashmap entry, and thus
/// must remain a valid pointer until the hashmap entry is removed or the
/// hashmap is destroyed.
static int hashmap_put(struct hashmap_s *const hashmap, const char *const key,
const unsigned len, void *const value) HASHMAP_USED;
/// @brief Get an element from the hashmap.
/// @param hashmap The hashmap to get from.
/// @param key The string key to use.
/// @param len The length of the string key.
/// @return The previously set element, or NULL if none exists.
static void *hashmap_get(const struct hashmap_s *const hashmap,
const char *const key,
const unsigned len) HASHMAP_USED;
/// @brief Remove an element from the hashmap.
/// @param hashmap The hashmap to remove from.
/// @param key The string key to use.
/// @param len The length of the string key.
/// @return On success 0 is returned.
static int hashmap_remove(struct hashmap_s *const hashmap,
const char *const key,
const unsigned len) HASHMAP_USED;
/// @brief Iterate over all the elements in a hashmap.
/// @param hashmap The hashmap to iterate over.
/// @param f The function pointer to call on each element.
/// @param context The context to pass as the first argument to f.
/// @return If the entire hashmap was iterated then 0 is returned. Otherwise if
/// the callback function f returned non-zero then non-zero is returned.
static int hashmap_iterate(const struct hashmap_s *const hashmap,
int (*f)(void *const context, void *const value),
void *const context) HASHMAP_USED;
/// @brief Iterate over all the elements in a hashmap.
/// @param hashmap The hashmap to iterate over.
/// @param f The function pointer to call on each element.
/// @param context The context to pass as the first argument to f.
/// @return If the entire hashmap was iterated then 0 is returned.
/// Otherwise if the callback function f returned positive then the positive
/// value is returned. If the callback function returns -1, the current item
/// is removed and iteration continues.
static int hashmap_iterate_pairs(struct hashmap_s *const hashmap,
int (*f)(void *const, struct hashmap_element_s *const),
void *const context) HASHMAP_USED;
/// @brief Get the size of the hashmap.
/// @param hashmap The hashmap to get the size of.
/// @return The size of the hashmap.
static unsigned
hashmap_num_entries(const struct hashmap_s *const hashmap) HASHMAP_USED;
/// @brief Destroy the hashmap.
/// @param hashmap The hashmap to destroy.
static void hashmap_destroy(struct hashmap_s *const hashmap) HASHMAP_USED;
static unsigned hashmap_crc32_helper(const char *const s,
const unsigned len) HASHMAP_USED;
static unsigned
hashmap_hash_helper_int_helper(const struct hashmap_s *const m,
const char *const keystring,
const unsigned len) HASHMAP_USED;
static int hashmap_match_helper(const struct hashmap_element_s *const element,
const char *const key,
const unsigned len) HASHMAP_USED;
static int hashmap_hash_helper(const struct hashmap_s *const m,
const char *const key, const unsigned len,
unsigned *const out_index) HASHMAP_USED;
static int hashmap_rehash_iterator(void *const new_hash,
struct hashmap_element_s *const e) HASHMAP_USED;
static int hashmap_rehash_helper(struct hashmap_s *const m) HASHMAP_USED;
#if defined(__cplusplus)
}
#endif
#if defined(__cplusplus)
#define HASHMAP_CAST(type, x) static_cast<type>(x)
#define HASHMAP_PTR_CAST(type, x) reinterpret_cast<type>(x)
#define HASHMAP_NULL NULL
#else
#define HASHMAP_CAST(type, x) ((type)x)
#define HASHMAP_PTR_CAST(type, x) ((type)x)
#define HASHMAP_NULL 0
#endif
int hashmap_create(const unsigned initial_size,
struct hashmap_s *const out_hashmap) {
if (0 == initial_size || 0 != (initial_size & (initial_size - 1))) {
return 1;
}
out_hashmap->data =
HASHMAP_CAST(struct hashmap_element_s *,
calloc(initial_size, sizeof(struct hashmap_element_s)));
if (!out_hashmap->data) {
return 1;
}
out_hashmap->table_size = initial_size;
out_hashmap->size = 0;
return 0;
}
int hashmap_put(struct hashmap_s *const m, const char *const key,
const unsigned len, void *const value) {
unsigned int index;
/* Find a place to put our value. */
while (!hashmap_hash_helper(m, key, len, &index)) {
if (hashmap_rehash_helper(m)) {
return 1;
}
}
/* Set the data. */
m->data[index].data = value;
m->data[index].key = key;
m->data[index].key_len = len;
m->data[index].in_use = 1;
m->size++;
return 0;
}
void *hashmap_get(const struct hashmap_s *const m, const char *const key,
const unsigned len) {
unsigned int curr;
unsigned int i;
/* Find data location */
curr = hashmap_hash_helper_int_helper(m, key, len);
/* Linear probing, if necessary */
for (i = 0; i < HASHMAP_MAX_CHAIN_LENGTH; i++) {
if (m->data[curr].in_use) {
if (hashmap_match_helper(&m->data[curr], key, len)) {
return m->data[curr].data;
}
}
curr = (curr + 1) % m->table_size;
}
/* Not found */
return HASHMAP_NULL;
}
int hashmap_remove(struct hashmap_s *const m, const char *const key,
const unsigned len) {
unsigned int i;
unsigned int curr;
/* Find key */
curr = hashmap_hash_helper_int_helper(m, key, len);
/* Linear probing, if necessary */
for (i = 0; i < HASHMAP_MAX_CHAIN_LENGTH; i++) {
if (m->data[curr].in_use) {
if (hashmap_match_helper(&m->data[curr], key, len)) {
/* Blank out the fields including in_use */
memset(&m->data[curr], 0, sizeof(struct hashmap_element_s));
/* Reduce the size */
m->size--;
return 0;
}
}
curr = (curr + 1) % m->table_size;
}
return 1;
}
int hashmap_iterate(const struct hashmap_s *const m,
int (*f)(void *const, void *const), void *const context) {
unsigned int i;
/* Linear probing */
for (i = 0; i < m->table_size; i++) {
if (m->data[i].in_use) {
if (!f(context, m->data[i].data)) {
return 1;
}
}
}
return 0;
}
int hashmap_iterate_pairs(struct hashmap_s *const hashmap,
int (*f)(void *const, struct hashmap_element_s *const),
void *const context) {
unsigned int i;
struct hashmap_element_s *p;
int r;
/* Linear probing */
for (i = 0; i < hashmap->table_size; i++) {
p=&hashmap->data[i];
if (p->in_use) {
r=f(context, p);
switch (r)
{
case -1: /* remove item */
memset(p, 0, sizeof(struct hashmap_element_s));
hashmap->size--;
break;
case 0: /* continue iterating */
break;
default: /* early exit */
return 1;
}
}
}
return 0;
}
void hashmap_destroy(struct hashmap_s *const m) {
free(m->data);
memset(m, 0, sizeof(struct hashmap_s));
}
unsigned hashmap_num_entries(const struct hashmap_s *const m) {
return m->size;
}
unsigned hashmap_crc32_helper(const char *const s, const unsigned len) {
unsigned i;
unsigned crc32val = 0;
#if defined(HASHMAP_SSE42)
for (i = 0; i < len; i++) {
crc32val = _mm_crc32_u8(crc32val, HASHMAP_CAST(unsigned char, s[i]));
}
return crc32val;
#else
// Using polynomial 0x11EDC6F41 to match SSE 4.2's crc function.
static const unsigned crc32_tab[] = {
0x00000000U, 0xF26B8303U, 0xE13B70F7U, 0x1350F3F4U, 0xC79A971FU,
0x35F1141CU, 0x26A1E7E8U, 0xD4CA64EBU, 0x8AD958CFU, 0x78B2DBCCU,
0x6BE22838U, 0x9989AB3BU, 0x4D43CFD0U, 0xBF284CD3U, 0xAC78BF27U,
0x5E133C24U, 0x105EC76FU, 0xE235446CU, 0xF165B798U, 0x030E349BU,
0xD7C45070U, 0x25AFD373U, 0x36FF2087U, 0xC494A384U, 0x9A879FA0U,
0x68EC1CA3U, 0x7BBCEF57U, 0x89D76C54U, 0x5D1D08BFU, 0xAF768BBCU,
0xBC267848U, 0x4E4DFB4BU, 0x20BD8EDEU, 0xD2D60DDDU, 0xC186FE29U,
0x33ED7D2AU, 0xE72719C1U, 0x154C9AC2U, 0x061C6936U, 0xF477EA35U,
0xAA64D611U, 0x580F5512U, 0x4B5FA6E6U, 0xB93425E5U, 0x6DFE410EU,
0x9F95C20DU, 0x8CC531F9U, 0x7EAEB2FAU, 0x30E349B1U, 0xC288CAB2U,
0xD1D83946U, 0x23B3BA45U, 0xF779DEAEU, 0x05125DADU, 0x1642AE59U,
0xE4292D5AU, 0xBA3A117EU, 0x4851927DU, 0x5B016189U, 0xA96AE28AU,
0x7DA08661U, 0x8FCB0562U, 0x9C9BF696U, 0x6EF07595U, 0x417B1DBCU,
0xB3109EBFU, 0xA0406D4BU, 0x522BEE48U, 0x86E18AA3U, 0x748A09A0U,
0x67DAFA54U, 0x95B17957U, 0xCBA24573U, 0x39C9C670U, 0x2A993584U,
0xD8F2B687U, 0x0C38D26CU, 0xFE53516FU, 0xED03A29BU, 0x1F682198U,
0x5125DAD3U, 0xA34E59D0U, 0xB01EAA24U, 0x42752927U, 0x96BF4DCCU,
0x64D4CECFU, 0x77843D3BU, 0x85EFBE38U, 0xDBFC821CU, 0x2997011FU,
0x3AC7F2EBU, 0xC8AC71E8U, 0x1C661503U, 0xEE0D9600U, 0xFD5D65F4U,
0x0F36E6F7U, 0x61C69362U, 0x93AD1061U, 0x80FDE395U, 0x72966096U,
0xA65C047DU, 0x5437877EU, 0x4767748AU, 0xB50CF789U, 0xEB1FCBADU,
0x197448AEU, 0x0A24BB5AU, 0xF84F3859U, 0x2C855CB2U, 0xDEEEDFB1U,
0xCDBE2C45U, 0x3FD5AF46U, 0x7198540DU, 0x83F3D70EU, 0x90A324FAU,
0x62C8A7F9U, 0xB602C312U, 0x44694011U, 0x5739B3E5U, 0xA55230E6U,
0xFB410CC2U, 0x092A8FC1U, 0x1A7A7C35U, 0xE811FF36U, 0x3CDB9BDDU,
0xCEB018DEU, 0xDDE0EB2AU, 0x2F8B6829U, 0x82F63B78U, 0x709DB87BU,
0x63CD4B8FU, 0x91A6C88CU, 0x456CAC67U, 0xB7072F64U, 0xA457DC90U,
0x563C5F93U, 0x082F63B7U, 0xFA44E0B4U, 0xE9141340U, 0x1B7F9043U,
0xCFB5F4A8U, 0x3DDE77ABU, 0x2E8E845FU, 0xDCE5075CU, 0x92A8FC17U,
0x60C37F14U, 0x73938CE0U, 0x81F80FE3U, 0x55326B08U, 0xA759E80BU,
0xB4091BFFU, 0x466298FCU, 0x1871A4D8U, 0xEA1A27DBU, 0xF94AD42FU,
0x0B21572CU, 0xDFEB33C7U, 0x2D80B0C4U, 0x3ED04330U, 0xCCBBC033U,
0xA24BB5A6U, 0x502036A5U, 0x4370C551U, 0xB11B4652U, 0x65D122B9U,
0x97BAA1BAU, 0x84EA524EU, 0x7681D14DU, 0x2892ED69U, 0xDAF96E6AU,
0xC9A99D9EU, 0x3BC21E9DU, 0xEF087A76U, 0x1D63F975U, 0x0E330A81U,
0xFC588982U, 0xB21572C9U, 0x407EF1CAU, 0x532E023EU, 0xA145813DU,
0x758FE5D6U, 0x87E466D5U, 0x94B49521U, 0x66DF1622U, 0x38CC2A06U,
0xCAA7A905U, 0xD9F75AF1U, 0x2B9CD9F2U, 0xFF56BD19U, 0x0D3D3E1AU,
0x1E6DCDEEU, 0xEC064EEDU, 0xC38D26C4U, 0x31E6A5C7U, 0x22B65633U,
0xD0DDD530U, 0x0417B1DBU, 0xF67C32D8U, 0xE52CC12CU, 0x1747422FU,
0x49547E0BU, 0xBB3FFD08U, 0xA86F0EFCU, 0x5A048DFFU, 0x8ECEE914U,
0x7CA56A17U, 0x6FF599E3U, 0x9D9E1AE0U, 0xD3D3E1ABU, 0x21B862A8U,
0x32E8915CU, 0xC083125FU, 0x144976B4U, 0xE622F5B7U, 0xF5720643U,
0x07198540U, 0x590AB964U, 0xAB613A67U, 0xB831C993U, 0x4A5A4A90U,
0x9E902E7BU, 0x6CFBAD78U, 0x7FAB5E8CU, 0x8DC0DD8FU, 0xE330A81AU,
0x115B2B19U, 0x020BD8EDU, 0xF0605BEEU, 0x24AA3F05U, 0xD6C1BC06U,
0xC5914FF2U, 0x37FACCF1U, 0x69E9F0D5U, 0x9B8273D6U, 0x88D28022U,
0x7AB90321U, 0xAE7367CAU, 0x5C18E4C9U, 0x4F48173DU, 0xBD23943EU,
0xF36E6F75U, 0x0105EC76U, 0x12551F82U, 0xE03E9C81U, 0x34F4F86AU,
0xC69F7B69U, 0xD5CF889DU, 0x27A40B9EU, 0x79B737BAU, 0x8BDCB4B9U,
0x988C474DU, 0x6AE7C44EU, 0xBE2DA0A5U, 0x4C4623A6U, 0x5F16D052U,
0xAD7D5351U};
for (i = 0; i < len; i++) {
crc32val = crc32_tab[(HASHMAP_CAST(unsigned char, crc32val) ^
HASHMAP_CAST(unsigned char, s[i]))] ^
(crc32val >> 8);
}
return crc32val;
#endif
}
unsigned hashmap_hash_helper_int_helper(const struct hashmap_s *const m,
const char *const keystring,
const unsigned len) {
unsigned key = hashmap_crc32_helper(keystring, len);
/* Robert Jenkins' 32 bit Mix Function */
key += (key << 12);
key ^= (key >> 22);
key += (key << 4);
key ^= (key >> 9);
key += (key << 10);
key ^= (key >> 2);
key += (key << 7);
key ^= (key >> 12);
/* Knuth's Multiplicative Method */
key = (key >> 3) * 2654435761;
return key % m->table_size;
}
int hashmap_match_helper(const struct hashmap_element_s *const element,
const char *const key, const unsigned len) {
return (element->key_len == len) && (0 == memcmp(element->key, key, len));
}
int hashmap_hash_helper(const struct hashmap_s *const m, const char *const key,
const unsigned len, unsigned *const out_index) {
unsigned int curr;
unsigned int i;
/* If full, return immediately */
if (m->size >= m->table_size) {
return 0;
}
/* Find the best index */
curr = hashmap_hash_helper_int_helper(m, key, len);
/* Linear probing */
for (i = 0; i < HASHMAP_MAX_CHAIN_LENGTH; i++) {
if (!m->data[curr].in_use) {
*out_index = curr;
return 1;
}
if (m->data[curr].in_use &&
hashmap_match_helper(&m->data[curr], key, len)) {
*out_index = curr;
return 1;
}
curr = (curr + 1) % m->table_size;
}
return 0;
}
int hashmap_rehash_iterator(void *const new_hash,
struct hashmap_element_s *const e) {
int temp=hashmap_put(HASHMAP_PTR_CAST(struct hashmap_s *, new_hash),
e->key, e->key_len, e->data);
if (0<temp) {
return 1;
}
/* clear old value to avoid stale pointers */
return -1;
}
/*
* Doubles the size of the hashmap, and rehashes all the elements
*/
int hashmap_rehash_helper(struct hashmap_s *const m) {
/* If this multiplication overflows hashmap_create will fail. */
unsigned new_size = 2 * m->table_size;
struct hashmap_s new_hash;
int flag = hashmap_create(new_size, &new_hash);
if (0!=flag) {
return flag;
}
/* copy the old elements to the new table */
flag = hashmap_iterate_pairs(m, hashmap_rehash_iterator, HASHMAP_PTR_CAST(void *, &new_hash));
if (0!=flag) {
return flag;
}
hashmap_destroy(m);
/* put new hash into old hash structure by copying */
memcpy(m, &new_hash, sizeof(struct hashmap_s));
return 0;
}
#if defined(_MSC_VER)
#pragma warning(pop)
#elif defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,122 +0,0 @@
/*
Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
All rights reserved.
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.
Source: http://git.ozlabs.org/?p=ccan;a=tree;f=ccan/json;hb=HEAD
*/
#ifndef CCAN_JSON_H
#define CCAN_JSON_H
#include <stdbool.h>
#include <stddef.h>
typedef enum {
JSON_NULL,
JSON_BOOL,
JSON_STRING,
JSON_NUMBER,
JSON_ARRAY,
JSON_OBJECT,
} JsonTag;
typedef struct JsonNode JsonNode;
struct JsonNode
{
/* only if parent is an object or array (NULL otherwise) */
JsonNode *parent;
JsonNode *prev, *next;
/* only if parent is an object (NULL otherwise) */
char *key; /* Must be valid UTF-8. */
JsonTag tag;
union {
/* JSON_BOOL */
bool bool_;
/* JSON_STRING */
char *string_; /* Must be valid UTF-8. */
/* JSON_NUMBER */
double number_;
/* JSON_ARRAY */
/* JSON_OBJECT */
struct {
JsonNode *head, *tail;
} children;
};
};
/*** Encoding, decoding, and validation ***/
JsonNode *json_decode (const char *json);
char *json_encode (const JsonNode *node);
char *json_encode_string (const char *str);
char *json_stringify (const JsonNode *node, const char *space);
void json_delete (JsonNode *node);
bool json_validate (const char *json);
/*** Lookup and traversal ***/
JsonNode *json_find_element (JsonNode *array, int index);
JsonNode *json_find_member (JsonNode *object, const char *key);
JsonNode *json_first_child (const JsonNode *node);
#define json_foreach(i, object_or_array) \
for ((i) = json_first_child(object_or_array); \
(i) != NULL; \
(i) = (i)->next)
/*** Construction and manipulation ***/
JsonNode *json_mknull(void);
JsonNode *json_mkbool(bool b);
JsonNode *json_mkstring(const char *s);
JsonNode *json_mknumber(double n);
JsonNode *json_mkarray(void);
JsonNode *json_mkobject(void);
void json_append_element(JsonNode *array, JsonNode *element);
void json_prepend_element(JsonNode *array, JsonNode *element);
void json_append_member(JsonNode *object, const char *key, JsonNode *value);
void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);
void json_remove_from_parent(JsonNode *node);
/*** Debugging ***/
/*
* Look for structure and encoding problems in a JsonNode or its descendents.
*
* If a problem is detected, return false, writing a description of the problem
* to errmsg (unless errmsg is NULL).
*/
bool json_check(const JsonNode *node, char errmsg[256]);
// Added by Lea Anthony 28/11/2020
int json_array_length(JsonNode *array);
#endif

View File

@@ -1,885 +0,0 @@
//
// Created by Lea Anthony on 6/1/21.
//
#include "ffenestri_darwin.h"
#include "menu_darwin.h"
#include "contextmenus_darwin.h"
#include "common.h"
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
Menu* NewMenu(JsonNode *menuData) {
Menu *result = malloc(sizeof(Menu));
result->processedMenu = menuData;
// No title by default
result->title = "";
// Initialise menuCallbackDataCache
vec_init(&result->callbackDataCache);
// Allocate MenuItem Map
if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) {
ABORT("[NewMenu] Not enough memory to allocate menuItemMap!");
}
// Allocate the Radio Group Map
if( 0 != hashmap_create((const unsigned)4, &result->radioGroupMap)) {
ABORT("[NewMenu] Not enough memory to allocate radioGroupMap!");
}
// Init other members
result->menu = NULL;
result->parentData = NULL;
return result;
}
Menu* NewApplicationMenu(const char *menuAsJSON) {
// Parse the menu json
JsonNode *processedMenu = json_decode(menuAsJSON);
if( processedMenu == NULL ) {
// Parse error!
ABORT("Unable to parse Menu JSON: %s", menuAsJSON);
}
Menu *result = NewMenu(processedMenu);
result->menuType = ApplicationMenuType;
return result;
}
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType) {
MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData));
result->menu = menu;
result->menuID = menuID;
result->menuItem = menuItem;
result->menuItemType = menuItemType;
// Store reference to this so we can destroy later
vec_push(&menu->callbackDataCache, result);
return result;
}
void DeleteMenu(Menu *menu) {
// Free menu item hashmap
hashmap_destroy(&menu->menuItemMap);
// Free radio group members
if( hashmap_num_entries(&menu->radioGroupMap) > 0 ) {
if (0 != hashmap_iterate_pairs(&menu->radioGroupMap, freeHashmapItem, NULL)) {
ABORT("[DeleteMenu] Failed to release radioGroupMap entries!");
}
}
// Free radio groups hashmap
hashmap_destroy(&menu->radioGroupMap);
// Free up the processed menu memory
if (menu->processedMenu != NULL) {
json_delete(menu->processedMenu);
menu->processedMenu = NULL;
}
// Release the vector memory
vec_deinit(&menu->callbackDataCache);
// Free nsmenu if we have it
if ( menu->menu != NULL ) {
msg(menu->menu, s("release"));
}
free(menu);
}
// Creates a JSON message for the given menuItemID and data
const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID) {
JsonNode *jsonObject = json_mkobject();
if (menuItemID == NULL ) {
ABORT("Item ID NULL for menu!!\n");
}
json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID));
json_append_member(jsonObject, "menuType", json_mkstring(MenuTypeAsString[(int)menuType]));
if (data != NULL) {
json_append_member(jsonObject, "data", json_mkstring(data));
}
if (parentID != NULL) {
json_append_member(jsonObject, "parentID", json_mkstring(parentID));
}
const char *payload = json_encode(jsonObject);
json_delete(jsonObject);
const char *result = concat("MC", payload);
MEMFREE(payload);
return result;
}
// Callback for text menu items
void menuItemCallback(id self, SEL cmd, id sender) {
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
const char *message;
// Update checkbox / radio item
if( callbackData->menuItemType == Checkbox) {
// Toggle state
bool state = msg(callbackData->menuItem, s("state"));
msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
} else if( callbackData->menuItemType == Radio ) {
// Check the menu items' current state
bool selected = msg(callbackData->menuItem, s("state"));
// If it's already selected, exit early
if (selected) return;
// Get this item's radio group members and turn them off
id *members = (id*)hashmap_get(&(callbackData->menu->radioGroupMap), (char*)callbackData->menuID, strlen(callbackData->menuID));
// Uncheck all members of the group
id thisMember = members[0];
int count = 0;
while(thisMember != NULL) {
msg(thisMember, s("setState:"), NSControlStateValueOff);
count = count + 1;
thisMember = members[count];
}
// check the selected menu item
msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
}
const char *menuID = callbackData->menuID;
const char *data = NULL;
enum MenuType menuType = callbackData->menu->menuType;
const char *parentID = NULL;
// Generate message to send to backend
if( menuType == ContextMenuType ) {
// Get the context menu data from the menu
ContextMenu* contextMenu = (ContextMenu*) callbackData->menu->parentData;
data = contextMenu->contextMenuData;
parentID = contextMenu->ID;
} else if ( menuType == TrayMenuType ) {
parentID = (const char*) callbackData->menu->parentData;
}
message = createMenuClickedMessage(menuID, data, menuType, parentID);
// Notify the backend
messageFromWindowCallback(message);
MEMFREE(message);
}
id processAcceleratorKey(const char *key) {
// Guard against no accelerator key
if( key == NULL ) {
return str("");
}
if( STREQ(key, "Backspace") ) {
return strunicode(0x0008);
}
if( STREQ(key, "Tab") ) {
return strunicode(0x0009);
}
if( STREQ(key, "Return") ) {
return strunicode(0x000d);
}
if( STREQ(key, "Escape") ) {
return strunicode(0x001b);
}
if( STREQ(key, "Left") ) {
return strunicode(0x001c);
}
if( STREQ(key, "Right") ) {
return strunicode(0x001d);
}
if( STREQ(key, "Up") ) {
return strunicode(0x001e);
}
if( STREQ(key, "Down") ) {
return strunicode(0x001f);
}
if( STREQ(key, "Space") ) {
return strunicode(0x0020);
}
if( STREQ(key, "Delete") ) {
return strunicode(0x007f);
}
if( STREQ(key, "Home") ) {
return strunicode(0x2196);
}
if( STREQ(key, "End") ) {
return strunicode(0x2198);
}
if( STREQ(key, "Page Up") ) {
return strunicode(0x21de);
}
if( STREQ(key, "Page Down") ) {
return strunicode(0x21df);
}
if( STREQ(key, "F1") ) {
return strunicode(0xf704);
}
if( STREQ(key, "F2") ) {
return strunicode(0xf705);
}
if( STREQ(key, "F3") ) {
return strunicode(0xf706);
}
if( STREQ(key, "F4") ) {
return strunicode(0xf707);
}
if( STREQ(key, "F5") ) {
return strunicode(0xf708);
}
if( STREQ(key, "F6") ) {
return strunicode(0xf709);
}
if( STREQ(key, "F7") ) {
return strunicode(0xf70a);
}
if( STREQ(key, "F8") ) {
return strunicode(0xf70b);
}
if( STREQ(key, "F9") ) {
return strunicode(0xf70c);
}
if( STREQ(key, "F10") ) {
return strunicode(0xf70d);
}
if( STREQ(key, "F11") ) {
return strunicode(0xf70e);
}
if( STREQ(key, "F12") ) {
return strunicode(0xf70f);
}
if( STREQ(key, "F13") ) {
return strunicode(0xf710);
}
if( STREQ(key, "F14") ) {
return strunicode(0xf711);
}
if( STREQ(key, "F15") ) {
return strunicode(0xf712);
}
if( STREQ(key, "F16") ) {
return strunicode(0xf713);
}
if( STREQ(key, "F17") ) {
return strunicode(0xf714);
}
if( STREQ(key, "F18") ) {
return strunicode(0xf715);
}
if( STREQ(key, "F19") ) {
return strunicode(0xf716);
}
if( STREQ(key, "F20") ) {
return strunicode(0xf717);
}
if( STREQ(key, "F21") ) {
return strunicode(0xf718);
}
if( STREQ(key, "F22") ) {
return strunicode(0xf719);
}
if( STREQ(key, "F23") ) {
return strunicode(0xf71a);
}
if( STREQ(key, "F24") ) {
return strunicode(0xf71b);
}
if( STREQ(key, "F25") ) {
return strunicode(0xf71c);
}
if( STREQ(key, "F26") ) {
return strunicode(0xf71d);
}
if( STREQ(key, "F27") ) {
return strunicode(0xf71e);
}
if( STREQ(key, "F28") ) {
return strunicode(0xf71f);
}
if( STREQ(key, "F29") ) {
return strunicode(0xf720);
}
if( STREQ(key, "F30") ) {
return strunicode(0xf721);
}
if( STREQ(key, "F31") ) {
return strunicode(0xf722);
}
if( STREQ(key, "F32") ) {
return strunicode(0xf723);
}
if( STREQ(key, "F33") ) {
return strunicode(0xf724);
}
if( STREQ(key, "F34") ) {
return strunicode(0xf725);
}
if( STREQ(key, "F35") ) {
return strunicode(0xf726);
}
// if( STREQ(key, "Insert") ) {
// return strunicode(0xf727);
// }
// if( STREQ(key, "PrintScreen") ) {
// return strunicode(0xf72e);
// }
// if( STREQ(key, "ScrollLock") ) {
// return strunicode(0xf72f);
// }
if( STREQ(key, "NumLock") ) {
return strunicode(0xf739);
}
return str(key);
}
void addSeparator(id menu) {
id item = msg(c("NSMenuItem"), s("separatorItem"));
msg(menu, s("addItem:"), item);
}
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
id item = ALLOC("NSMenuItem");
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
return item;
}
id createMenuItem(id title, const char *action, const char *key) {
id item = ALLOC("NSMenuItem");
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
msg(item, s("autorelease"));
return item;
}
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
id item = createMenuItem(str(title), action, key);
msg(item, s("setEnabled:"), !disabled);
msg(menu, s("addItem:"), item);
return item;
}
id createMenu(id title) {
id menu = ALLOC("NSMenu");
msg(menu, s("initWithTitle:"), title);
msg(menu, s("setAutoenablesItems:"), NO);
// msg(menu, s("autorelease"));
return menu;
}
void createDefaultAppMenu(id parentMenu) {
// App Menu
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
id appMenu = createMenu(appName);
msg(appMenuItem, s("setSubmenu:"), appMenu);
msg(parentMenu, s("addItem:"), appMenuItem);
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
id item = createMenuItem(title, "hide:", "h");
msg(appMenu, s("addItem:"), item);
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
addSeparator(appMenu);
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
item = createMenuItem(title, "terminate:", "q");
msg(appMenu, s("addItem:"), item);
}
void createDefaultEditMenu(id parentMenu) {
// Edit Menu
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
id editMenu = createMenu(str("Edit"));
msg(editMenuItem, s("setSubmenu:"), editMenu);
msg(parentMenu, s("addItem:"), editMenuItem);
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
addSeparator(editMenu);
addMenuItem(editMenu, "Cut", "cut:", "x", FALSE);
addMenuItem(editMenu, "Copy", "copy:", "c", FALSE);
addMenuItem(editMenu, "Paste", "paste:", "v", FALSE);
addMenuItem(editMenu, "Select All", "selectAll:", "a", FALSE);
}
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
const char *roleName = item->string_;
if ( STREQ(roleName, "appMenu") ) {
createDefaultAppMenu(parentMenu);
return;
}
if ( STREQ(roleName, "editMenu")) {
createDefaultEditMenu(parentMenu);
return;
}
if ( STREQ(roleName, "hide")) {
addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE);
return;
}
if ( STREQ(roleName, "hideothers")) {
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
return;
}
if ( STREQ(roleName, "unhide")) {
addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE);
return;
}
if ( STREQ(roleName, "front")) {
addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE);
return;
}
if ( STREQ(roleName, "undo")) {
addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE);
return;
}
if ( STREQ(roleName, "redo")) {
addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE);
return;
}
if ( STREQ(roleName, "cut")) {
addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE);
return;
}
if ( STREQ(roleName, "copy")) {
addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE);
return;
}
if ( STREQ(roleName, "paste")) {
addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE);
return;
}
if ( STREQ(roleName, "delete")) {
addMenuItem(parentMenu, "Delete", "delete:", "", FALSE);
return;
}
if( STREQ(roleName, "pasteandmatchstyle")) {
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
}
if ( STREQ(roleName, "selectall")) {
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
return;
}
if ( STREQ(roleName, "minimize")) {
addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE);
return;
}
if ( STREQ(roleName, "zoom")) {
addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE);
return;
}
if ( STREQ(roleName, "quit")) {
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
return;
}
if ( STREQ(roleName, "togglefullscreen")) {
addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE);
return;
}
}
// This converts a string array of modifiers into the
// equivalent MacOS Modifier Flags
unsigned long parseModifiers(const char **modifiers) {
// Our result is a modifier flag list
unsigned long result = 0;
const char *thisModifier = modifiers[0];
int count = 0;
while( thisModifier != NULL ) {
// Determine flags
if( STREQ(thisModifier, "CmdOrCtrl") ) {
result |= NSEventModifierFlagCommand;
}
if( STREQ(thisModifier, "OptionOrAlt") ) {
result |= NSEventModifierFlagOption;
}
if( STREQ(thisModifier, "Shift") ) {
result |= NSEventModifierFlagShift;
}
if( STREQ(thisModifier, "Super") ) {
result |= NSEventModifierFlagCommand;
}
if( STREQ(thisModifier, "Control") ) {
result |= NSEventModifierFlagControl;
}
count++;
thisModifier = modifiers[count];
}
return result;
}
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey) {
id item = ALLOC("NSMenuItem");
// Store the item in the menu item map
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg(parentmenu, s("addItem:"), item);
return item;
}
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key) {
id item = ALLOC("NSMenuItem");
// Store the item in the menu item map
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg(parentmenu, s("addItem:"), item);
return item;
}
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
}
// Process image
if( image != NULL && strlen(image) > 0) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
msg(item, s("setImage:"), nsimage);
}
// Process Menu Item attributes
id dictionary = ALLOC_INIT("NSMutableDictionary");
// Process font
id font;
CGFloat fontSizeFloat = (CGFloat)fontSize;
// Check if valid
id fontNameAsNSString = str(fontName);
id fontsOnSystem = msg(msg(c("NSFontManager"), s("sharedFontManager")), s("availableFonts"));
bool valid = msg(fontsOnSystem, s("containsObject:"), fontNameAsNSString);
if( valid ) {
font = msg(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
} else {
bool supportsMonospacedDigitSystemFont = (bool) msg(c("NSFont"), s("respondsToSelector:"), s("monospacedDigitSystemFontOfSize:weight:"));
if( supportsMonospacedDigitSystemFont ) {
font = msg(c("NSFont"), s("monospacedDigitSystemFontOfSize:weight:"), fontSizeFloat, NSFontWeightRegular);
} else {
font = msg(c("NSFont"), s("menuFontOfSize:"), fontSizeFloat);
}
}
// Add font to dictionary
msg(dictionary, s("setObject:forKey:"), font, lookupStringConstant(str("NSFontAttributeName")));
// Add offset to dictionary
id offset = msg(c("NSNumber"), s("numberWithFloat:"), 0.0);
msg(dictionary, s("setObject:forKey:"), offset, lookupStringConstant(str("NSBaselineOffsetAttributeName")));
// RGBA
if( RGBA != NULL && strlen(RGBA) > 0) {
unsigned short r, g, b, a;
// white by default
r = g = b = a = 255;
int count = sscanf(RGBA, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
if (count > 0) {
id colour = msg(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(float)r / 255.0,
(float)g / 255.0,
(float)b / 255.0,
(float)a / 255.0);
msg(dictionary, s("setObject:forKey:"), colour, lookupStringConstant(str("NSForegroundColorAttributeName")));
msg(colour, s("release"));
}
}
id attributedString = ALLOC("NSMutableAttributedString");
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
msg(dictionary, s("release"));
msg(item, s("setAttributedTitle:"), attributedString);
msg(attributedString, s("autorelease"));
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
// Process modifiers
if( modifiers != NULL ) {
unsigned long modifierFlags = parseModifiers(modifiers);
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
}
msg(parentMenu, s("addItem:"), item);
return item;
}
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
// Check if this item is hidden and if so, exit early!
bool hidden = false;
getJSONBool(item, "Hidden", &hidden);
if( hidden ) {
return;
}
// Get the role
JsonNode *role = json_find_member(item, "Role");
if( role != NULL ) {
processMenuRole(menu, parentMenu, role);
return;
}
// Check if this is a submenu
JsonNode *submenu = json_find_member(item, "SubMenu");
if( submenu != NULL ) {
// Get the label
JsonNode *menuNameNode = json_find_member(item, "Label");
const char *name = "";
if ( menuNameNode != NULL) {
name = menuNameNode->string_;
}
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
id thisMenu = createMenu(str(name));
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
msg(parentMenu, s("addItem:"), thisMenuItem);
JsonNode *submenuItems = json_find_member(submenu, "Items");
// If we have no items, just return
if ( submenuItems == NULL ) {
return;
}
// Loop over submenu items
JsonNode *item;
json_foreach(item, submenuItems) {
// Get item label
processMenuItem(menu, thisMenu, item);
}
return;
}
// This is a user menu. Get the common data
// Get the label
const char *label = getJSONString(item, "Label");
if ( label == NULL) {
label = "(empty)";
}
const char *menuid = getJSONString(item, "ID");
if ( menuid == NULL) {
menuid = "";
}
bool disabled = false;
getJSONBool(item, "Disabled", &disabled);
// Get the Accelerator
JsonNode *accelerator = json_find_member(item, "Accelerator");
const char *acceleratorkey = NULL;
const char **modifiers = NULL;
const char *tooltip = getJSONString(item, "Tooltip");
const char *image = getJSONString(item, "Image");
const char *fontName = getJSONString(item, "FontName");
const char *RGBA = getJSONString(item, "RGBA");
int fontSize = 12;
getJSONInt(item, "FontSize", &fontSize);
// If we have an accelerator
if( accelerator != NULL ) {
// Get the key
acceleratorkey = getJSONString(accelerator, "Key");
// Check if there are modifiers
JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
if ( modifiersList != NULL ) {
// Allocate an array of strings
int noOfModifiers = json_array_length(modifiersList);
// Do we have any?
if (noOfModifiers > 0) {
modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
JsonNode *modifier;
int count = 0;
// Iterate the modifiers and save a reference to them in our new array
json_foreach(modifier, modifiersList) {
// Get modifier name
modifiers[count] = modifier->string_;
count++;
}
// Null terminate the modifier list
modifiers[count] = NULL;
}
}
}
// Get the Type
JsonNode *type = json_find_member(item, "Type");
if( type != NULL ) {
if( STREQ(type->string_, "Text")) {
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA);
}
else if ( STREQ(type->string_, "Separator")) {
addSeparator(parentMenu);
}
else if ( STREQ(type->string_, "Checkbox")) {
// Get checked state
bool checked = false;
getJSONBool(item, "Checked", &checked);
processCheckboxMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
}
else if ( STREQ(type->string_, "Radio")) {
// Get checked state
bool checked = false;
getJSONBool(item, "Checked", &checked);
processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
}
}
if ( modifiers != NULL ) {
free(modifiers);
}
return;
}
void processMenuData(Menu *menu, JsonNode *menuData) {
JsonNode *items = json_find_member(menuData, "Items");
if( items == NULL ) {
// Parse error!
ABORT("Unable to find 'Items' in menu JSON!");
}
// Iterate items
JsonNode *item;
json_foreach(item, items) {
// Process each menu item
processMenuItem(menu, menu->menu, item);
}
}
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) {
int groupLength;
getJSONInt(radioGroup, "Length", &groupLength);
JsonNode *members = json_find_member(radioGroup, "Members");
JsonNode *member;
// Allocate array
size_t arrayLength = sizeof(id)*(groupLength+1);
id memberList[arrayLength];
// Build the radio group items
int count=0;
json_foreach(member, members) {
// Get menu by id
id menuItem = (id)hashmap_get(&menu->menuItemMap, (char*)member->string_, strlen(member->string_));
// Save Member
memberList[count] = menuItem;
count = count + 1;
}
// Null terminate array
memberList[groupLength] = 0;
// Store the members
json_foreach(member, members) {
// Copy the memberList
char *newMemberList = (char *)malloc(arrayLength);
memcpy(newMemberList, memberList, arrayLength);
// add group to each member of group
hashmap_put(&menu->radioGroupMap, member->string_, strlen(member->string_), newMemberList);
}
}
id GetMenu(Menu *menu) {
// Pull out the menu data
JsonNode *menuData = json_find_member(menu->processedMenu, "Menu");
if( menuData == NULL ) {
ABORT("Unable to find Menu data: %s", menu->processedMenu);
}
menu->menu = createMenu(str(""));
// Process the menu data
processMenuData(menu, menuData);
// Create the radiogroup cache
JsonNode *radioGroups = json_find_member(menu->processedMenu, "RadioGroups");
if( radioGroups == NULL ) {
// Parse error!
ABORT("Unable to find RadioGroups data: %s", menu->processedMenu);
}
// Iterate radio groups
JsonNode *radioGroup;
json_foreach(radioGroup, radioGroups) {
// Get item label
processRadioGroupJSON(menu, radioGroup);
}
return menu->menu;
}

View File

@@ -1,114 +0,0 @@
//
// Created by Lea Anthony on 6/1/21.
//
#ifndef MENU_DARWIN_H
#define MENU_DARWIN_H
#include "common.h"
#include "ffenestri_darwin.h"
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2};
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
static const char *MenuTypeAsString[] = {
"ApplicationMenu", "ContextMenu", "TrayMenu",
};
typedef struct _NSRange {
unsigned long location;
unsigned long length;
} NSRange;
#define NSFontWeightUltraLight -0.8
#define NSFontWeightThin -0.6
#define NSFontWeightLight -0.4
#define NSFontWeightRegular 0.0
#define NSFontWeightMedium 0.23
#define NSFontWeightSemibold 0.3
#define NSFontWeightBold 0.4
#define NSFontWeightHeavy 0.56
#define NSFontWeightBlack 0.62
extern void messageFromWindowCallback(const char *);
typedef struct {
const char *title;
/*** Internal ***/
// The decoded version of the Menu JSON
JsonNode *processedMenu;
struct hashmap_s menuItemMap;
struct hashmap_s radioGroupMap;
// Vector to keep track of callback data memory
vec_void_t callbackDataCache;
// The NSMenu for this menu
id menu;
// The parent data, eg ContextMenuStore or Tray
void *parentData;
// The commands for the menu callbacks
const char *callbackCommand;
// This indicates if we are an Application Menu, tray menu or context menu
enum MenuType menuType;
} Menu;
typedef struct {
id menuItem;
Menu *menu;
const char *menuID;
enum MenuItemType menuItemType;
} MenuItemCallbackData;
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
Menu* NewMenu(JsonNode *menuData);
Menu* NewApplicationMenu(const char *menuAsJSON);
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType);
void DeleteMenu(Menu *menu);
// Creates a JSON message for the given menuItemID and data
const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID);
// Callback for text menu items
void menuItemCallback(id self, SEL cmd, id sender);
id processAcceleratorKey(const char *key);
void addSeparator(id menu);
id createMenuItemNoAutorelease( id title, const char *action, const char *key);
id createMenuItem(id title, const char *action, const char *key);
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled);
id createMenu(id title);
void createDefaultAppMenu(id parentMenu);
void createDefaultEditMenu(id parentMenu);
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item);
// This converts a string array of modifiers into the
// equivalent MacOS Modifier Flags
unsigned long parseModifiers(const char **modifiers);
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey);
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA);
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
void processMenuData(Menu *menu, JsonNode *menuData);
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
id GetMenu(Menu *menu);
#endif //ASSETS_C_MENU_DARWIN_H

File diff suppressed because one or more lines are too long

View File

@@ -1,202 +0,0 @@
//
// Created by Lea Anthony on 12/1/21.
//
#include "common.h"
#include "traymenu_darwin.h"
#include "trayicons.h"
// A cache for all our tray menu icons
// Global because it's a singleton
struct hashmap_s trayIconCache;
TrayMenu* NewTrayMenu(const char* menuJSON) {
TrayMenu* result = malloc(sizeof(TrayMenu));
/*
{"ID":"0","Label":"Test Tray Label","Icon":"","ProcessedMenu":{"Menu":{"Items":[{"ID":"0","Label":"Show Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0},{"ID":"1","Label":"Hide Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0},{"ID":"2","Label":"Minimise Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0},{"ID":"3","Label":"Unminimise Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0}]},"RadioGroups":null}}
*/
JsonNode* processedJSON = json_decode(menuJSON);
if( processedJSON == NULL ) {
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", menuJSON);
}
// Save reference to this json
result->processedJSON = processedJSON;
// TODO: Make this configurable
result->trayIconPosition = NSImageLeft;
result->ID = mustJSONString(processedJSON, "ID");
result->label = mustJSONString(processedJSON, "Label");
result->icon = mustJSONString(processedJSON, "Icon");
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
// Create the menu
result->menu = NewMenu(processedMenu);
// Init tray status bar item
result->statusbaritem = NULL;
// Set the menu type and store the tray ID in the parent data
result->menu->menuType = TrayMenuType;
result->menu->parentData = (void*) result->ID;
return result;
}
void DumpTrayMenu(TrayMenu* trayMenu) {
printf(" ['%s':%p] = { label: '%s', icon: '%s', menu: %p, statusbar: %p }\n", trayMenu->ID, trayMenu, trayMenu->label, trayMenu->icon, trayMenu->menu, trayMenu->statusbaritem );
}
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
// Exit early if NULL
if( trayMenu->label == NULL ) {
return;
}
// Update button label
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setTitle:"), str(label));
}
void UpdateTrayIcon(TrayMenu *trayMenu) {
// Exit early if NULL
if( trayMenu->icon == NULL ) {
return;
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
// Empty icon means remove it
if( STREMPTY(trayMenu->icon) ) {
// Remove image
msg(statusBarButton, s("setImage:"), NULL);
return;
}
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
msg(statusBarButton, s("setImage:"), trayImage);
}
void ShowTrayMenu(TrayMenu* trayMenu) {
// Create a status bar item if we don't have one
if( trayMenu->statusbaritem == NULL ) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg(trayMenu->statusbaritem, s("retain"));
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
// Update the icon if needed
UpdateTrayIcon(trayMenu);
// Update the label if needed
UpdateTrayLabel(trayMenu, trayMenu->label);
// Update the menu
id menu = GetMenu(trayMenu->menu);
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
}
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
// updated with the data from the new menu.
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
// Delete the old menu
DeleteMenu(currentMenu->menu);
// Set the new one
currentMenu->menu = newMenu->menu;
// Delete the old JSON
json_delete(currentMenu->processedJSON);
// Set the new JSON
currentMenu->processedJSON = newMenu->processedJSON;
// Copy the other data
currentMenu->ID = newMenu->ID;
currentMenu->label = newMenu->label;
currentMenu->trayIconPosition = newMenu->trayIconPosition;
currentMenu->icon = newMenu->icon;
}
void DeleteTrayMenu(TrayMenu* trayMenu) {
// printf("Freeing TrayMenu:\n");
// DumpTrayMenu(trayMenu);
// Delete the menu
DeleteMenu(trayMenu->menu);
// Free JSON
if (trayMenu->processedJSON != NULL ) {
json_delete(trayMenu->processedJSON);
}
// Free the status item
if ( trayMenu->statusbaritem != NULL ) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
msg(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
msg(trayMenu->statusbaritem, s("release"));
trayMenu->statusbaritem = NULL;
}
// Free the tray menu memory
MEMFREE(trayMenu);
}
void LoadTrayIcons() {
// Allocate the Tray Icons
if( 0 != hashmap_create((const unsigned)4, &trayIconCache)) {
// Couldn't allocate map
ABORT("Not enough memory to allocate trayIconCache!");
}
unsigned int count = 0;
while( 1 ) {
const unsigned char *name = trayIcons[count++];
if( name == 0x00 ) {
break;
}
const unsigned char *lengthAsString = trayIcons[count++];
if( name == 0x00 ) {
break;
}
const unsigned char *data = trayIcons[count++];
if( data == 0x00 ) {
break;
}
int length = atoi((const char *)lengthAsString);
// Create the icon and add to the hashmap
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
id trayImage = ALLOC("NSImage");
msg(trayImage, s("initWithData:"), imageData);
hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage);
}
}
void UnloadTrayIcons() {
// Release the tray cache images
if( hashmap_num_entries(&trayIconCache) > 0 ) {
if (0!=hashmap_iterate_pairs(&trayIconCache, releaseNSObject, NULL)) {
ABORT("failed to release hashmap entries!");
}
}
//Free radio groups hashmap
hashmap_destroy(&trayIconCache);
}

View File

@@ -1,38 +0,0 @@
//
// Created by Lea Anthony on 12/1/21.
//
#ifndef TRAYMENU_DARWIN_H
#define TRAYMENU_DARWIN_H
#include "common.h"
#include "menu_darwin.h"
typedef struct {
const char *label;
const char *icon;
const char *ID;
Menu* menu;
id statusbaritem;
int trayIconPosition;
JsonNode* processedJSON;
} TrayMenu;
TrayMenu* NewTrayMenu(const char *trayJSON);
void DumpTrayMenu(TrayMenu* trayMenu);
void ShowTrayMenu(TrayMenu* trayMenu);
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
void UpdateTrayIcon(TrayMenu *trayMenu);
void UpdateTrayLabel(TrayMenu *trayMenu, const char*);
void LoadTrayIcons();
void UnloadTrayIcons();
void DeleteTrayMenu(TrayMenu* trayMenu);
#endif //TRAYMENU_DARWIN_H

View File

@@ -1,133 +0,0 @@
//
// Created by Lea Anthony on 12/1/21.
//
#include "common.h"
#include "traymenustore_darwin.h"
#include "traymenu_darwin.h"
#include <stdlib.h>
TrayMenuStore* NewTrayMenuStore() {
TrayMenuStore* result = malloc(sizeof(TrayMenuStore));
// Allocate Tray Menu Store
if( 0 != hashmap_create((const unsigned)4, &result->trayMenuMap)) {
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
}
return result;
}
int dumpTrayMenu(void *const context, struct hashmap_element_s *const e) {
DumpTrayMenu(e->data);
return 0;
}
void DumpTrayMenuStore(TrayMenuStore* store) {
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
}
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON);
//TODO: check if there is already an entry for this menu
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
}
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
ShowTrayMenu(e->data);
// 0 to retain element, -1 to delete.
return 0;
}
void ShowTrayMenusInStore(TrayMenuStore* store) {
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
}
}
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
DeleteTrayMenu(e->data);
return -1;
}
void DeleteTrayMenuStore(TrayMenuStore *store) {
// Delete context menus
if (hashmap_num_entries(&store->trayMenuMap) > 0) {
if (0 != hashmap_iterate_pairs(&store->trayMenuMap, freeTrayMenu, NULL)) {
ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
}
}
// Destroy tray menu map
hashmap_destroy(&store->trayMenuMap);
}
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
// Get the current menu
return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
}
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
// Get the current menu
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
if (result == NULL ) {
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
}
return result;
}
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
// Parse the JSON
JsonNode *parsedUpdate = mustParseJSON(JSON);
// Get the data out
const char* ID = mustJSONString(parsedUpdate, "ID");
const char* Label = mustJSONString(parsedUpdate, "Label");
// Check we have this menu
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
UpdateTrayLabel(menu, Label);
}
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON);
// DumpTrayMenu(newMenu);
// Get the current menu
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
// If we don't have a menu, we create one
if ( currentMenu == NULL ) {
// Store the new menu
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
// Show it
ShowTrayMenu(newMenu);
return;
}
// DumpTrayMenu(currentMenu);
// Save the status bar reference
newMenu->statusbaritem = currentMenu->statusbaritem;
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
// Delete the current menu
DeleteMenu(currentMenu->menu);
currentMenu->menu = NULL;
// Free the tray menu memory
MEMFREE(currentMenu);
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
// Show the updated menu
ShowTrayMenu(newMenu);
}

View File

@@ -1,27 +0,0 @@
//
// Created by Lea Anthony on 7/1/21.
//
#ifndef TRAYMENUSTORE_DARWIN_H
#define TRAYMENUSTORE_DARWIN_H
typedef struct {
int dummy;
// This is our tray menu map
// It maps tray IDs to TrayMenu*
struct hashmap_s trayMenuMap;
} TrayMenuStore;
TrayMenuStore* NewTrayMenuStore();
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON);
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
void ShowTrayMenusInStore(TrayMenuStore* store);
void DeleteTrayMenuStore(TrayMenuStore* store);
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
#endif //TRAYMENUSTORE_DARWIN_H

View File

@@ -1,113 +0,0 @@
/**
* Copyright (c) 2014 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See LICENSE for details.
*/
#include "vec.h"
int vec_expand_(char **data, int *length, int *capacity, int memsz) {
if (*length + 1 > *capacity) {
void *ptr;
int n = (*capacity == 0) ? 1 : *capacity << 1;
ptr = realloc(*data, n * memsz);
if (ptr == NULL) return -1;
*data = ptr;
*capacity = n;
}
return 0;
}
int vec_reserve_(char **data, int *length, int *capacity, int memsz, int n) {
(void) length;
if (n > *capacity) {
void *ptr = realloc(*data, n * memsz);
if (ptr == NULL) return -1;
*data = ptr;
*capacity = n;
}
return 0;
}
int vec_reserve_po2_(
char **data, int *length, int *capacity, int memsz, int n
) {
int n2 = 1;
if (n == 0) return 0;
while (n2 < n) n2 <<= 1;
return vec_reserve_(data, length, capacity, memsz, n2);
}
int vec_compact_(char **data, int *length, int *capacity, int memsz) {
if (*length == 0) {
free(*data);
*data = NULL;
*capacity = 0;
return 0;
} else {
void *ptr;
int n = *length;
ptr = realloc(*data, n * memsz);
if (ptr == NULL) return -1;
*capacity = n;
*data = ptr;
}
return 0;
}
int vec_insert_(char **data, int *length, int *capacity, int memsz,
int idx
) {
int err = vec_expand_(data, length, capacity, memsz);
if (err) return err;
memmove(*data + (idx + 1) * memsz,
*data + idx * memsz,
(*length - idx) * memsz);
return 0;
}
void vec_splice_(char **data, int *length, int *capacity, int memsz,
int start, int count
) {
(void) capacity;
memmove(*data + start * memsz,
*data + (start + count) * memsz,
(*length - start - count) * memsz);
}
void vec_swapsplice_(char **data, int *length, int *capacity, int memsz,
int start, int count
) {
(void) capacity;
memmove(*data + start * memsz,
*data + (*length - count) * memsz,
count * memsz);
}
void vec_swap_(char **data, int *length, int *capacity, int memsz,
int idx1, int idx2
) {
unsigned char *a, *b, tmp;
int count;
(void) length;
(void) capacity;
if (idx1 == idx2) return;
a = (unsigned char*) *data + idx1 * memsz;
b = (unsigned char*) *data + idx2 * memsz;
count = memsz;
while (count--) {
tmp = *a;
*a = *b;
*b = tmp;
a++, b++;
}
}

View File

@@ -1,181 +0,0 @@
/**
* Copyright (c) 2014 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See LICENSE for details.
*/
#ifndef VEC_H
#define VEC_H
#include <stdlib.h>
#include <string.h>
#define VEC_VERSION "0.2.1"
#define vec_unpack_(v)\
(char**)&(v)->data, &(v)->length, &(v)->capacity, sizeof(*(v)->data)
#define vec_t(T)\
struct { T *data; int length, capacity; }
#define vec_init(v)\
memset((v), 0, sizeof(*(v)))
#define vec_deinit(v)\
( free((v)->data),\
vec_init(v) )
#define vec_push(v, val)\
( vec_expand_(vec_unpack_(v)) ? -1 :\
((v)->data[(v)->length++] = (val), 0), 0 )
#define vec_pop(v)\
(v)->data[--(v)->length]
#define vec_splice(v, start, count)\
( vec_splice_(vec_unpack_(v), start, count),\
(v)->length -= (count) )
#define vec_swapsplice(v, start, count)\
( vec_swapsplice_(vec_unpack_(v), start, count),\
(v)->length -= (count) )
#define vec_insert(v, idx, val)\
( vec_insert_(vec_unpack_(v), idx) ? -1 :\
((v)->data[idx] = (val), 0), (v)->length++, 0 )
#define vec_sort(v, fn)\
qsort((v)->data, (v)->length, sizeof(*(v)->data), fn)
#define vec_swap(v, idx1, idx2)\
vec_swap_(vec_unpack_(v), idx1, idx2)
#define vec_truncate(v, len)\
((v)->length = (len) < (v)->length ? (len) : (v)->length)
#define vec_clear(v)\
((v)->length = 0)
#define vec_first(v)\
(v)->data[0]
#define vec_last(v)\
(v)->data[(v)->length - 1]
#define vec_reserve(v, n)\
vec_reserve_(vec_unpack_(v), n)
#define vec_compact(v)\
vec_compact_(vec_unpack_(v))
#define vec_pusharr(v, arr, count)\
do {\
int i__, n__ = (count);\
if (vec_reserve_po2_(vec_unpack_(v), (v)->length + n__) != 0) break;\
for (i__ = 0; i__ < n__; i__++) {\
(v)->data[(v)->length++] = (arr)[i__];\
}\
} while (0)
#define vec_extend(v, v2)\
vec_pusharr((v), (v2)->data, (v2)->length)
#define vec_find(v, val, idx)\
do {\
for ((idx) = 0; (idx) < (v)->length; (idx)++) {\
if ((v)->data[(idx)] == (val)) break;\
}\
if ((idx) == (v)->length) (idx) = -1;\
} while (0)
#define vec_remove(v, val)\
do {\
int idx__;\
vec_find(v, val, idx__);\
if (idx__ != -1) vec_splice(v, idx__, 1);\
} while (0)
#define vec_reverse(v)\
do {\
int i__ = (v)->length / 2;\
while (i__--) {\
vec_swap((v), i__, (v)->length - (i__ + 1));\
}\
} while (0)
#define vec_foreach(v, var, iter)\
if ( (v)->length > 0 )\
for ( (iter) = 0;\
(iter) < (v)->length && (((var) = (v)->data[(iter)]), 1);\
++(iter))
#define vec_foreach_rev(v, var, iter)\
if ( (v)->length > 0 )\
for ( (iter) = (v)->length - 1;\
(iter) >= 0 && (((var) = (v)->data[(iter)]), 1);\
--(iter))
#define vec_foreach_ptr(v, var, iter)\
if ( (v)->length > 0 )\
for ( (iter) = 0;\
(iter) < (v)->length && (((var) = &(v)->data[(iter)]), 1);\
++(iter))
#define vec_foreach_ptr_rev(v, var, iter)\
if ( (v)->length > 0 )\
for ( (iter) = (v)->length - 1;\
(iter) >= 0 && (((var) = &(v)->data[(iter)]), 1);\
--(iter))
int vec_expand_(char **data, int *length, int *capacity, int memsz);
int vec_reserve_(char **data, int *length, int *capacity, int memsz, int n);
int vec_reserve_po2_(char **data, int *length, int *capacity, int memsz,
int n);
int vec_compact_(char **data, int *length, int *capacity, int memsz);
int vec_insert_(char **data, int *length, int *capacity, int memsz,
int idx);
void vec_splice_(char **data, int *length, int *capacity, int memsz,
int start, int count);
void vec_swapsplice_(char **data, int *length, int *capacity, int memsz,
int start, int count);
void vec_swap_(char **data, int *length, int *capacity, int memsz,
int idx1, int idx2);
typedef vec_t(void*) vec_void_t;
typedef vec_t(char*) vec_str_t;
typedef vec_t(int) vec_int_t;
typedef vec_t(char) vec_char_t;
typedef vec_t(float) vec_float_t;
typedef vec_t(double) vec_double_t;
#endif

View File

@@ -1,279 +0,0 @@
package fs
import (
"crypto/md5"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"unsafe"
"github.com/leaanthony/slicer"
)
// LocalDirectory gets the caller's file directory
// Equivalent to node's __DIRNAME
func LocalDirectory() string {
_, thisFile, _, _ := runtime.Caller(1)
return filepath.Dir(thisFile)
}
// RelativeToCwd returns an absolute path based on the cwd
// and the given relative path
func RelativeToCwd(relativePath string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(cwd, relativePath), nil
}
// Mkdir will create the given directory
func Mkdir(dirname string) error {
return os.Mkdir(dirname, 0755)
}
// MkDirs creates the given nested directories.
// Returns error on failure
func MkDirs(fullPath string, mode ...os.FileMode) error {
var perms os.FileMode
perms = 0700
if len(mode) == 1 {
perms = mode[0]
}
return os.MkdirAll(fullPath, perms)
}
// MoveFile attempts to move the source file to the target
// Target is a fully qualified path to a file *name*, not a
// directory
func MoveFile(source string, target string) error {
return os.Rename(source, target)
}
// DeleteFile will delete the given file
func DeleteFile(filename string) error {
return os.Remove(filename)
}
// CopyFile from source to target
func CopyFile(source string, target string) error {
s, err := os.Open(source)
if err != nil {
return err
}
defer s.Close()
d, err := os.Create(target)
if err != nil {
return err
}
if _, err := io.Copy(d, s); err != nil {
d.Close()
return err
}
return d.Close()
}
// DirExists - Returns true if the given path resolves to a directory on the filesystem
func DirExists(path string) bool {
fi, err := os.Lstat(path)
if err != nil {
return false
}
return fi.Mode().IsDir()
}
// FileExists returns a boolean value indicating whether
// the given file exists
func FileExists(path string) bool {
fi, err := os.Lstat(path)
if err != nil {
return false
}
return fi.Mode().IsRegular()
}
// RelativePath returns a qualified path created by joining the
// directory of the calling file and the given relative path.
//
// Example: RelativePath("..") in *this* file would give you '/path/to/wails2/v2/internal`
func RelativePath(relativepath string, optionalpaths ...string) string {
_, thisFile, _, _ := runtime.Caller(1)
localDir := filepath.Dir(thisFile)
// If we have optional paths, join them to the relativepath
if len(optionalpaths) > 0 {
paths := []string{relativepath}
paths = append(paths, optionalpaths...)
relativepath = filepath.Join(paths...)
}
result, err := filepath.Abs(filepath.Join(localDir, relativepath))
if err != nil {
// I'm allowing this for 1 reason only: It's fatal if the path
// supplied is wrong as it's only used internally in Wails. If we get
// that path wrong, we should know about it immediately. The other reason is
// that it cuts down a ton of unnecassary error handling.
panic(err)
}
return result
}
// MustLoadString attempts to load a string and will abort with a fatal message if
// something goes wrong
func MustLoadString(filename string) string {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Printf("FATAL: Unable to load file '%s': %s\n", filename, err.Error())
os.Exit(1)
}
return *(*string)(unsafe.Pointer(&data))
}
// MD5File returns the md5sum of the given file
func MD5File(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close()
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
// MustMD5File will call MD5File and abort the program on error
func MustMD5File(filename string) string {
result, err := MD5File(filename)
if err != nil {
println("FATAL: Unable to MD5Sum file:", err.Error())
os.Exit(1)
}
return result
}
// MustWriteString will attempt to write the given data to the given filename
// It will abort the program in the event of a failure
func MustWriteString(filename string, data string) {
err := ioutil.WriteFile(filename, []byte(data), 0755)
if err != nil {
fatal("Unable to write file", filename, ":", err.Error())
os.Exit(1)
}
}
// fatal will print the optional messages and die
func fatal(message ...string) {
if len(message) > 0 {
print("FATAL:")
for text := range message {
print(text)
}
}
os.Exit(1)
}
// GetSubdirectories returns a list of subdirectories for the given root directory
func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
var result slicer.StringSlicer
// Iterate root dir
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// If we have a directory, save it
if info.IsDir() {
result.Add(path)
}
return nil
})
return &result, err
}
func DirIsEmpty(dir string) (bool, error) {
if !DirExists(dir) {
return false, fmt.Errorf("DirIsEmpty called with a non-existant directory: %s", dir)
}
// CREDIT: https://stackoverflow.com/a/30708914/8325411
f, err := os.Open(dir)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1) // Or f.Readdir(1)
if err == io.EOF {
return true, nil
}
return false, err // Either not empty or error, suits both cases
}
// Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist, destination directory must *not* exist.
// Symlinks are ignored and skipped.
func CopyDir(src string, dst string) (err error) {
src = filepath.Clean(src)
dst = filepath.Clean(dst)
si, err := os.Stat(src)
if err != nil {
return err
}
if !si.IsDir() {
return fmt.Errorf("source is not a directory")
}
_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
return
}
if err == nil {
return fmt.Errorf("destination already exists")
}
err = os.MkdirAll(dst, si.Mode())
if err != nil {
return
}
entries, err := ioutil.ReadDir(src)
if err != nil {
return
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
err = CopyDir(srcPath, dstPath)
if err != nil {
return
}
} else {
// Skip symlinks.
if entry.Mode()&os.ModeSymlink != 0 {
continue
}
err = CopyFile(srcPath, dstPath)
if err != nil {
return
}
}
}
return
}

View File

@@ -1,31 +0,0 @@
package fs
import (
"os"
"path/filepath"
"testing"
"github.com/matryer/is"
)
func TestRelativePath(t *testing.T) {
is := is.New(t)
cwd, err := os.Getwd()
is.Equal(err, nil)
// Check current directory
actual := RelativePath(".")
is.Equal(actual, cwd)
// Check 2 parameters
actual = RelativePath("..", "fs")
is.Equal(actual, cwd)
// Check 3 parameters including filename
actual = RelativePath("..", "fs", "fs.go")
expected := filepath.Join(cwd, "fs.go")
is.Equal(actual, expected)
}

View File

@@ -1,103 +0,0 @@
package github
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sort"
"strings"
)
// GetVersionTags gets the list of tags on the Wails repo
// It returns a list of sorted tags in descending order
func GetVersionTags() ([]*SemanticVersion, error) {
result := []*SemanticVersion{}
var err error
resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/tags")
if err != nil {
return result, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return result, err
}
data := []map[string]interface{}{}
err = json.Unmarshal(body, &data)
if err != nil {
return result, err
}
// Convert tag data to Version structs
for _, tag := range data {
version := tag["name"].(string)
if !strings.HasPrefix(version, "v2") {
continue
}
semver, err := NewSemanticVersion(version)
if err != nil {
return result, err
}
result = append(result, semver)
}
// Reverse Sort
sort.Sort(sort.Reverse(SemverCollection(result)))
return result, err
}
// GetLatestStableRelease gets the latest stable release on GitHub
func GetLatestStableRelease() (result *SemanticVersion, err error) {
tags, err := GetVersionTags()
if err != nil {
return nil, err
}
for _, tag := range tags {
if tag.IsRelease() {
return tag, nil
}
}
return nil, fmt.Errorf("no release tag found")
}
// GetLatestPreRelease gets the latest prerelease on GitHub
func GetLatestPreRelease() (result *SemanticVersion, err error) {
tags, err := GetVersionTags()
if err != nil {
return nil, err
}
for _, tag := range tags {
if tag.IsPreRelease() {
return tag, nil
}
}
return nil, fmt.Errorf("no prerelease tag found")
}
// IsValidTag returns true if the given string is a valid tag
func IsValidTag(tagVersion string) (bool, error) {
if tagVersion[0] == 'v' {
tagVersion = tagVersion[1:]
}
tags, err := GetVersionTags()
if err != nil {
return false, err
}
for _, tag := range tags {
if tag.String() == tagVersion {
return true, nil
}
}
return false, nil
}

View File

@@ -1,106 +0,0 @@
package github
import (
"fmt"
"github.com/Masterminds/semver"
)
// SemanticVersion is a struct containing a semantic version
type SemanticVersion struct {
Version *semver.Version
}
// NewSemanticVersion creates a new SemanticVersion object with the given version string
func NewSemanticVersion(version string) (*SemanticVersion, error) {
semverVersion, err := semver.NewVersion(version)
if err != nil {
return nil, err
}
return &SemanticVersion{
Version: semverVersion,
}, nil
}
// IsRelease returns true if it's a release version
func (s *SemanticVersion) IsRelease() bool {
// Limit to v2
if s.Version.Major() != 2 {
return false
}
return len(s.Version.Prerelease()) == 0 && len(s.Version.Metadata()) == 0
}
// IsPreRelease returns true if it's a prerelease version
func (s *SemanticVersion) IsPreRelease() bool {
// Limit to v1
if s.Version.Major() != 2 {
return false
}
return len(s.Version.Prerelease()) > 0
}
func (s *SemanticVersion) String() string {
return s.Version.String()
}
// IsGreaterThan returns true if this version is greater than the given version
func (s *SemanticVersion) IsGreaterThan(version *SemanticVersion) (bool, error) {
// Set up new constraint
constraint, err := semver.NewConstraint("> " + version.Version.String())
if err != nil {
return false, err
}
// Check if the desired one is greater than the requested on
success, msgs := constraint.Validate(s.Version)
if !success {
return false, msgs[0]
}
return true, nil
}
// IsGreaterThanOrEqual returns true if this version is greater than or equal the given version
func (s *SemanticVersion) IsGreaterThanOrEqual(version *SemanticVersion) (bool, error) {
// Set up new constraint
constraint, err := semver.NewConstraint(">= " + version.Version.String())
if err != nil {
return false, err
}
// Check if the desired one is greater than the requested on
success, msgs := constraint.Validate(s.Version)
if !success {
return false, msgs[0]
}
return true, nil
}
// MainVersion returns the main version of any version+prerelease+metadata
// EG: MainVersion("1.2.3-pre") => "1.2.3"
func (s *SemanticVersion) MainVersion() *SemanticVersion {
mainVersion := fmt.Sprintf("%d.%d.%d", s.Version.Major(), s.Version.Minor(), s.Version.Patch())
result, _ := NewSemanticVersion(mainVersion)
return result
}
// SemverCollection is a collection of SemanticVersion objects
type SemverCollection []*SemanticVersion
// Len returns the length of a collection. The number of Version instances
// on the slice.
func (c SemverCollection) Len() int {
return len(c)
}
// Less is needed for the sort interface to compare two Version objects on the
// slice. If checks if one is less than the other.
func (c SemverCollection) Less(i, j int) bool {
return c[i].Version.LessThan(c[j].Version)
}
// Swap is needed for the sort interface to replace the Version objects
// at two different positions in the slice.
func (c SemverCollection) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}

View File

@@ -1,127 +0,0 @@
package html
import (
"fmt"
"io/ioutil"
"log"
"net/url"
"path/filepath"
"regexp"
"strings"
"unsafe"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/js"
)
type assetTypes struct {
JS string
CSS string
FAVICON string
HTML string
}
// AssetTypes is an enum for the asset type keys
var AssetTypes *assetTypes = &assetTypes{
JS: "javascript",
CSS: "css",
FAVICON: "favicon",
HTML: "html",
}
// Asset describes an asset type and its path
type Asset struct {
Type string
Path string
Data string
}
// Load the asset from disk
func (a *Asset) Load(basedirectory string) error {
assetpath := filepath.Join(basedirectory, a.Path)
data, err := ioutil.ReadFile(assetpath)
if err != nil {
return err
}
a.Data = string(data)
return nil
}
// AsString returns the data as a READ ONLY string
func (a *Asset) AsString() string {
return a.Data
}
// AsCHexData processes the asset data so it may be used by C
func (a *Asset) AsCHexData() string {
// This will be our final string to hexify
dataString := a.Data
switch a.Type {
case AssetTypes.HTML:
// Escape HTML
var re = regexp.MustCompile(`\s{2,}`)
result := re.ReplaceAllString(a.Data, ``)
result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\r\n", "")
result = strings.ReplaceAll(result, "\n", "")
// Inject wailsloader code
result = strings.Replace(result, `</body>`, `<script id='wailsloader'>window.wailsloader = { html: true, runtime: false, userjs: false, usercss: false };var self=document.querySelector('#wailsloader');self.parentNode.removeChild(self);</script></body>`, 1)
url := url.URL{Path: result}
urlString := strings.ReplaceAll(url.String(), "/", "%2f")
// Save Data uRI string
dataString = "data:text/html;charset=utf-8," + urlString
case AssetTypes.CSS:
// Escape CSS data
var re = regexp.MustCompile(`\s{2,}`)
result := re.ReplaceAllString(a.Data, ``)
result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\r\n", "")
result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\t", "")
result = strings.ReplaceAll(result, `\`, `\\`)
result = strings.ReplaceAll(result, `"`, `\"`)
result = strings.ReplaceAll(result, `'`, `\'`)
result = strings.ReplaceAll(result, ` {`, `{`)
result = strings.ReplaceAll(result, `: `, `:`)
dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result)
case AssetTypes.JS:
m := minify.New()
m.AddFunc("application/javascript", js.Minify)
var err error
dataString, err = m.String("application/javascript", a.Data+";")
if err != nil {
log.Fatal(err)
}
a.Data = dataString
}
// Get byte data of the string
bytes := *(*[]byte)(unsafe.Pointer(&dataString))
// Create a strings builder
var cdata strings.Builder
// Set buffer size to 4k
cdata.Grow(4096)
// Convert each byte to hex
for _, b := range bytes {
cdata.WriteString(fmt.Sprintf("0x%x, ", b))
}
return cdata.String()
}
// Dump will output the asset to the terminal
func (a *Asset) Dump() {
fmt.Printf("{ Type: %s, Path: %s, Data: %+v }\n", a.Type, a.Path, a.Data[:10])
}

View File

@@ -1,215 +0,0 @@
package html
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/assetdb"
"golang.org/x/net/html"
)
// AssetBundle is a collection of Assets
type AssetBundle struct {
assets []*Asset
basedirectory string
}
// NewAssetBundle creates a new AssetBundle struct containing
// the given html and all the assets referenced by it
func NewAssetBundle(pathToHTML string) (*AssetBundle, error) {
// Create result
result := &AssetBundle{
basedirectory: filepath.Dir(pathToHTML),
}
err := result.loadAssets(pathToHTML)
if err != nil {
return nil, err
}
return result, nil
}
// loadAssets processes the given html file and loads in
// all referenced assets
func (a *AssetBundle) loadAssets(pathToHTML string) error {
// Save HTML
htmlAsset := &Asset{
Type: AssetTypes.HTML,
Path: filepath.Base(pathToHTML),
}
err := htmlAsset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, htmlAsset)
return a.processHTML(htmlAsset.AsString())
}
// Credit to: https://drstearns.github.io/tutorials/tokenizing/
func (a *AssetBundle) processHTML(htmldata string) error {
// Tokenize the html
buf := bytes.NewBufferString(htmldata)
tokenizer := html.NewTokenizer(buf)
paths := slicer.String()
for {
//get the next token type
tokenType := tokenizer.Next()
//if it's an error token, we either reached
//the end of the file, or the HTML was malformed
if tokenType == html.ErrorToken {
err := tokenizer.Err()
if err == io.EOF {
//end of the file, break out of the loop
break
}
//otherwise, there was an error tokenizing,
//which likely means the HTML was malformed.
//since this is a simple command-line utility,
//we can just use log.Fatalf() to report the error
//and exit the process with a non-zero status code
return tokenizer.Err()
}
//process the token according to the token type...
if tokenType == html.StartTagToken {
//get the token
token := tokenizer.Token()
//if the name of the element is "title"
if "link" == token.Data {
//the next token should be the page title
tokenType = tokenizer.Next()
//just make sure it's actually a text token
asset := &Asset{}
for _, attr := range token.Attr {
// Favicon
if attr.Key == "rel" && attr.Val == "icon" {
asset.Type = AssetTypes.FAVICON
}
if attr.Key == "href" {
asset.Path = attr.Val
}
// standard stylesheet
if attr.Key == "rel" && attr.Val == "stylesheet" {
asset.Type = AssetTypes.CSS
}
if attr.Key == "as" && attr.Val == "style" {
asset.Type = AssetTypes.CSS
}
if attr.Key == "as" && attr.Val == "script" {
asset.Type = AssetTypes.JS
}
}
// Ensure we don't include duplicates
if !paths.Contains(asset.Path) {
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
paths.Add(asset.Path)
}
}
if "script" == token.Data {
tokenType = tokenizer.Next()
//just make sure it's actually a text token
asset := &Asset{Type: AssetTypes.JS}
for _, attr := range token.Attr {
if attr.Key == "src" {
asset.Path = attr.Val
break
}
}
if !paths.Contains(asset.Path) {
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
paths.Add(asset.Path)
}
}
}
}
return nil
}
// WriteToCFile dumps all the assets to C files in the given directory
func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
// Write out the assets.c file
var cdata strings.Builder
// Write header
header := `// assets.h
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL.
// This file was auto-generated. DO NOT MODIFY.
`
cdata.WriteString(header)
// Loop over the Assets
var err error
assetVariables := slicer.String()
var variableName string
for index, asset := range a.assets {
// For desktop we ignore the favicon
if asset.Type == AssetTypes.FAVICON {
continue
}
variableName = fmt.Sprintf("%s%d", asset.Type, index)
assetCdata := fmt.Sprintf("const unsigned char %s[]={ %s0x00 };\n", variableName, asset.AsCHexData())
cdata.WriteString(assetCdata)
assetVariables.Add(variableName)
}
if assetVariables.Length() > 0 {
cdata.WriteString(fmt.Sprintf("\nconst unsigned char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
} else {
cdata.WriteString("\nconst unsigned char *assets[] = { 0x00 };")
}
// Save file
assetsFile := filepath.Join(targetDir, "assets.h")
err = ioutil.WriteFile(assetsFile, []byte(cdata.String()), 0600)
if err != nil {
return "", err
}
return assetsFile, nil
}
// ConvertToAssetDB returns an assetdb.AssetDB initialized with
// the items in the AssetBundle
func (a *AssetBundle) ConvertToAssetDB() (*assetdb.AssetDB, error) {
theassetdb := assetdb.NewAssetDB()
// Loop over the Assets
for _, asset := range a.assets {
theassetdb.AddAsset(asset.Path, []byte(asset.Data))
}
return theassetdb, nil
}
// Dump will output the assets to the terminal
func (a *AssetBundle) Dump() {
println("Assets:")
for _, asset := range a.assets {
asset.Dump()
}
}

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