Compare commits

..

147 Commits

Author SHA1 Message Date
Lea Anthony
52554009ce Merge pull request #807 from Baiyuetribe/v2-alpha
Add commonly used shortcut keys
2021-09-15 23:33:48 +10:00
Lea Anthony
9f89a4a58a Merge branch 'v2-alpha' into v2-alpha 2021-09-15 23:33:33 +10:00
Lea Anthony
5585d72c1c [v2] Fix wailsjs generation 2021-09-15 23:20:47 +10:00
Lea Anthony
7748cc2497 [windows] Support ctrl-a 2021-09-15 23:19:48 +10:00
佰阅部落
60f9a02143 Add commonly used shortcut keys
add ctral+A,arrowUp,left,down,right and del
2021-09-15 20:59:06 +08:00
Lea Anthony
899e522c74 [windows] Webview to handle ctrl-x/c/z/v. Foreward other accelerators to Window. 2021-09-15 20:30:14 +10:00
Lea Anthony
be87d73da8 [v2] go fmt + windowIsTranslucent 2021-09-15 20:30:14 +10:00
Travis McLane
00639677f5 fix Apple Silicon check 2021-09-14 12:19:22 -05:00
leaanthony
3c25a73ae8 The runtime was rebuilt 2021-09-14 10:24:40 +00:00
Lea Anthony
344103739f [v2] Fix JS builds 2021-09-14 20:24:14 +10:00
Lea Anthony
c2b8247fbb [v2] Fix type on runtime 2021-09-14 06:48:19 +10:00
leaanthony
03cdfad418 The runtime was rebuilt 2021-09-13 20:24:44 +00:00
Lea Anthony
3fe4c5455a Merge pull request #804 from misitebao/fix-js-runtime
Fix js runtime
2021-09-14 06:24:21 +10:00
misitebao
a6b95b23c9 fix: fix js runtime related issues 2021-09-13 21:56:09 +08:00
misitebao
2616d87922 fix: fix the BrowserOpenURL method signature in the ts declaration file 2021-09-13 21:54:18 +08:00
leaanthony
f983649bc6 The runtime was rebuilt 2021-09-13 10:49:07 +00:00
Lea Anthony
15ed02b78c [v2] More updates 2021-09-13 20:48:41 +10:00
Lea Anthony
2cb93b4b4b [v2] Tidy up typescript declaration 2021-09-13 20:45:36 +10:00
Lea Anthony
7e0ad801b0 Update runtime.yml 2021-09-13 20:38:33 +10:00
Lea Anthony
ccdfa9a8be [v2] Update runtime 2021-09-13 20:35:52 +10:00
Lea Anthony
85e04d8094 Merge pull request #801 from misitebao/796-support-browseropenurl
feat(runtime): add a method to open the link from the default browser(#796)
2021-09-13 20:22:10 +10:00
Lea Anthony
e7d4ec5836 Merge branch 'v2-alpha' into 796-support-browseropenurl 2021-09-13 20:21:59 +10:00
Lea Anthony
7fdbb0372c Update runtime.yml 2021-09-13 20:17:04 +10:00
Lea Anthony
72f6f08f11 Merge pull request #800 from wailsapp/test-update-runtime
[v2] Update Workflows + runtime
2021-09-13 20:14:35 +10:00
Lea Anthony
d16d5bdcd4 Update runtime.yml 2021-09-13 20:13:28 +10:00
Lea Anthony
22d0e4ff8f Update runtime.yml 2021-09-13 20:08:25 +10:00
Lea Anthony
c001a5f3cb Update runtime.yml 2021-09-13 20:05:50 +10:00
Lea Anthony
43ab0e084f [v2] Update Workflow 2021-09-13 20:04:08 +10:00
misitebao
954470250e feat: added a method to open the link in runtime(wailsapp#796) 2021-09-13 17:58:36 +08:00
Lea Anthony
5ab5246aa0 [v2] Update Workflows + runtime 2021-09-13 19:35:14 +10:00
Lea Anthony
abbbdda102 [v2] Update Workflows + runtime 2021-09-13 18:58:09 +10:00
Lea Anthony
d270cc2ffa [v2] Update Workflows 2021-09-13 17:45:09 +10:00
Lea Anthony
d6cdd1df82 [v2] Update Menu runtime 2021-09-12 20:45:40 +10:00
Lea Anthony
3c0da9fd15 [windows] support runtime.WindowSetRGBA 2021-09-12 16:32:43 +10:00
Lea Anthony
c9f93cd313 [windows] Handle naked go build 2021-09-10 19:38:19 +10:00
Lea Anthony
78c43d23b6 [windows] Allow fullscreen with maxsize constraints 2021-09-09 20:45:41 +10:00
Lea Anthony
322f6b9b64 [windows] Disable Status Bar 2021-09-09 20:26:04 +10:00
Lea Anthony
88bbfcb7f1 [v2] Guard against nil context 2021-09-09 20:11:48 +10:00
Lea Anthony
cae9827841 [v2] Support Window runtime in JS 2021-09-09 20:10:18 +10:00
Lea Anthony
414b0149f2 [v2] Add forcebuild flag. Tags flag to dev. Refactor dev. 2021-09-09 19:44:06 +10:00
Lea Anthony
33e4ec3e80 [v2] "Pack" by default in dev 2021-09-08 23:52:20 +10:00
Lea Anthony
c46182923c [v2] Add version to wails.json 2021-09-08 23:32:48 +10:00
Lea Anthony
5d2242f7a4 [v2] Delete .git for remote templates 2021-09-08 21:38:46 +10:00
Lea Anthony
6204e63c56 [v2] Svelte template fixes 2021-09-08 21:29:53 +10:00
Lea Anthony
4468edc939 [windows] Disable icon fix pt 2 - The revenge of 2021-09-08 21:27:49 +10:00
Lea Anthony
b54a94135d [v2] CSS mimetype fix 2021-09-08 19:48:49 +10:00
Lea Anthony
e4d70f94b1 [windows] Temporarily remove docker as optional requirement 2021-09-07 20:36:51 +10:00
Lea Anthony
70418783a8 [v2] Semver tests 2021-09-07 18:38:07 +10:00
Lea Anthony
d9aa125a15 [v2] Refactor devserver -> devwebserver 2021-09-07 18:37:47 +10:00
Lea Anthony
dacea399ab [v2] Refactor assets type 2021-09-07 07:06:58 +10:00
Lea Anthony
a78b9a5b9b [v2] allow no package.json 2021-09-07 07:06:18 +10:00
Lea Anthony
36cd6af6df [v2] Update base templates 2021-09-07 07:05:34 +10:00
Lea Anthony
1e9807189c [windows] Workaround for DPI scaling bug 2021-09-07 07:04:29 +10:00
Lea Anthony
d54834d501 [v2] Localise deps temporarily 2021-09-06 21:40:11 +10:00
Lea Anthony
ac06e6728d [windows] WebView2 check fix 2021-09-06 17:44:57 +10:00
Lea Anthony
227c316cb0 [v2] Extract asset path calculation 2021-09-05 22:40:52 +10:00
Lea Anthony
2c2ce66ec4 [windows] Fix bug with windows options 2021-09-05 22:40:34 +10:00
Lea Anthony
9a99b47f07 [v2] Add title to OpenFileDialog 2021-09-05 18:40:07 +10:00
Lea Anthony
505bb51a27 [v2] Tidy up. 2021-09-05 16:21:24 +10:00
Lea Anthony
6ade38e0ff [windows] Fix build assets, use 512x512 icon 2021-09-03 20:34:14 +10:00
Lea Anthony
6551e1b499 [v2] latest runtime build 2021-09-03 19:00:07 +10:00
Lea Anthony
20f82cbde4 [v2] Default project path to cwd 2021-09-03 18:55:44 +10:00
Lea Anthony
1ee9cf41e2 [v2] Enable package by default in wails build 2021-09-03 18:55:24 +10:00
Lea Anthony
1cd38d12f9 Add webview2 to wails doctor, refactored IsAppleSilicon 2021-09-03 05:55:51 +10:00
Lea Anthony
0b71d64931 [v2] window.backend -> window.go 2021-09-01 20:52:34 +10:00
Lea Anthony
4954655ad4 [windows] Tidy wails.json 2021-09-01 06:48:06 +10:00
Lea Anthony
8db91df185 [windows] Fix compiled binary path 2021-09-01 06:42:30 +10:00
Lea Anthony
ac4c17ca09 [windows] Generate bindings + models 2021-08-31 19:51:43 +10:00
Lea Anthony
604dc6ace3 [windows] Rename assetdir json key 2021-08-31 19:47:40 +10:00
Lea Anthony
627aa06786 [windows] Use forward slash paths for assetdir and wailsjsdir 2021-08-31 19:38:29 +10:00
Lea Anthony
e718fb8333 [windows-x] Add LogTrace 2021-08-29 14:36:31 +10:00
Lea Anthony
c5d9fd1a0d [windows-x] Generate TS models 2021-08-28 14:51:40 +10:00
Lea Anthony
63117fd519 [windows-x] Only delete wailsbindings in production build 2021-08-28 14:51:12 +10:00
Lea Anthony
a7c5064a33 [windows-x] Generate runtime wrapper in project, initial model binding, better error handling in calls, assetdir fix, 2021-08-27 21:11:03 +10:00
Lea Anthony
f20ce7411d [windows-x] Add wailsjs directory flag 2021-08-26 23:25:29 +10:00
Lea Anthony
a7c04ac891 [windows-x] updated desktop.js 2021-08-26 23:24:57 +10:00
Lea Anthony
01de76a32e [windows-x] Improve compiler flags (big speedup) 2021-08-26 23:23:30 +10:00
Lea Anthony
a861aa36b9 [windows-x] Fix accelerator key handling 2021-08-26 21:33:16 +10:00
Lea Anthony
af593ef47a [windows-x] Only show window after NavigationCompleted 2021-08-26 21:26:32 +10:00
Lea Anthony
b98ab1f5b5 [windows-x] Production build compile fix 2021-08-26 21:25:44 +10:00
Lea Anthony
5ff3a286cf [windows-x] Support OnDomReady, fix build 2021-08-26 21:06:54 +10:00
Lea Anthony
58dc917fb7 [windows-x] Startup/Shutdown -> OnStartup/OnShutdown 2021-08-26 20:38:03 +10:00
Lea Anthony
e00d65d468 [windows-x] Update events system to accommodate dev build 2021-08-25 20:54:05 +10:00
Lea Anthony
dbcf4058e5 [windows-x] Update log runtime 2021-08-25 20:30:31 +10:00
Lea Anthony
b558246d52 [windows-x] Dev mode. Auto rebuild/reload. Auto open browser. Disconnect spinner. Base template update. mimecache 2021-08-24 23:48:03 +10:00
Lea Anthony
c3c88f5e27 [windows-x] Huge updates to devmode, runtime, build command. 2021-08-22 23:05:22 +10:00
Lea Anthony
31468aa177 [windows-x] Initial support for dev 2021-08-18 22:13:45 +10:00
Lea Anthony
6923ea301b [windows-x] Refactor runtime & asset server 2021-08-18 22:12:00 +10:00
Lea Anthony
ec8d8e4587 [windows-x] Init startup hook 2021-08-18 22:05:30 +10:00
Lea Anthony
32591465f3 [windows-x] Fix runtime timeout 2021-08-18 19:29:08 +10:00
Lea Anthony
1c5c26e04a [windows-x] Remove -k flag 2021-08-17 20:52:09 +10:00
Lea Anthony
83baf4c6bb [windows-x] Support debug flag, prevent devtools, zoom and context menus in prod builds 2021-08-17 20:38:14 +10:00
Lea Anthony
c383a61036 [windows-x] Support webview2 runtime checker on startup 2021-08-16 21:35:25 +10:00
Lea Anthony
9d9c3d971a [windows-x] Update wails doctor 2021-08-16 19:19:32 +10:00
Lea Anthony
8acfeba3e1 [windows-x] Fix compress text output 2021-08-15 21:32:47 +10:00
Lea Anthony
09bae529de [windows-x] Fix icon bug 2021-08-15 21:27:49 +10:00
Lea Anthony
38c507a605 [windows-x] Add skip frontend build option 2021-08-15 21:27:33 +10:00
Lea Anthony
b7cd36921e [windows-x] Experimental -> Default 2021-08-15 21:07:34 +10:00
Lea Anthony
de255729e6 [windows-x] Support window drag 2021-08-15 20:17:58 +10:00
Lea Anthony
3ac1dcc8d9 [windows-x] Use winres for processing manifest + icon 2021-08-15 20:16:14 +10:00
Lea Anthony
1d6cce7c52 [windows-x] Simplify build assets 2021-08-14 19:35:00 +10:00
Lea Anthony
c2ac4961ef [windows-x] Don't process assets when building 2021-08-14 19:24:29 +10:00
Lea Anthony
8e84bdfa8d [windows-x] Support calling bound methods 2021-08-13 23:44:24 +10:00
Lea Anthony
316e4de8e2 [windows-x] Support events 2021-08-11 21:26:41 +10:00
Lea Anthony
d83fd1c2b4 [windows] [WIP] Support embedded asset.FS, updated runtime 2021-08-10 06:35:12 +10:00
Lea Anthony
619d8cc05e [windows-x] Support events runtime js->go, Refactor events methods, Refactor JS runtime. 2021-08-01 22:14:56 +10:00
Lea Anthony
244b3dc2b4 [windows-x] Support embed.fs assets, log runtime 2021-08-01 22:14:56 +10:00
Lea Anthony
1a69d93d32 [windows-x] Remove Super key for now 2021-08-01 22:14:56 +10:00
Lea Anthony
5ba85f2817 [windows-x] Fix icon 2021-08-01 22:14:56 +10:00
Lea Anthony
76f19f0752 Merge pull request #762 from s12chung/inline-js
[v2] handle inline javascript in assetbundle.go
2021-07-31 00:07:17 +10:00
Lea Anthony
5b10ee4b40 [windows-x] Runtime ported. Menu shortcuts. 2021-07-25 20:21:21 +10:00
Lea Anthony
72b1e58218 [windows-x] Deprecate Super key for now 2021-07-25 20:19:08 +10:00
Lea Anthony
7eb0718c9c [windows-x] Tidy up refactor 2021-07-25 15:56:53 +10:00
Lea Anthony
ca8d41dd3b [windows-x] Refactor runtime again 2021-07-25 15:37:30 +10:00
Lea Anthony
179d26b1c4 [windows-x] revert refactor 2021-07-25 15:22:13 +10:00
Lea Anthony
9786053324 [windows-x] Refactor runtime 2021-07-25 15:04:01 +10:00
Lea Anthony
aee395020f [windows-x] Support multiple radiogroups 2021-07-25 14:43:03 +10:00
Lea Anthony
f377c94ba3 [windows-x] Support Application Menu, menu items, callbacks, checkboxes 2021-07-24 18:37:20 +10:00
Lea Anthony
c5f1bd4449 [windows] Experimental guard 2021-07-24 15:04:02 +10:00
Lea Anthony
e11dc0e080 [windows] Support all window runtime methods 2021-07-24 10:34:43 +10:00
Lea Anthony
0ea135a546 [windows] Support more runtime methods,
WindowCenter, WindowMaximise,WindowUnmaximise,WindowMinimise,WindowUnminimise, WindowSetPos,WindowSetSize
Settings: DisableWindowIcon, StartHidden, HideWindowOnClose
2021-07-24 06:55:47 +10:00
Lea Anthony
ccda8c5760 [window] frontend experiment 2021-07-22 19:49:54 +10:00
Steve Chung
8a5287794e handle inline javascript in assetbundle.go 2021-07-21 17:22:18 -04:00
Lea Anthony
3e28bfe717 [v2] [broken - WIP] Update vanilla template 2021-07-19 23:08:00 +10:00
Lea Anthony
06ebe5f51f [v2] [broken - WIP] Major refactor of runtime in progress 2021-07-19 23:04:36 +10:00
Lea Anthony
f02c140709 [v2] [broken - WIP] Major refactor of runtime in progress 2021-07-19 20:21:41 +10:00
Lea Anthony
b80a64b0ee [v2] [broken - WIP] Major refactor of runtime in progress 2021-07-18 20:00:01 +10:00
Lea Anthony
5d2cc81123 [windows] Prevent WebView2 from processing keystrokes 2021-07-18 19:02:33 +10:00
Lea Anthony
b86d2fe8cd [windows] Support more accelerator keys 2021-07-18 13:11:15 +10:00
Lea Anthony
fee14babbc [windows] Basic accelerator support 2021-07-18 12:32:14 +10:00
Lea Anthony
f06ffb62de [windows] Remove debug statement 2021-07-18 11:55:12 +10:00
Lea Anthony
4a1a4d75ad [v2] v2.0.0-alpha.73 2021-07-16 22:31:53 +10:00
Lea Anthony
b552c16539 [windows] Add key handler for Webview2 to prevent default hotkeys. 2021-07-16 22:30:58 +10:00
Lea Anthony
d574d53fca [windows] Add debug log for Webview2 version and minimum required version 2021-07-16 21:13:34 +10:00
Lea Anthony
2b69ac8391 [windows] Update manifest to use windows common comtrols v6 2021-07-16 20:15:08 +10:00
Lea Anthony
e7cb40d5ee [windows] use idgen to track menu IDs. Fix chechbox typo 2021-07-11 20:21:23 +10:00
Lea Anthony
f409dbdab1 [v2] v2.0.0-alpha.72 2021-07-11 14:15:05 +10:00
Lea Anthony
1748e8479f [windows] Fixes for window sizing 2021-07-11 11:29:34 +10:00
Lea Anthony
4f2788a294 [windows] Fixes for radiobox sync 2021-07-10 17:03:55 +10:00
Travis McLane
856b81ab04 add missing import 2021-07-09 15:08:59 -05:00
Lea Anthony
76aab2271c [windows] Menu checkboxes and radio groups now fully in sync when using Go menus mutltiple times. 2021-07-08 23:34:56 +10:00
Lea Anthony
3192026e6d [windows] Support updating application menu 2021-07-06 21:07:35 +10:00
Lea Anthony
90dd05e52e [windows] Support radio groups in menus 2021-07-06 07:30:48 +10:00
Lea Anthony
e73cf44ddc [windows] Preliminary support for application menu. More work TBD. 2021-07-05 21:37:48 +10:00
Lea Anthony
4c2804eac9 [v2] Create default windows app menu 2021-07-04 13:59:21 +10:00
Lea Anthony
e1dd77fd3f [v2] Remove SetTitle from templates 2021-07-04 09:51:19 +10:00
Lea Anthony
b69f1e6c43 [v2] Put mac specific calls behind build tag 2021-07-04 09:35:45 +10:00
404 changed files with 10113 additions and 19925 deletions

View File

@@ -1,8 +1,10 @@
name: latest pre-release
on:
push:
branches:
- develop
tags:
- '**-pre**'
- '**-pre**'
jobs:
build:

29
.github/workflows/runtime.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Runtime
on:
push:
branches:
- v2-alpha
paths:
- 'v2/internal/frontend/runtime/**'
jobs:
rebuild-runtime:
name: Rebuild the runtime
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14.17.6
cache: 'npm'
cache-dependency-path: v2/internal/frontend/runtime/package-lock.json
- run: npm install
working-directory: v2/internal/frontend/runtime
- run: npm run build
working-directory: v2/internal/frontend/runtime
- name: Commit changes
uses: devops-infra/action-commit-push@master
with:
github_token: "${{ secrets.GITHUB_TOKEN }}"
commit_prefix: "[AUTO]"
commit_message: "The runtime was rebuilt"

View File

@@ -1,3 +1,4 @@
//go:build linux || darwin || !windows
// +build linux darwin !windows
package wails

View File

@@ -1,3 +1,4 @@
//go:build windows || !linux || !darwin
// +build windows !linux !darwin
package wails

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,4 @@
//go:build dev
// +build dev
package main

View File

@@ -1,3 +1,4 @@
//go:build dev
// +build dev
package main

View File

@@ -1,3 +1,4 @@
//go:build windows
// +build windows
package cmd

1
go.sum
View File

@@ -79,7 +79,6 @@ golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-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=

View File

@@ -1,4 +1,4 @@
package interfaces
// Runtime interface
type Runtime interface {}
type Runtime interface{}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,4 +2,5 @@
This branch contains WORK IN PROGRESS! There are no guarantees. Use at your peril!
This document will be updated as progress is made.
This document will be updated as progress is made.

View File

@@ -12,8 +12,6 @@ import (
"github.com/wailsapp/wails/v2/internal/system"
"github.com/wailsapp/wails/v2/internal/shell"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/clilogger"
@@ -29,13 +27,9 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
command := app.NewSubCommand("build", "Builds the application")
// 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)
// Setup noPackage flag
noPackage := false
command.BoolFlag("noPackage", "Skips platform specific packaging", &noPackage)
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
@@ -62,11 +56,6 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
tags := ""
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &tags)
// Retain assets
keepAssets := false
command.BoolFlag("k", "Keep generated assets", &keepAssets)
// Retain assets
outputFilename := ""
command.StringFlag("o", "Output filename", &outputFilename)
@@ -77,8 +66,11 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
webview2 := "download"
command.StringFlag("webview2", "WebView2 installer strategy: download,embed,browser,error.", &webview2)
runDelve := false
command.BoolFlag("delve", "Runs the built binary in delve for debugging", &runDelve)
skipFrontend := false
command.BoolFlag("s", "Skips building the frontend", &skipFrontend)
forceBuild := false
command.BoolFlag("f", "Force build application", &forceBuild)
command.Action(func() error {
@@ -97,12 +89,6 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
app.PrintBanner()
}
// Setup mode
mode := build.Debug
if production {
mode = build.Production
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
@@ -158,34 +144,29 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
}
}
// If we want to use delve we need to compile in DEBUG mode
if runDelve {
mode = build.Debug
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
OutputFile: outputFilename,
CleanBuildDirectory: cleanBuildDirectory,
Mode: mode,
Pack: pack,
Mode: build.Production,
Pack: !noPackage,
LDFlags: ldflags,
Compiler: compilerCommand,
KeepAssets: keepAssets,
Verbosity: verbosity,
ForceBuild: forceBuild,
IgnoreFrontend: skipFrontend,
Compress: compress,
CompressFlags: compressFlags,
UserTags: userTags,
WebView2Strategy: wv2rtstrategy,
RunDelve: runDelve,
}
// Calculate platform and arch
platformSplit := strings.Split(platform, "/")
buildOptions.Platform = platformSplit[0]
if system.IsAppleSilicon() {
if system.IsAppleSilicon {
buildOptions.Arch = "arm64"
} else {
buildOptions.Arch = runtime.GOARCH
@@ -194,28 +175,20 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
buildOptions.Arch = platformSplit[1]
}
println("Build arch =", buildOptions.Arch)
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
buildModeText := "debug"
if production {
buildModeText = "production"
}
// Write out the system information
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "App Type: \t%s\n", buildOptions.OutputType)
fmt.Fprintf(w, "Platform: \t%s\n", buildOptions.Platform)
fmt.Fprintf(w, "Arch: \t%s\n", buildOptions.Arch)
fmt.Fprintf(w, "Compiler: \t%s\n", compilerPath)
fmt.Fprintf(w, "Skip Frontend: \t%t\n", skipFrontend)
fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
fmt.Fprintf(w, "Build Mode: \t%s\n", buildModeText)
fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
fmt.Fprintf(w, "Clean Build Dir: \t%t\n", buildOptions.CleanBuildDirectory)
fmt.Fprintf(w, "KeepAssets: \t%t\n", buildOptions.KeepAssets)
fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
if len(buildOptions.OutputFile) > 0 {
@@ -245,40 +218,5 @@ func doBuild(buildOptions *build.Options) error {
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Println("")
if buildOptions.RunDelve {
// 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
buildOptions.Logger.Println("Launching Delve on port 2345...")
cmdArgs := slicer.String([]string{"--listen=:2345", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", outputFilename})
if buildOptions.Verbosity == build.VERBOSE {
buildOptions.Logger.Println("\tRunning: dlv %s", cmdArgs.Join(" "))
}
stdout, stderr, err := shell.RunCommand(cwd, "dlv", cmdArgs.AsSlice()...)
if buildOptions.Verbosity == build.VERBOSE || err != nil {
trimstdout := strings.TrimSpace(stdout)
if trimstdout != "" {
buildOptions.Logger.Println(trimstdout)
}
trimstderr := strings.TrimSpace(stderr)
if trimstderr != "" {
buildOptions.Logger.Println(trimstderr)
}
}
if err != nil {
return err
}
}
return nil
}

View File

@@ -2,8 +2,11 @@ package dev
import (
"fmt"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/project"
"io"
"log"
"net/http"
"os"
"os/signal"
"path/filepath"
@@ -12,6 +15,7 @@ import (
"syscall"
"time"
"github.com/pkg/browser"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/fsnotify/fsnotify"
@@ -37,6 +41,14 @@ func LogDarkYellow(message string, args ...interface{}) {
println(colour.DarkYellow(text))
}
func sliceToMap(input []string) map[string]struct{} {
result := map[string]struct{}{}
for _, value := range input {
result[value] = struct{}{}
}
return result
}
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
@@ -50,20 +62,35 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
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)
assetDir := ""
command.StringFlag("assetdir", "Serve assets from the given directory", &assetDir)
// extensions to trigger rebuilds
showWarnings := false
command.BoolFlag("w", "Show warnings", &showWarnings)
// extensions to trigger rebuilds of application
extensions := "go"
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go", &extensions)
openBrowser := false
command.BoolFlag("browser", "Open application in browser", &openBrowser)
noreload := false
command.BoolFlag("noreload", "Disable reload on asset change", &noreload)
wailsjsdir := ""
command.StringFlag("wailsjsdir", "Directory to generate the Wails JS modules", &wailsjsdir)
// tags to pass to `go`
tags := ""
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &tags)
// Verbosity
verbosity := 1
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
command.IntFlag("v", "Verbosity level (0 - silent, 1 - standard, 2 - verbose)", &verbosity)
loglevel := ""
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
command.StringFlag("loglevel", "Loglevel to use - Trace, Dev, Info, Warning, Error", &loglevel)
forceBuild := false
command.BoolFlag("f", "Force build application", &forceBuild)
command.Action(func() error {
@@ -71,7 +98,65 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
logger := clilogger.New(w)
app.PrintBanner()
// TODO: Check you are in a project directory
cwd, err := os.Getwd()
if err != nil {
return err
}
projectConfig, err := project.Load(cwd)
if err != nil {
return err
}
if projectConfig.AssetDirectory == "" && assetDir == "" {
return fmt.Errorf("No asset directory provided. Please use -assetdir to indicate which directory contains your built assets.")
}
if assetDir == "" && projectConfig.AssetDirectory != "" {
assetDir = projectConfig.AssetDirectory
}
if assetDir != projectConfig.AssetDirectory {
projectConfig.AssetDirectory = filepath.ToSlash(assetDir)
err := projectConfig.Save()
if err != nil {
return err
}
}
if err != nil {
return err
}
if wailsjsdir == "" && projectConfig.WailsJSDir != "" {
wailsjsdir = projectConfig.WailsJSDir
}
if wailsjsdir == "" {
wailsjsdir = "./frontend"
}
if wailsjsdir != projectConfig.WailsJSDir {
projectConfig.WailsJSDir = filepath.ToSlash(wailsjsdir)
err := projectConfig.Save()
if err != nil {
return err
}
}
buildOptions := &build.Options{
Logger: logger,
OutputType: "dev",
Mode: build.Dev,
Arch: runtime.GOARCH,
Pack: true,
Platform: runtime.GOOS,
LDFlags: ldflags,
Compiler: compilerCommand,
ForceBuild: forceBuild,
IgnoreFrontend: false,
Verbosity: verbosity,
WailsJSDir: wailsjsdir,
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
@@ -85,13 +170,12 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
}(watcher)
var debugBinaryProcess *process.Process = nil
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
var extensionsThatTriggerARebuild = sliceToMap(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)
exitCodeChannel := make(chan int, 1)
var passthruArgs []string
//if len(os.Args) > 2 {
@@ -100,70 +184,26 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Do initial build
logger.Println("Building application for development...")
newProcess, appBinary, err := restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
newProcess, appBinary, err := restartApp(logger, buildOptions, debugBinaryProcess, loglevel, passthruArgs, assetDir, false, exitCodeChannel)
if err != nil {
return err
}
if newProcess != nil {
debugBinaryProcess = newProcess
}
// open browser
if openBrowser {
err = browser.OpenURL("http://localhost:34115")
if err != nil {
return err
}
}
if err != nil {
return err
}
var newBinaryProcess *process.Process
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())
}
LogGreen("[New Directory] Watching new directory: %s", event.Name)
}
}
return
}
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
var rebuild bool = false
// Iterate all file patterns
for _, pattern := range extensionsThatTriggerARebuild {
if strings.HasSuffix(event.Name, pattern) {
rebuild = true
break
}
}
if !rebuild {
if showWarnings {
LogDarkYellow("[File change] %s did not match extension list (%s)", event.Name, extensions)
}
return
}
LogGreen("[Attempting rebuild] %s updated", event.Name)
// Do a rebuild
// Try and build the app
newBinaryProcess, _, err = restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
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
projectDir, err := os.Getwd()
@@ -188,20 +228,88 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
if strings.HasPrefix(dir, filepath.Join(projectDir, "build")) {
return
}
//println("Watching", dir)
err = watcher.Add(dir)
if err != nil {
logger.Fatal(err.Error())
}
})
// Wait until we get a quit signal
// Main Loop
quit := false
// Use 100ms debounce
interval := 100 * time.Millisecond
timer := time.NewTimer(interval)
rebuild := false
reload := false
for quit == false {
//reload := false
select {
case exitCode := <-exitCodeChannel:
if exitCode == 0 {
quit = true
}
case item := <-watcher.Events:
// Check for file writes
if item.Op&fsnotify.Write == fsnotify.Write {
// Ignore directories
if fs.DirExists(item.Name) {
continue
}
// Iterate all file patterns
ext := filepath.Ext(item.Name)
if ext != "" {
ext = ext[1:]
if _, exists := extensionsThatTriggerARebuild[ext]; exists {
rebuild = true
continue
}
}
if strings.HasPrefix(item.Name, assetDir) {
reload = true
}
timer.Reset(interval)
}
// Check for new directories
if item.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list
if fs.DirExists(item.Name) {
//node_modules is BANNED!
if !strings.Contains(item.Name, "node_modules") {
err := watcher.Add(item.Name)
if err != nil {
logger.Fatal("%s", err.Error())
}
LogGreen("Added new directory to watcher: %s", item.Name)
}
}
}
case <-timer.C:
if rebuild {
rebuild = false
LogGreen("[Rebuild triggered] files updated")
// Try and build the app
newBinaryProcess, _, err = restartApp(logger, buildOptions, debugBinaryProcess, loglevel, passthruArgs, assetDir, false, exitCodeChannel)
if err != nil {
LogRed("Error during build: %s", err.Error())
continue
}
// If we have a new process, save it
if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess
}
}
if reload {
reload = false
_, err = http.Get("http://localhost:34115/wails/reload")
if err != nil {
LogRed("Error during refresh: %s", err.Error())
}
}
case <-quitChannel:
LogGreen("\nCaught quit")
// Notify debouncer to quit
debounceQuit <- true
quit = true
}
}
@@ -228,30 +336,14 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
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, buildOptions *build.Options, debugBinaryProcess *process.Process, loglevel string, passthruArgs []string, assetDir string, firstRun bool, exitCodeChannel chan int) (*process.Process, string, error) {
func restartApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string, passthruArgs []string, verbosity int) (*process.Process, string, error) {
appBinary, err := buildApp(logger, ldflags, compilerCommand, verbosity)
appBinary, err := build.Build(buildOptions)
println()
if err != nil {
if firstRun {
return nil, "", err
}
LogRed("Build error - continuing to run current version")
LogDarkYellow(err.Error())
return nil, "", nil
@@ -268,15 +360,18 @@ func restartApp(logger *clilogger.CLILogger, ldflags string, compilerCommand str
debugBinaryProcess = nil
}
// TODO: Generate `backend.js`
// Start up new binary with correct args
var args = []string{"-loglevel", loglevel}
if len(passthruArgs) > 0 {
args = append(args, passthruArgs...)
args := slicer.StringSlicer{}
args.Add("-loglevel", loglevel)
if assetDir != "" {
args.Add("-assetdir", assetDir)
}
newProcess := process.NewProcess(logger, appBinary, args...)
err = newProcess.Start()
if len(passthruArgs) > 0 {
args.AddSlice(passthruArgs)
}
newProcess := process.NewProcess(appBinary, args.AsSlice()...)
err = newProcess.Start(exitCodeChannel)
if err != nil {
// Remove binary
deleteError := fs.DeleteFile(appBinary)
@@ -288,29 +383,3 @@ func restartApp(logger *clilogger.CLILogger, ldflags string, compilerCommand str
return newProcess, appBinary, nil
}
func buildApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, verbosity int) (string, error) {
// Create random output file
outputFile := "wailsdev"
if runtime.GOOS == "windows" {
outputFile += ".exe"
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: "dev",
Mode: build.Debug,
Pack: false,
Platform: runtime.GOOS,
LDFlags: ldflags,
Compiler: compilerCommand,
OutputFile: outputFile,
IgnoreFrontend: true,
Verbosity: verbosity,
}
return build.Build(buildOptions)
}

View File

@@ -35,6 +35,8 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
}
logger.Println("Done.")
logger.Println("")
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)

View File

@@ -1,14 +1,13 @@
package main
import (
"context"
"fmt"
"github.com/wailsapp/wails/v2"
)
// App struct
type App struct {
runtime *wails.Runtime
runtime context.Context
}
// NewApp creates a new App application struct
@@ -17,14 +16,16 @@ func NewApp() *App {
}
// startup is called at application startup
func (b *App) startup(runtime *wails.Runtime) {
func (b *App) startup(ctx context.Context) {
// Perform your setup here
b.runtime = runtime
runtime.Window.SetTitle("{{.ProjectName}}")
//TODO: move to new runtime layout
//b.runtime = runtime
//runtime.Window.SetTitle("{{.ProjectName}}")
}
// shutdown is called at application termination
func (b *App) shutdown() {
func (b *App) shutdown(ctx context.Context) {
// Perform your teardown here
}

View File

@@ -7,9 +7,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/mac"
)
func main() {
@@ -30,22 +28,15 @@ func main() {
Frameless: false,
StartHidden: false,
HideWindowOnClose: false,
DevTools: false,
RGBA: 0x000000FF,
RGBA: &options.RGBA{0, 0, 0, 255},
Windows: &windows.Options{
WebviewIsTransparent: true,
WindowBackgroundIsTranslucent: true,
DisableWindowIcon: true,
WebviewIsTransparent: true,
WindowIsTranslucent: true,
DisableWindowIcon: true,
},
Mac: &mac.Options{
WebviewIsTransparent: true,
WindowBackgroundIsTranslucent: true,
TitleBar: mac.TitleBarHiddenInset(),
Menu: menu.DefaultMacMenu(),
},
LogLevel: logger.DEBUG,
Startup: app.startup,
Shutdown: app.shutdown,
LogLevel: logger.DEBUG,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},

View File

@@ -225,6 +225,15 @@ func Install(options *Options) (bool, error) {
log.Fatal(err)
}
}(tempdir)
if err != nil {
return false, err
}
// Remove the .git directory
err = os.RemoveAll(filepath.Join(tempdir, ".git"))
if err != nil {
return false, err
}
templateFS := os.DirFS(tempdir)
template, err = parseTemplate(templateFS)
if err != nil {

View File

@@ -1,14 +1,13 @@
package main
import (
"context"
"fmt"
"github.com/wailsapp/wails/v2"
)
// App application struct
type App struct {
runtime *wails.Runtime
ctx context.Context
}
// NewApp creates a new App application struct
@@ -17,14 +16,13 @@ func NewApp() *App {
}
// startup is called at application startup
func (b *App) startup(runtime *wails.Runtime) {
func (b *App) startup(ctx context.Context) {
// Perform your setup here
b.runtime = runtime
runtime.Window.SetTitle("{{.ProjectName}}")
b.ctx = ctx
}
// shutdown is called at application termination
func (b *App) shutdown() {
func (b *App) shutdown(ctx context.Context) {
// Perform your teardown here
}

View File

@@ -3,7 +3,7 @@
let greeting = "";
function greet() {
window.backend.main.App.Greet(name).then((result) => {
window.go.main.App.Greet(name).then((result) => {
greeting = result;
});
}

View File

@@ -0,0 +1,52 @@
package main
import (
"embed"
"log"
"github.com/wailsapp/wails/v2/pkg/options/windows"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
//go:embed frontend/dist
var assets embed.FS
func main() {
// Create application with options
app := NewApp()
err := wails.Run(&options.App{
Title: "",
Width: 800,
Height: 600,
MinWidth: 400,
MinHeight: 400,
MaxWidth: 1280,
MaxHeight: 1024,
DisableResize: false,
Fullscreen: false,
Frameless: false,
StartHidden: false,
HideWindowOnClose: false,
RGBA: 0x000000FF,
Assets: assets,
Windows: &windows.Options{
WebviewIsTransparent: true,
WindowIsTranslucent: true,
DisableWindowIcon: true,
},
LogLevel: logger.DEBUG,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}

View File

@@ -1,56 +0,0 @@
package main
import (
"log"
"github.com/wailsapp/wails/v2/pkg/options/windows"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/mac"
)
func main() {
// Create application with options
app := NewApp()
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 800,
Height: 600,
MinWidth: 400,
MinHeight: 400,
MaxWidth: 1280,
MaxHeight: 1024,
DisableResize: false,
Fullscreen: false,
Frameless: false,
StartHidden: false,
HideWindowOnClose: false,
DevTools: false,
RGBA: 0x000000FF,
Windows: &windows.Options{
WebviewIsTransparent: true,
WindowBackgroundIsTranslucent: true,
DisableWindowIcon: true,
},
Mac: &mac.Options{
WebviewIsTransparent: true,
WindowBackgroundIsTranslucent: true,
TitleBar: mac.TitleBarHiddenInset(),
Menu: menu.DefaultMacMenu(),
},
LogLevel: logger.DEBUG,
Startup: app.startup,
Shutdown: app.shutdown,
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}

View File

@@ -1,11 +1,11 @@
{
"name": "{{.ProjectName}}",
"outputfilename": "{{.BinaryName}}",
"html": "frontend/dist/index.html",
"frontend:build": "npm run build",
"frontend:install": "npm install",
"author": {
"name": "{{.AuthorName}}",
"email": "{{.AuthorEmail}}"
}
"name": "{{.ProjectName}}",
"outputfilename": "{{.BinaryName}}",
"assetdir": "frontend/dist",
"frontend:build": "npm run build",
"frontend:install": "npm install",
"author": {
"name": "{{.AuthorName}}",
"email": "{{.AuthorEmail}}"
}
}

View File

@@ -2,15 +2,15 @@
## About
This template uses vanilla JS / HTML and CSS. [Rollup](https://rollupjs.org/guide/en/) is used for processing and bundling the files.
## Building
To build this project in debug mode, use `wails build`. For production, use `wails build -production`.
To generate a platform native package, add the `-package` flag.
This template uses vanilla JS / HTML and CSS.
## Live Development
To run in live development mode, run `wails dev` in the project directory. In another terminal, go into the `frontend`
directory and run `npm run dev`. The frontend dev server will run on http://localhost:5000. Connect to this
in your browser and connect to your application.
To run in live development mode, run `wails dev` in the project directory. In another terminal, go into the `frontend`
directory and run `npm run dev`. The frontend dev server will run on http://localhost:5000. Connect to this in your
browser and connect to your application.
## Building
For a production build, use `wails build`.

View File

@@ -1,14 +1,13 @@
package main
import (
"context"
"fmt"
"github.com/wailsapp/wails/v2"
)
// App struct
type App struct {
runtime *wails.Runtime
ctx context.Context
}
// NewApp creates a new App application struct
@@ -17,14 +16,13 @@ func NewApp() *App {
}
// startup is called at application startup
func (b *App) startup(runtime *wails.Runtime) {
func (b *App) startup(ctx context.Context) {
// Perform your setup here
b.runtime = runtime
runtime.Window.SetTitle("{{.ProjectName}}")
b.ctx = ctx
}
// shutdown is called at application termination
func (b *App) shutdown() {
func (b *App) shutdown(ctx context.Context) {
// Perform your teardown here
}

View File

@@ -1,27 +0,0 @@
{
"name": "vanilla",
"version": "1.0.0",
"description": "Vanilla Wails v2 template",
"main": "src/main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv dist"
},
"author": "{{.AuthorName}}",
"license": "ISC",
"dependencies": {
"@rollup/plugin-commonjs": "^19.0.0",
"@rollup/plugin-image": "^2.0.6",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-url": "^6.0.0",
"@wails/runtime": "^1.3.20",
"rollup": "^2.50.4",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-terser": "^7.0.2",
"sirv-cli": "^1.0.12"
}
}

View File

@@ -1,103 +0,0 @@
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import postcss from 'rollup-plugin-postcss';
import image from '@rollup/plugin-image';
import url from '@rollup/plugin-url';
import copy from 'rollup-plugin-copy';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'dist/main.js'
},
onwarn: handleRollupWarning,
plugins: [
image(),
copy({
targets: [
{ src: 'src/index.html', dest: 'dist' },
{ src: 'src/main.css', dest: 'dist' },
]
}),
// Embed binary files
url({
include: ['**/*.woff', '**/*.woff2'],
limit: Infinity,
}),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
}),
commonjs(),
// PostCSS preprocessing
postcss({
extensions: ['.css', '.scss'],
extract: true,
minimize: false,
use: [
['sass', {
includePaths: [
'./src',
'./node_modules'
]
}]
],
}),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('dist'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};
function handleRollupWarning(warning) {
console.error('ERROR: ' + warning.toString());
}
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}

View File

@@ -0,0 +1,93 @@
Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 551 436" version="1.1" xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.27527,0,0,1.27527,104.01,410.563)">
<path d="M0,-51.891L14.429,-51.891L13.043,-21.183L22.568,-51.891L34.226,-51.891L34.084,-21.183L42.365,-51.891L56.794,-51.891L38.526,0L25.198,0L25.34,-32.45L15.211,0L1.919,0L0,-51.891Z"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1.27527,0,0,1.27527,224.985,367.503)">
<path d="M0,15.639L5.793,15.639L5.971,-3.589L0,15.639ZM-20.187,33.765L-0.675,-18.126L16.42,-18.126L20.08,33.765L5.437,33.765L5.509,26.123L-3.057,26.123L-5.332,33.765L-20.187,33.765Z"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.19929,-1.2596,-1.2596,-0.19929,332.323,396.949)">
<path d="M-16.046,33.107L36.491,33.107L38.757,18.784L-13.785,18.82L-16.046,33.107Z"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1.27527,0,0,1.27527,353.217,344.388)">
<path d="M0,51.891L8.246,0L22.781,0L16.597,39.024L27.224,39.024L25.199,51.891L0,51.891Z"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1.27527,0,0,1.27527,427.939,364.922)">
<path d="M0,19.83C1.611,21.181 3.305,22.224 5.083,22.959C6.859,23.693 8.565,24.06 10.2,24.06C11.645,24.06 12.794,23.663 13.647,22.87C14.5,22.076 14.927,20.992 14.927,19.617C14.927,18.434 14.571,17.254 13.861,16.081C13.15,14.908 11.775,13.351 9.738,11.408C7.273,9.015 5.58,6.906 4.655,5.081C3.731,3.257 3.27,1.243 3.27,-0.96C3.27,-5.912 4.839,-9.846 7.979,-12.76C11.118,-15.674 15.377,-17.132 20.756,-17.132C22.936,-17.132 25.008,-16.889 26.975,-16.403C28.941,-15.917 30.943,-15.165 32.982,-14.146L30.92,-1.493C29.356,-2.583 27.834,-3.412 26.354,-3.981C24.872,-4.551 23.457,-4.835 22.106,-4.835C20.898,-4.835 19.943,-4.521 19.245,-3.894C18.546,-3.265 18.196,-2.406 18.196,-1.316C18.196,0.154 19.535,2.215 22.213,4.868C22.544,5.2 22.805,5.46 22.995,5.649C25.696,8.304 27.473,10.578 28.326,12.475C29.179,14.37 29.605,16.56 29.605,19.049C29.605,24.594 27.893,28.965 24.469,32.163C21.046,35.361 16.36,36.962 10.413,36.962C7.877,36.962 5.479,36.66 3.216,36.056C0.953,35.45 -0.948,34.615 -2.488,33.549L0,19.83Z"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(-166.599,4.57132,4.57132,166.599,147.403,167.648)">
<path d="M0.883,-0.081L0.121,0.081L0.256,-0.063L0.883,-0.081Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
</g>
<g transform="matrix(-106.443,-16.0669,-16.0669,106.443,428.19,188.033)">
<path d="M0.878,-0.285L-0.073,0.71L-1.186,0.542L0.015,0.207L-0.846,0.077L0.355,-0.258L-0.505,-0.388L0.649,-0.71L0.878,-0.285Z"
style="fill:url(#_Linear2);fill-rule:nonzero;"/>
</g>
<g transform="matrix(-114.484,-162.408,-162.408,114.484,333.291,285.804)">
<path d="M0.44,-0.04L0.44,-0.04L0.44,-0.04L0.265,-0.056L0.177,0.437L-0.311,-0.255L0.262,-0.437L0.568,-0.437L0.44,-0.04Z"
style="fill:url(#_Linear3);fill-rule:nonzero;"/>
</g>
<g transform="matrix(61.6919,58.8091,58.8091,-61.6919,258.631,180.413)">
<path d="M0.5,0L0.5,-0L0.5,0L0.5,0Z" style="fill:url(#_Linear4);fill-rule:nonzero;"/>
</g>
<g transform="matrix(238.126,298.893,298.893,-238.126,113.516,-150.536)">
<path d="M0.622,-0.115L0.761,-0.115L0.806,-0.013L0.826,0.182L0.622,-0.115Z"
style="fill:url(#_Linear5);fill-rule:nonzero;"/>
</g>
<g transform="matrix(-369.529,-97.4118,-97.4118,369.529,582.38,94.027)">
<path d="M0.467,0.005L0.49,0.062L0.271,-0.062L0.467,0.005Z" style="fill:url(#_Linear6);fill-rule:nonzero;"/>
</g>
<g transform="matrix(-496.156,-53.9751,-53.9751,496.156,367.888,125.085)">
<path d="M0.2,0.001L0.219,-0.018L0.614,0.012L0.519,0.089L0.282,0.068L0.2,0.135L0.463,0.194L0.374,0.266L0.138,0.186L0.138,0.186L0.138,0.186L0.047,0.033L-0.131,-0.266L0.2,0.001Z"
style="fill:url(#_Linear7);fill-rule:nonzero;"/>
</g>
<g transform="matrix(185.076,176.427,176.427,-185.076,153.446,80.1488)">
<path d="M0.735,-0L0.735,-0L0.735,0L0.735,-0Z" style="fill:url(#_Linear8);fill-rule:nonzero;"/>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,-3.46945e-18,-3.46945e-18,-1,0,-3.05761e-06)"><stop offset="0" style="stop-color:rgb(227,50,50);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(107,0,13);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,-1,0,-2.75467e-06)"><stop offset="0" style="stop-color:rgb(227,50,50);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(107,0,13);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,-1.11022e-16,-1.11022e-16,-1,0,-2.61861e-06)"><stop offset="0" style="stop-color:rgb(227,50,50);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(107,0,13);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,-5.55112e-17,-5.55112e-17,-1,0,-1.57562e-06)"><stop offset="0" style="stop-color:rgb(227,50,50);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(107,0,13);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.801899,-0.59746,-0.59746,0.801899,1.3495,0.447457)"><stop offset="0" style="stop-color:rgb(227,50,50);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(107,0,13);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear6" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,-2.77556e-17,-2.77556e-17,-1,0,-1.92826e-06)"><stop offset="0" style="stop-color:rgb(227,50,50);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(107,0,13);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear7" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,-1,0,9.68429e-07)"><stop offset="0" style="stop-color:rgb(227,50,50);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(107,0,13);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear8" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,-1,0,1.43665e-07)"><stop offset="0" style="stop-color:rgb(227,50,50);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(107,0,13);stop-opacity:1"/></linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -1,21 +1,16 @@
// Get input + focus
let nameElement = document.getElementById("name");
nameElement.focus();
import {ready} from '@wails/runtime';
// Setup the greet function
window.greet = function () {
ready( () => {
// Get input + focus
let nameElement = document.getElementById("name");
nameElement.focus();
// Get name
let name = nameElement.value;
// Setup the greet function
window.greet = function () {
// Get name
let name = nameElement.value;
// Call App.Greet(name)
window.backend.main.App.Greet(name).then((result) => {
// Update result with data back from App.Greet()
document.getElementById("result").innerText = result;
});
};
});
// Call App.Greet(name)
window.go.main.App.Greet(name).then((result) => {
// Update result with data back from App.Greet()
document.getElementById("result").innerText = result;
});
};

View File

@@ -1,17 +1,19 @@
package main
import (
"embed"
"log"
"github.com/wailsapp/wails/v2/pkg/options/windows"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/mac"
)
//go:embed frontend/src
var assets embed.FS
func main() {
// Create application with options
@@ -30,22 +32,16 @@ func main() {
Frameless: false,
StartHidden: false,
HideWindowOnClose: false,
DevTools: false,
RGBA: 0x000000FF,
RGBA: &options.RGBA{0, 0, 0, 255},
Assets: assets,
Windows: &windows.Options{
WebviewIsTransparent: true,
WindowBackgroundIsTranslucent: true,
DisableWindowIcon: true,
WebviewIsTransparent: false,
WindowIsTranslucent: false,
DisableWindowIcon: false,
},
Mac: &mac.Options{
WebviewIsTransparent: true,
WindowBackgroundIsTranslucent: true,
TitleBar: mac.TitleBarHiddenInset(),
Menu: menu.DefaultMacMenu(),
},
LogLevel: logger.DEBUG,
Startup: app.startup,
Shutdown: app.shutdown,
LogLevel: logger.DEBUG,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},

View File

@@ -1,11 +1,9 @@
{
"name": "{{.ProjectName}}",
"outputfilename": "{{.BinaryName}}",
"html": "frontend/dist/index.html",
"frontend:build": "npm run build",
"frontend:install": "npm install",
"author": {
"name": "{{.AuthorName}}",
"email": "{{.AuthorEmail}}"
}
"name": "{{.ProjectName}}",
"outputfilename": "{{.BinaryName}}",
"assetdir": "frontend/src",
"author": {
"name": "{{.AuthorName}}",
"email": "{{.AuthorEmail}}"
}
}

View File

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

View File

@@ -1,43 +1,86 @@
module github.com/wailsapp/wails/v2
go 1.16
go 1.17
require (
github.com/Masterminds/semver v1.5.0
github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gabriel-vasile/mimetype v1.3.1
github.com/go-git/go-billy/v5 v5.2.0 // indirect
github.com/go-git/go-git/v5 v5.3.0
github.com/gofiber/fiber/v2 v2.17.0
github.com/gofiber/websocket/v2 v2.0.8
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/gorilla/websocket v1.4.1
github.com/imdario/mergo v0.3.12
github.com/jackmordaunt/icns v1.0.0
github.com/klauspost/compress v1.11.3 // indirect
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/debme v1.2.1
github.com/leaanthony/go-ansi-parser v1.0.1
github.com/leaanthony/go-common-file-dialog v1.0.3
github.com/leaanthony/go-webview2 v0.0.0-20210914103035-f00aa774a934
github.com/leaanthony/gosod v1.0.1
github.com/leaanthony/idgen v1.0.0
github.com/leaanthony/slicer v1.5.0
github.com/leaanthony/webview2runtime v1.1.0
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0
github.com/leaanthony/winc v0.0.0-20210906113646-fd400754f6ba
github.com/leaanthony/winicon v1.0.0
github.com/matryer/is v1.4.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/olekukonko/tablewriter v0.0.4
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.0 // indirect
github.com/tc-hib/winres v0.1.5
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/tidwall/sjson v1.1.7
github.com/tkrajina/typescriptify-golang-structs v0.1.6
github.com/wailsapp/wails v1.16.6
github.com/wzshiming/ctc v1.2.3
github.com/xyproto/xpm v1.2.1
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
github.com/ztrue/tracerr v0.3.0
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
golang.org/x/mod v0.4.1 // indirect
golang.org/x/net v0.0.0-20210326060303-6b1517762897
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273
golang.org/x/text v0.3.5 // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf
golang.org/x/tools v0.1.0
nhooyr.io/websocket v1.8.6
)
require (
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/abadojack/whatlanggo v1.0.1 // indirect
github.com/andybalholm/brotli v1.0.2 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/fasthttp/websocket v0.0.0-20200320073529-1554a54587ab // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e // indirect
github.com/mattn/go-runewidth v0.0.7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/sirupsen/logrus v1.4.1 // indirect
github.com/tidwall/gjson v1.8.0 // indirect
github.com/tidwall/match v1.0.3 // indirect
github.com/tidwall/pretty v1.1.0 // indirect
github.com/tkrajina/go-reflector v0.5.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.28.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

116
v2/go.sum
View File

@@ -1,10 +1,16 @@
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4=
github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -15,11 +21,16 @@ 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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fasthttp/websocket v0.0.0-20200320073529-1554a54587ab h1:9e2joQGp642wHGFP5m86SDptAavrdGBe8/x9DGEEAaI=
github.com/fasthttp/websocket v0.0.0-20200320073529-1554a54587ab/go.mod h1:smsv/h4PBEBaU0XDTY5UwJTpZv69fQ0FfcLJr21mA6Y=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
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/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
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/gabriel-vasile/mimetype v1.3.1 h1:qevA6c2MtE1RorlScnixeG0VA1H4xrXyhyX3oWBynNQ=
github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
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=
@@ -36,9 +47,11 @@ github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbK
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc=
github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/colors v1.2.0/go.mod h1:miw1R2JIE19cclPxsXqNdzLZsk4DP4iF+m88bRc7kfM=
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=
@@ -51,11 +64,16 @@ 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/gofiber/fiber/v2 v2.17.0 h1:qP3PkGUbBB0i9iQh5E057XI1yO5CZigUxZhyUFYAFoM=
github.com/gofiber/fiber/v2 v2.17.0/go.mod h1:iftruuHGkRYGEXVISmdD7HTYWyfS2Bh+Dkfq4n/1Owg=
github.com/gofiber/websocket/v2 v2.0.8 h1:Hb4y6IxYZVMO0segROODXJiXVgVD3a6i7wnfot8kM6k=
github.com/gofiber/websocket/v2 v2.0.8/go.mod h1:fv8HSGQX09sauNv9g5Xq8GeGAaahLFYQKKb4ZdT0x2w=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
@@ -64,23 +82,33 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
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/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5 h1:pdFFlHXY9tZXmJz+tRSm1DzYEH4ebha7cffmm607bMU=
github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -97,22 +125,41 @@ github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQ
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/go-webview2 v0.0.0-20210914103035-f00aa774a934 h1:nK/JTPyJi5QRqYjVZjXgtN4/dhg2qtngoLxLDVn429k=
github.com/leaanthony/go-webview2 v0.0.0-20210914103035-f00aa774a934/go.mod h1:lS5ds4bruPk9d7lzdF/OH31Z0YCerI6MmHNFGsWoUnM=
github.com/leaanthony/gosod v1.0.1 h1:F+4c3DmEBfigi7oAswCV2RpQ+k4DcNbhuCZUGdBHacQ=
github.com/leaanthony/gosod v1.0.1/go.mod h1:W8RyeSFBXu7RpIxPGEJfW4moSyGGEjlJMLV25wEbAdU=
github.com/leaanthony/idgen v1.0.0 h1:IZreR+JGEzFV4yeVuBZA25gM0keUoFy+RDUldncQ+Jw=
github.com/leaanthony/idgen v1.0.0/go.mod h1:4nBZnt8ml/f/ic/EVQuLxuj817RccT2fyrUaZFxrcVA=
github.com/leaanthony/slicer v1.4.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
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/leaanthony/spinner v0.5.3/go.mod h1:oHlrvWicr++CVV7ALWYi+qHk/XNA91D9IJ48IqmpVUo=
github.com/leaanthony/synx v0.1.0/go.mod h1:Iz7eybeeG8bdq640iR+CwYb8p+9EOsgMWghkSRyZcqs=
github.com/leaanthony/webview2runtime v1.1.0 h1:N0pv55ift8XtqozIp4PNOtRCJ/Qdd/qzx80lUpalS4c=
github.com/leaanthony/webview2runtime v1.1.0/go.mod h1:hH9GnWCve3DYzNaPOcPbhHQ7fodXR1QJNsnwixid4Tk=
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0 h1:FPGYnfxuuxqCZhrGq8nKjthEcYHgHmFbyY953Xv9cNI=
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
github.com/leaanthony/winc v0.0.0-20210906113646-fd400754f6ba h1:JalbOARv2XoT3RtJg7XGKKYcp7IndycC7pccLlfVRZI=
github.com/leaanthony/winc v0.0.0-20210906113646-fd400754f6ba/go.mod h1:KEbMsKoznsebyGHwLk5LqkFOxL5uXSRdvpP4+avmAMs=
github.com/leaanthony/wincursor v0.1.0/go.mod h1:7TVwwrzSH/2Y9gLOGH+VhA+bZhoWXBRgbGNTMk+yimE=
github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ=
github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
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/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
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-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
@@ -124,21 +171,31 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S
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/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc=
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f h1:PgA+Olipyj258EIEYnpFFONrrCcAIWNUNoFhUfMqAGY=
github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba/go.mod h1:iLnlXG2Pakcii2CU0cbY07DRCSvpWNa7nFxtevhOChk=
github.com/tc-hib/winres v0.1.5 h1:2dA5yfjdoEA3UyRaOC92HNMt3jap66pLzoW4MjpC/0M=
github.com/tc-hib/winres v0.1.5/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A=
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=
@@ -153,10 +210,25 @@ github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.7 h1:sgVPwu/yygHJ2m1pJDLgGM/h+1F5odx5Q9ljG3imRm8=
github.com/tidwall/sjson v1.1.7/go.mod h1:w/yG+ezBeTdUxiKs5NcPicO9diP38nk96QBAbIIGeFs=
github.com/tkrajina/go-reflector v0.5.4 h1:dS9aJEa/eYNQU/fwsb5CSiATOxcNyA/gG/A7a582D5s=
github.com/tkrajina/go-reflector v0.5.4/go.mod h1:9PyLgEOzc78ey/JmQQHbW8cQJ1oucLlNQsg8yFvkVk8=
github.com/tkrajina/typescriptify-golang-structs v0.1.6 h1:AlUeArKYvOdsl0wL7VKtRDgBqT7Hvk87w7hYGrITcqg=
github.com/tkrajina/typescriptify-golang-structs v0.1.6/go.mod h1:mfb2iqie4FjTKHfbjjCp08SRphjYaM7f2LdfUcNP7wY=
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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasthttp v1.26.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4=
github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/wailsapp/wails v1.16.6 h1:iN0tP0O/Gwr8SKWwgH4t+IqDlMCGeVquWoHTegk8JlQ=
github.com/wailsapp/wails v1.16.6/go.mod h1:aADbAvTzZrKGd4Td7d1l4Dp5Hx7lLJEvVH7guIHoDf8=
github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c=
github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28=
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY=
@@ -166,28 +238,39 @@ github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6e
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=
github.com/ztrue/tracerr v0.3.0 h1:lDi6EgEYhPYPnKcjsYzmWw4EkFEoA/gfe+I9Y5f+h6Y=
github.com/ztrue/tracerr v0.3.0/go.mod h1:qEalzze4VN9O8tnhBXScfCrmoJo10o8TN5ciKjm6Mww=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
golang.org/x/mod v0.4.1/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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -195,20 +278,27 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 h1:faDu4veV+8pcThn4fewv6TVlNCezafGoC1gM/mxQLbQ=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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=
@@ -222,6 +312,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/AlecAivazis/survey.v1 v1.8.4/go.mod h1:iBNOmqKz/NUbZx3bA+4hAGLRC7fSK7tgtVDT4tB22XA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -234,6 +325,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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=
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=

View File

@@ -1,4 +1,5 @@
// +build debug
//go:build dev
// +build dev
package app
@@ -14,11 +15,6 @@ 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
loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
flag.Parse()

View File

@@ -1,3 +1,4 @@
//go:build desktop && !server
// +build desktop,!server
package app
@@ -11,7 +12,6 @@ import (
"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"
@@ -46,12 +46,8 @@ type App struct {
// This is our binding DB
bindings *binding.Bindings
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
// OnStartup/OnShutdown
startupCallback func(ctx context.Context)
shutdownCallback func()
}
@@ -69,7 +65,8 @@ func CreateApp(appoptions *options.App) (*App, error) {
menuManager := menumanager.NewManager()
// Process the application menu
menuManager.SetApplicationMenu(options.GetApplicationMenu(appoptions))
appMenu := options.GetApplicationMenu(appoptions)
menuManager.SetApplicationMenu(appMenu)
// Process context menus
contextMenus := options.GetContextMenus(appoptions)
@@ -86,7 +83,7 @@ func CreateApp(appoptions *options.App) (*App, error) {
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
bindingExemptions := []interface{}{appoptions.OnStartup, appoptions.OnShutdown, appoptions.OnDomReady}
result := &App{
appType: "desktop",
@@ -95,8 +92,8 @@ func CreateApp(appoptions *options.App) (*App, error) {
logger: myLogger,
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
menuManager: menuManager,
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
startupCallback: appoptions.OnStartup,
shutdownCallback: appoptions.OnShutdown,
}
result.options = appoptions
@@ -144,12 +141,8 @@ func (a *App) Run() error {
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)
log, err := subsystem.NewLog(a.servicebus, a.logger)
if err != nil {
return err
}
@@ -206,7 +199,7 @@ func (a *App) Run() error {
}
// Start the call subsystem
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB())
if err != nil {
return err
}
@@ -254,7 +247,7 @@ func (a *App) Run() error {
return err
}
// Shutdown callback
// OnShutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}

View File

@@ -1,9 +1,11 @@
//go:build dev
// +build dev
package app
import (
"context"
"github.com/wailsapp/wails/runtime"
"sync"
"github.com/wailsapp/wails/v2/internal/bridge"
@@ -14,7 +16,6 @@ import (
"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"
@@ -50,7 +51,7 @@ type App struct {
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
// OnStartup/OnShutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
@@ -86,15 +87,15 @@ func CreateApp(appoptions *options.App) (*App, error) {
}
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
bindingExemptions := []interface{}{appoptions.OnStartup, appoptions.OnShutdown, appoptions.OnDomReady}
result := &App{
appType: "dev",
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
logger: myLogger,
servicebus: servicebus.New(myLogger),
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
startupCallback: appoptions.OnStartup,
shutdownCallback: appoptions.OnShutdown,
bridge: bridge.NewBridge(myLogger),
menuManager: menuManager,
}
@@ -185,7 +186,7 @@ func (a *App) Run() error {
}
// Start the call subsystem
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB())
if err != nil {
return err
}
@@ -236,7 +237,7 @@ func (a *App) Run() error {
return err
}
// Shutdown callback
// OnShutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}

View File

@@ -9,11 +9,18 @@ import (
func (a *App) PreflightChecks(options *options.App) error {
_ = options
// Process the webview2 runtime situation. We can pass a strategy in via the `webview2` flag for `wails build`.
// This will determine how wv2runtime.Process will handle a lack of valid runtime.
err := wv2runtime.Process()
installedVersion, err := wv2runtime.Process()
if installedVersion != nil {
a.logger.Debug("WebView2 Runtime installed: Name: '%s' Version:'%s' Location:'%s'. Minimum version required: %s.",
installedVersion.Name, installedVersion.Version, installedVersion.Location, wv2runtime.MinimumRuntimeVersion)
}
if err != nil {
return err
}
return nil
}

View File

@@ -1,4 +1,5 @@
// +build !debug
//go:build production
// +build production
package app

View File

@@ -1,8 +1,10 @@
//go:build server && !desktop
// +build server,!desktop
package app
import (
"context"
"os"
"path/filepath"
@@ -12,7 +14,6 @@ import (
"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"
@@ -26,7 +27,6 @@ type App struct {
call *subsystem.Call
event *subsystem.Event
log *subsystem.Log
runtime *subsystem.Runtime
options *options.App
@@ -38,12 +38,8 @@ type App struct {
debug bool
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
// OnStartup/OnShutdown
startupCallback func(ctx context.Context)
shutdownCallback func()
}
@@ -63,8 +59,8 @@ func CreateApp(appoptions *options.App) (*App, error) {
logger: myLogger,
servicebus: servicebus.New(myLogger),
webserver: webserver.NewWebServer(myLogger),
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
startupCallback: appoptions.OnStartup,
shutdownCallback: appoptions.OnShutdown,
}
// Initialise app
@@ -109,19 +105,15 @@ func (a *App) Run() error {
}
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback)
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)
log, err := subsystem.NewLog(a.servicebus, a.logger)
if err != nil {
return err
}
@@ -135,7 +127,7 @@ func (a *App) Run() error {
a.dispatcher.Start()
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings)
if err != nil {
return err
}

View File

@@ -0,0 +1,29 @@
//go:build !dev && !production && windows
package appng
import (
"github.com/leaanthony/winc/w32"
"github.com/wailsapp/wails/v2/pkg/options"
"os/exec"
)
// App defines a Wails application structure
type App struct{}
func (a *App) Run() error {
return nil
}
// CreateApp creates the app!
func CreateApp(_ *options.App) (*App, error) {
result := w32.MessageBox(0,
`Wails applications will not build without the correct build tags.
Please use "wails build" or press "OK" to open the documentation on how to use "go build"`,
"Error",
w32.MB_ICONERROR|w32.MB_OKCANCEL)
if result == 1 {
exec.Command("rundll32", "url.dll,FileProtocolHandler", "https://wails.io").Start()
}
return nil, nil
}

View File

@@ -0,0 +1,166 @@
//go:build dev
// +build dev
package appng
import (
"context"
"flag"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
"github.com/wailsapp/wails/v2/internal/frontend/devserver"
"github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/internal/signal"
pkglogger "github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"os"
"path/filepath"
)
// App defines a Wails application structure
type App struct {
frontend frontend.Frontend
logger *logger.Logger
signal *signal.Manager
options *options.App
menuManager *menumanager.Manager
// Indicates if the app is in debug mode
debug bool
// OnStartup/OnShutdown
startupCallback func(ctx context.Context)
shutdownCallback func(ctx context.Context)
ctx context.Context
}
func (a *App) Run() error {
err := a.frontend.Run(a.ctx)
if a.shutdownCallback != nil {
a.shutdownCallback(a.ctx)
}
return err
}
// CreateApp creates the app!
func CreateApp(appoptions *options.App) (*App, error) {
var err error
ctx := context.WithValue(context.Background(), "debug", true)
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
// Check for CLI Flags
assetdir := flag.String("assetdir", "", "Directory to serve assets")
loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
flag.Parse()
if assetdir != nil && *assetdir != "" {
ctx = context.WithValue(ctx, "assetdir", *assetdir)
}
if loglevel != nil && *loglevel != "" {
level, err := pkglogger.StringToLogLevel(*loglevel)
if err != nil {
return nil, err
}
myLogger.SetLogLevel(level)
}
// Attach logger to context
ctx = context.WithValue(ctx, "logger", myLogger)
// Preflight checks
err = PreflightChecks(appoptions, myLogger)
if err != nil {
return nil, err
}
// Merge default options
options.MergeDefaults(appoptions)
var menuManager *menumanager.Manager
// Process the application menu
if appoptions.Menu != nil {
// Create the menu manager
menuManager = menumanager.NewManager()
err = menuManager.SetApplicationMenu(appoptions.Menu)
if err != nil {
return nil, err
}
}
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.OnStartup, appoptions.OnShutdown, appoptions.OnDomReady}
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions)
err = generateBindings(appBindings)
if err != nil {
return nil, err
}
eventHandler := runtime.NewEvents(myLogger)
ctx = context.WithValue(ctx, "events", eventHandler)
messageDispatcher := dispatcher.NewDispatcher(myLogger, appBindings, eventHandler)
// Create the frontends and register to event handler
desktopFrontend := desktop.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher)
appFrontend := devserver.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher, menuManager, desktopFrontend)
eventHandler.AddFrontend(appFrontend)
eventHandler.AddFrontend(desktopFrontend)
result := &App{
ctx: ctx,
frontend: appFrontend,
logger: myLogger,
menuManager: menuManager,
startupCallback: appoptions.OnStartup,
shutdownCallback: appoptions.OnShutdown,
debug: true,
}
result.options = appoptions
return result, nil
}
func generateBindings(bindings *binding.Bindings) error {
cwd, err := os.Getwd()
if err != nil {
return err
}
projectConfig, err := project.Load(cwd)
if err != nil {
return err
}
targetDir := filepath.Join(projectConfig.WailsJSDir, "wailsjs", "go")
err = os.RemoveAll(targetDir)
if err != nil {
return err
}
_ = fs.MkDirs(targetDir)
modelsFile := filepath.Join(targetDir, "models.ts")
err = bindings.WriteTS(modelsFile)
if err != nil {
return err
}
// Write backend method wrappers
bindingsFilename := filepath.Join(targetDir, "bindings.js")
err = bindings.GenerateBackendJS(bindingsFilename)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,101 @@
//go:build production
// +build production
package appng
import (
"context"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
"github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/signal"
"github.com/wailsapp/wails/v2/pkg/options"
)
// App defines a Wails application structure
type App struct {
frontend frontend.Frontend
logger *logger.Logger
signal *signal.Manager
options *options.App
menuManager *menumanager.Manager
// Indicates if the app is in debug mode
debug bool
// OnStartup/OnShutdown
startupCallback func(ctx context.Context)
shutdownCallback func(ctx context.Context)
ctx context.Context
}
func (a *App) Run() error {
err := a.frontend.Run(a.ctx)
if a.shutdownCallback != nil {
a.shutdownCallback(a.ctx)
}
return err
}
// CreateApp creates the app!
func CreateApp(appoptions *options.App) (*App, error) {
var err error
ctx := context.Background()
// Merge default options
options.MergeDefaults(appoptions)
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
// Preflight Checks
err = PreflightChecks(appoptions, myLogger)
if err != nil {
return nil, err
}
// Create the menu manager
menuManager := menumanager.NewManager()
// Process the application menu
if appoptions.Menu != nil {
err = menuManager.SetApplicationMenu(appoptions.Menu)
if err != nil {
return nil, err
}
}
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.OnStartup, appoptions.OnShutdown, appoptions.OnDomReady}
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions)
eventHandler := runtime.NewEvents(myLogger)
ctx = context.WithValue(ctx, "events", eventHandler)
messageDispatcher := dispatcher.NewDispatcher(myLogger, appBindings, eventHandler)
appFrontend := desktop.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher)
eventHandler.AddFrontend(appFrontend)
result := &App{
ctx: ctx,
frontend: appFrontend,
logger: myLogger,
menuManager: menuManager,
startupCallback: appoptions.OnStartup,
shutdownCallback: appoptions.OnShutdown,
debug: false,
}
result.options = appoptions
result.ctx = context.WithValue(result.ctx, "debug", result.debug)
return result, nil
}

View File

@@ -0,0 +1,27 @@
//go:build windows
package appng
import (
"github.com/wailsapp/wails/v2/internal/ffenestri/windows/wv2runtime"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
func PreflightChecks(options *options.App, logger *logger.Logger) error {
_ = options
// Process the webview2 runtime situation. We can pass a strategy in via the `webview2` flag for `wails build`.
// This will determine how wv2runtime.Process will handle a lack of valid runtime.
installedVersion, err := wv2runtime.Process()
if installedVersion != nil {
logger.Debug("WebView2 Runtime installed: Name: '%s' Version:'%s' Location:'%s'. Minimum version required: %s.",
installedVersion.Name, installedVersion.Version, installedVersion.Location, wv2runtime.MinimumRuntimeVersion)
}
if err != nil {
return err
}
return nil
}

View File

@@ -1,11 +1,10 @@
{
"name": "backend",
"name": "go",
"version": "1.0.0",
"description": "Package to wrap backend method calls",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"description": "Package to wrap your bound go methods",
"main": "bindings.js",
"types": "bindings.d.ts",
"scripts": {},
"author": "",
"license": "ISC"
}

View File

@@ -2,12 +2,12 @@ package binding
import (
"fmt"
"github.com/tkrajina/typescriptify-golang-structs/typescriptify"
"reflect"
"runtime"
"strings"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger"
)
@@ -15,15 +15,25 @@ type Bindings struct {
db *DB
logger logger.CustomLogger
exemptions slicer.StringSlicer
// Typescript writer
converter *typescriptify.TypeScriptify
}
// NewBindings returns a new Bindings object
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
result := &Bindings{
db: newDB(),
logger: logger.CustomLogger("Bindings"),
db: newDB(),
logger: logger.CustomLogger("Bindings"),
converter: typescriptify.New(),
}
// No backups
result.converter.WithBackupDir("")
// Hack for TS compilation error
result.converter.AddImport("export {};")
for _, exemption := range exemptions {
if exemptions == nil {
continue
@@ -65,6 +75,11 @@ func (b *Bindings) Add(structPtr interface{}) error {
return nil
}
func (b *Bindings) WriteTS(filename string) error {
println("WriteTS to:", filename)
return b.converter.ConvertToFile(filename)
}
func (b *Bindings) DB() *DB {
return b.db
}

View File

@@ -4,20 +4,18 @@ import (
"bytes"
_ "embed"
"fmt"
"log"
"github.com/wailsapp/wails/v2/internal/fs"
"os"
"path/filepath"
"strings"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/leaanthony/slicer"
)
//go:embed assets/package.json
var packageJSON []byte
func (b *Bindings) GenerateBackendJS() {
func (b *Bindings) GenerateBackendJS(targetfile string) error {
store := b.db.store
var output bytes.Buffer
@@ -26,7 +24,7 @@ func (b *Bindings) GenerateBackendJS() {
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
const backend = {`)
const go = {`)
output.WriteString("\n")
var sortedPackageNames slicer.StringSlicer
@@ -84,7 +82,7 @@ const backend = {`)
argsString := args.Join(", ")
output.WriteString(fmt.Sprintf(" \"%s\": (%s) => {", methodName, argsString))
output.WriteString("\n")
output.WriteString(fmt.Sprintf(" return window.backend.%s.%s.%s(%s);", packageName, structName, methodName, argsString))
output.WriteString(fmt.Sprintf(" return window.go.%s.%s.%s(%s);", packageName, structName, methodName, argsString))
output.WriteString("\n")
output.WriteString(fmt.Sprintf(" },"))
output.WriteString("\n")
@@ -100,35 +98,19 @@ const backend = {`)
})
output.WriteString(`};
export default backend;`)
export default go;`)
output.WriteString("\n")
// TODO: Make this configurable in wails.json
dirname, err := fs.RelativeToCwd("frontend/src/backend")
if err != nil {
log.Fatal(err)
}
if !fs.DirExists(dirname) {
err := fs.MkDirs(dirname)
if err != nil {
log.Fatal(err)
}
}
packageJsonFile := filepath.Join(dirname, "package.json")
dir := filepath.Dir(targetfile)
packageJsonFile := filepath.Join(dir, "package.json")
if !fs.FileExists(packageJsonFile) {
err := os.WriteFile(packageJsonFile, packageJSON, 0755)
if err != nil {
log.Fatal(err)
return err
}
}
filename := filepath.Join(dirname, "index.js")
err = os.WriteFile(filename, output.Bytes(), 0755)
if err != nil {
log.Fatal(err)
}
return os.WriteFile(targetfile, output.Bytes(), 0755)
}
func goTypeToJSDocType(input string) string {
@@ -150,6 +132,9 @@ func goTypeToJSDocType(input string) string {
arrayType := goTypeToJSDocType(input[2:])
return "Array.<" + arrayType + ">"
default:
if strings.ContainsRune(input, '.') {
return strings.Split(input, ".")[1]
}
return "any"
}
}

View File

@@ -77,6 +77,24 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ {
input := methodType.In(inputIndex)
thisParam := newParameter("", input)
// Process struct pointer params
if input.Kind() == reflect.Ptr {
if input.Elem().Kind() == reflect.Struct {
typ := input.Elem()
a := reflect.New(typ)
s := reflect.Indirect(a).Interface()
b.converter.Add(s)
}
}
// Process struct params
if input.Kind() == reflect.Struct {
a := reflect.New(input)
s := reflect.Indirect(a).Interface()
b.converter.Add(s)
}
inputs = append(inputs, thisParam)
}

View File

@@ -1,7 +1,7 @@
package bridge
import (
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
type BridgeClient struct {
@@ -27,30 +27,30 @@ func (b BridgeClient) Quit() {
func (b BridgeClient) NotifyEvent(message string) {
b.session.sendMessage("n" + message)
b.session.log.Info("NotifyEvent: %s", message)
b.session.log.Info("Notify: %s", message)
}
func (b BridgeClient) CallResult(message string) {
b.session.sendMessage("c" + message)
}
func (b BridgeClient) OpenFileDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (b BridgeClient) OpenFileDialog(dialogOptions runtime.OpenDialogOptions, callbackID string) {
// Handled by dialog_client
}
func (b BridgeClient) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (b BridgeClient) OpenMultipleFilesDialog(dialogOptions runtime.OpenDialogOptions, callbackID string) {
// Handled by dialog_client
}
func (b BridgeClient) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (b BridgeClient) OpenDirectoryDialog(dialogOptions runtime.OpenDialogOptions, callbackID string) {
// Handled by dialog_client
}
func (b BridgeClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
func (b BridgeClient) SaveDialog(dialogOptions runtime.SaveDialogOptions, callbackID string) {
// Handled by dialog_client
}
func (b BridgeClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
func (b BridgeClient) MessageDialog(dialogOptions runtime.MessageDialogOptions, callbackID string) {
// Handled by dialog_client
}
@@ -118,8 +118,8 @@ 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) MenuSetApplicationMenu(menuJSON string) {
b.session.log.Info("MenuSetApplicationMenu unsupported in Bridge mode")
}
func (b BridgeClient) SetTrayMenu(trayMenuJSON string) {

View File

@@ -2,6 +2,7 @@ package bridge
import (
"fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
"os/exec"
"strconv"
"strings"
@@ -11,7 +12,6 @@ import (
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
)
type DialogClient struct {
@@ -37,17 +37,17 @@ func (d *DialogClient) NotifyEvent(message string) {
func (d *DialogClient) CallResult(message string) {
}
func (d *DialogClient) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (d *DialogClient) OpenDirectoryDialog(options runtime.OpenDialogOptions, callbackID string) {
}
func (d *DialogClient) OpenFileDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (d *DialogClient) OpenFileDialog(options runtime.OpenDialogOptions, callbackID string) {
}
func (d *DialogClient) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (d *DialogClient) OpenMultipleFilesDialog(options runtime.OpenDialogOptions, callbackID string) {
}
func (d *DialogClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
func (d *DialogClient) SaveDialog(options runtime.SaveDialogOptions, callbackID string) {
}
func (d *DialogClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
func (d *DialogClient) MessageDialog(options runtime.MessageDialogOptions, callbackID string) {
osa, err := exec.LookPath("osascript")
if err != nil {
@@ -58,23 +58,23 @@ func (d *DialogClient) MessageDialog(dialogOptions *dialog.MessageDialog, callba
var btns slicer.StringSlicer
defaultButton := ""
cancelButton := ""
for index, btn := range dialogOptions.Buttons {
for index, btn := range options.Buttons {
btns.Add(strconv.Quote(btn))
if btn == dialogOptions.DefaultButton {
if btn == options.DefaultButton {
defaultButton = fmt.Sprintf("default button %d", index+1)
}
if btn == dialogOptions.CancelButton {
if btn == options.CancelButton {
cancelButton = fmt.Sprintf("cancel button %d", index+1)
}
}
buttons := "{" + btns.Join(",") + "}"
script := fmt.Sprintf("display dialog \"%s\" buttons %s %s %s with title \"%s\"", dialogOptions.Message, buttons, defaultButton, cancelButton, dialogOptions.Title)
script := fmt.Sprintf("display dialog \"%s\" buttons %s %s %s with title \"%s\"", options.Message, buttons, defaultButton, cancelButton, options.Title)
go func() {
out, err := exec.Command(osa, "-e", script).Output()
if err != nil {
// Assume user has pressed cancel button
if dialogOptions.CancelButton != "" {
d.dispatcher.DispatchMessage("DM" + callbackID + "|" + dialogOptions.CancelButton)
if options.CancelButton != "" {
d.dispatcher.DispatchMessage("DM" + callbackID + "|" + options.CancelButton)
return
}
d.log.Error("Dialog had bad exit code. If this was a Cancel button, add 'CancelButton' to the dialog.MessageDialog struct. Error: %s", err.Error())

View File

@@ -0,0 +1,7 @@
## Windows
- Left and Right Win keys act the same
- Accelerators will automatically add appropriate text into the menu items. This can be prevented by adding a tab
character to the menu label
- Tooltips + styling with font currently unsupported
-

View File

@@ -46,7 +46,7 @@ extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
extern void WebviewIsTransparent(struct Application*);
extern void WindowBackgroundIsTranslucent(struct Application*);
extern void WindowIsTranslucent(struct Application*);
extern void* GetWindowHandle(struct Application*);
#ifdef __cplusplus

View File

@@ -8,11 +8,11 @@ package ffenestri
import "C"
import (
"runtime"
goruntime "runtime"
"strconv"
"strings"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/pkg/runtime"
"github.com/wailsapp/wails/v2/internal/logger"
)
@@ -127,9 +127,9 @@ func (c *Client) WindowSetColour(colour int) {
}
// OpenFileDialog will open a dialog with the given title and filter
func (c *Client) OpenFileDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (c *Client) OpenFileDialog(dialogOptions runtime.OpenDialogOptions, callbackID string) {
filters := []string{}
if runtime.GOOS == "darwin" {
if goruntime.GOOS == "darwin" {
for _, filter := range dialogOptions.Filters {
filters = append(filters, strings.Split(filter.Pattern, ",")...)
}
@@ -150,11 +150,10 @@ func (c *Client) OpenFileDialog(dialogOptions *dialog.OpenDialog, callbackID str
)
}
// OpenDirectoryDialog will open a dialog with the given title and filter
func (c *Client) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (c *Client) OpenDirectoryDialog(dialogOptions runtime.OpenDialogOptions, callbackID string) {
filters := []string{}
if runtime.GOOS == "darwin" {
if goruntime.GOOS == "darwin" {
for _, filter := range dialogOptions.Filters {
filters = append(filters, strings.Split(filter.Pattern, ",")...)
}
@@ -166,7 +165,7 @@ func (c *Client) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackI
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(false), // Files
c.app.bool2Cint(true), // Directories
c.app.bool2Cint(true), // Directories
c.app.bool2Cint(false), // Multiple
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
@@ -176,9 +175,9 @@ func (c *Client) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackI
}
// OpenMultipleFilesDialog will open a dialog with the given title and filter
func (c *Client) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (c *Client) OpenMultipleFilesDialog(dialogOptions runtime.OpenDialogOptions, callbackID string) {
filters := []string{}
if runtime.GOOS == "darwin" {
if goruntime.GOOS == "darwin" {
for _, filter := range dialogOptions.Filters {
filters = append(filters, strings.Split(filter.Pattern, ",")...)
}
@@ -200,9 +199,9 @@ func (c *Client) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callb
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
func (c *Client) SaveDialog(dialogOptions *runtime.SaveDialogOptions, callbackID string) {
filters := []string{}
if runtime.GOOS == "darwin" {
if goruntime.GOOS == "darwin" {
for _, filter := range dialogOptions.Filters {
filters = append(filters, strings.Split(filter.Pattern, ",")...)
}
@@ -220,7 +219,7 @@ func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string)
}
// MessageDialog will open a message dialog with the given options
func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
func (c *Client) MessageDialog(dialogOptions runtime.MessageDialogOptions, callbackID string) {
// Sanity check button length
if len(dialogOptions.Buttons) > 4 {

View File

@@ -10,13 +10,12 @@ import "C"
import (
"encoding/json"
"github.com/leaanthony/go-common-file-dialog/cfd"
"github.com/wailsapp/wails/v2/pkg/runtime"
"golang.org/x/sys/windows"
"log"
"strconv"
"syscall"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger"
)
@@ -131,7 +130,7 @@ func (c *Client) WindowSetColour(colour int) {
C.SetColour(c.app.app, r, g, b, a)
}
func convertFilters(filters []dialog.FileFilter) []cfd.FileFilter {
func convertFilters(filters []runtime.FileFilter) []cfd.FileFilter {
var result []cfd.FileFilter
for _, filter := range filters {
result = append(result, cfd.FileFilter(filter))
@@ -140,7 +139,7 @@ func convertFilters(filters []dialog.FileFilter) []cfd.FileFilter {
}
// OpenFileDialog will open a dialog with the given title and filter
func (c *Client) OpenFileDialog(options *dialog.OpenDialog, callbackID string) {
func (c *Client) OpenFileDialog(options runtime.OpenDialogOptions, callbackID string) {
config := cfd.DialogConfig{
Folder: options.DefaultDirectory,
FileFilters: convertFilters(options.Filters),
@@ -166,7 +165,7 @@ func (c *Client) OpenFileDialog(options *dialog.OpenDialog, callbackID string) {
}
// OpenDirectoryDialog will open a dialog with the given title and filter
func (c *Client) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (c *Client) OpenDirectoryDialog(dialogOptions runtime.OpenDialogOptions, callbackID string) {
config := cfd.DialogConfig{
Title: dialogOptions.Title,
Role: "PickFolder",
@@ -191,7 +190,7 @@ func (c *Client) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackI
}
// OpenMultipleFilesDialog will open a dialog with the given title and filter
func (c *Client) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
func (c *Client) OpenMultipleFilesDialog(dialogOptions runtime.OpenDialogOptions, callbackID string) {
config := cfd.DialogConfig{
Title: dialogOptions.Title,
Role: "OpenMultipleFiles",
@@ -222,7 +221,7 @@ func (c *Client) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callb
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
func (c *Client) SaveDialog(dialogOptions runtime.SaveDialogOptions, callbackID string) {
saveDialog, err := cfd.NewSaveFileDialog(cfd.DialogConfig{
Title: dialogOptions.Title,
Role: "SaveFile",
@@ -246,7 +245,7 @@ func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string)
}
// MessageDialog will open a message dialog with the given options
func (c *Client) MessageDialog(options *dialog.MessageDialog, callbackID string) {
func (c *Client) MessageDialog(options runtime.MessageDialogOptions, callbackID string) {
title, err := syscall.UTF16PtrFromString(options.Title)
if err != nil {
@@ -258,13 +257,13 @@ func (c *Client) MessageDialog(options *dialog.MessageDialog, callbackID string)
}
var flags uint32
switch options.Type {
case dialog.InfoDialog:
case runtime.InfoDialog:
flags = windows.MB_OK | windows.MB_ICONINFORMATION
case dialog.ErrorDialog:
case runtime.ErrorDialog:
flags = windows.MB_ICONERROR | windows.MB_OK
case dialog.QuestionDialog:
case runtime.QuestionDialog:
flags = windows.MB_YESNO
case dialog.WarningDialog:
case runtime.WarningDialog:
flags = windows.MB_OK | windows.MB_ICONWARNING
}
@@ -284,8 +283,8 @@ func (c *Client) DarkModeEnabled(callbackID string) {
}
// SetApplicationMenu sets the application menu
func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
func (c *Client) SetApplicationMenu(_ string) {
c.updateApplicationMenu()
}
// SetTrayMenu sets the tray menu

View File

@@ -135,7 +135,7 @@ struct Application {
int fullSizeContent;
int useToolBar;
int hideToolbarSeparator;
int windowBackgroundIsTranslucent;
int WindowIsTranslucent;
int hasURLHandlers;
const char *startupURL;
@@ -293,8 +293,8 @@ void Show(struct Application *app) {
);
}
void WindowBackgroundIsTranslucent(struct Application *app) {
app->windowBackgroundIsTranslucent = 1;
void WindowIsTranslucent(struct Application *app) {
app->WindowIsTranslucent = 1;
}
// Sends messages to the backend
@@ -1534,7 +1534,7 @@ void Run(struct Application *app, int argc, char **argv) {
applyWindowColour(app);
// Process translucency
if (app->windowBackgroundIsTranslucent) {
if (app->WindowIsTranslucent) {
makeWindowBackgroundTranslucent(app);
}
@@ -1775,7 +1775,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->useToolBar = 0;
result->hideToolbarSeparator = 0;
result->appearance = NULL;
result->windowBackgroundIsTranslucent = 0;
result->WindowIsTranslucent = 0;
// Window data
result->delegate = NULL;

View File

@@ -57,8 +57,8 @@ func (a *Application) processPlatformSettings() error {
}
// Check if window should be translucent
if mac.WindowBackgroundIsTranslucent {
C.WindowBackgroundIsTranslucent(a.app)
if mac.WindowIsTranslucent {
C.WindowIsTranslucent(a.app)
}
// Process menu

View File

@@ -138,7 +138,7 @@ 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 WindowIsTranslucent(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 *);

View File

@@ -862,7 +862,7 @@ void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {}
void AddContextMenu(struct Application* app, char *contextMenuJSON) {}
void UpdateContextMenu(struct Application* app, char *contextMenuJSON) {}
void WebviewIsTransparent(struct Application* app) {}
void WindowBackgroundIsTranslucent(struct Application* app) {}
void WindowIsTranslucent(struct Application* app) {}
void OpenDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {}
void SaveDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {}
void MessageDialog(struct Application* app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {}

View File

@@ -2,6 +2,7 @@
// License included in README.md
#include "ffenestri_windows.h"
#include "shellscalingapi.h"
#include "wv2ComHandler_windows.h"
#include <functional>
#include <atomic>
@@ -27,6 +28,25 @@ void dispatch(dispatchFunction func) {
PostThreadMessage(mainThread, WM_APP, 0, (LPARAM) new dispatchFunction(func));
}
void processKeyPress(UINT key) {
// Get state of Control
bool controlPressed = GetKeyState(VK_CONTROL) >> 15 != 0;
bool altPressed = GetKeyState(VK_MENU) >> 15 != 0;
bool shiftPressed = GetKeyState(VK_SHIFT) >> 15 != 0;
// Save the modifier keys
BYTE modState = 0;
if ( GetKeyState(VK_CONTROL) >> 15 != 0 ) { modState |= 1; }
if ( GetKeyState(VK_MENU) >> 15 != 0 ) { modState |= 2; }
if ( GetKeyState(VK_SHIFT) >> 15 != 0 ) { modState |= 4; }
if ( GetKeyState(VK_LWIN) >> 15 != 0 ) { modState |= 8; }
if ( GetKeyState(VK_RWIN) >> 15 != 0 ) { modState |= 8; }
// Notify app of keypress
handleKeypressInGo(key, modState);
}
LPWSTR cstrToLPWSTR(const char *cstr) {
int wchars_num = MultiByteToWideChar( CP_UTF8 , 0 , cstr , -1, NULL , 0 );
wchar_t* wstr = new wchar_t[wchars_num+1];
@@ -42,6 +62,30 @@ char* LPWSTRToCstr(LPWSTR input) {
return output;
}
// Credit: https://building.enlyze.com/posts/writing-win32-apps-like-its-2020-part-3/
typedef int (__cdecl *PGetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE,UINT*,UINT*);
void getDPIForWindow(struct Application *app)
{
HMODULE hShcore = LoadLibraryW(L"shcore");
if (hShcore)
{
PGetDpiForMonitor pGetDpiForMonitor = reinterpret_cast<PGetDpiForMonitor>(GetProcAddress(hShcore, "GetDpiForMonitor"));
if (pGetDpiForMonitor)
{
HMONITOR hMonitor = MonitorFromWindow(app->window, MONITOR_DEFAULTTOPRIMARY);
pGetDpiForMonitor(hMonitor, (MONITOR_DPI_TYPE)0, &app->dpix, &app->dpiy);
}
} else {
// We couldn't get the window's DPI above, so get the DPI of the primary monitor
// using an API that is available in all Windows versions.
HDC hScreenDC = GetDC(0);
app->dpix = GetDeviceCaps(hScreenDC, LOGPIXELSX);
app->dpiy = GetDeviceCaps(hScreenDC, LOGPIXELSY);
ReleaseDC(0, hScreenDC);
}
}
struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
// Create application
@@ -61,7 +105,7 @@ struct Application *NewApplication(const char *title, int width, int height, int
result->logLevel = logLevel;
result->hideWindowOnClose = hideWindowOnClose;
result->webviewIsTranparent = false;
result->windowBackgroundIsTranslucent = false;
result->WindowIsTranslucent = false;
result->disableWindowIcon = false;
// Min/Max Width/Height
@@ -88,6 +132,9 @@ struct Application *NewApplication(const char *title, int width, int height, int
// Used to remember the window location when going fullscreen
result->previousPlacement = { sizeof(result->previousPlacement) };
// DPI
result->dpix = result->dpiy = 0;
return result;
}
@@ -142,6 +189,14 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_CREATE: {
createApplicationMenu(hwnd);
break;
}
case WM_COMMAND:
menuClicked(LOWORD(wParam));
break;
case WM_CLOSE: {
DestroyWindow( app->window );
break;
@@ -165,36 +220,39 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
}
break;
}
case WM_KEYDOWN:
// This is needed because webview2 is sometimes not in focus
// https://github.com/MicrosoftEdge/WebView2Feedback/issues/1541
processKeyPress(wParam);
break;
case WM_GETMINMAXINFO: {
// Exit early if this is called before the window is created.
if ( app == NULL ) {
return 0;
}
// get pixel density
HDC hDC = GetDC(NULL);
double DPIScaleX = GetDeviceCaps(hDC, 88)/96.0;
double DPIScaleY = GetDeviceCaps(hDC, 90)/96.0;
ReleaseDC(NULL, hDC);
// update DPI
getDPIForWindow(app);
double DPIScaleX = app->dpix/96.0;
double DPIScaleY = app->dpiy/96.0;
RECT rcClient, rcWind;
RECT rcWind;
POINT ptDiff;
GetClientRect(hwnd, &rcClient);
GetWindowRect(hwnd, &rcWind);
int widthExtra = (rcWind.right - rcWind.left) - rcClient.right;
int heightExtra = (rcWind.bottom - rcWind.top) - rcClient.bottom;
int widthExtra = (rcWind.right - rcWind.left);
int heightExtra = (rcWind.bottom - rcWind.top);
LPMINMAXINFO mmi = (LPMINMAXINFO) lParam;
if (app->minWidth > 0 && app->minHeight > 0) {
mmi->ptMinTrackSize.x = app->minWidth * DPIScaleX + widthExtra;
mmi->ptMinTrackSize.y = app->minHeight * DPIScaleY + heightExtra;
mmi->ptMinTrackSize.x = app->minWidth * DPIScaleX;
mmi->ptMinTrackSize.y = app->minHeight * DPIScaleY;
}
if (app->maxWidth > 0 && app->maxHeight > 0) {
mmi->ptMaxSize.x = app->maxWidth * DPIScaleX + widthExtra;
mmi->ptMaxSize.y = app->maxHeight * DPIScaleY + heightExtra;
mmi->ptMaxTrackSize.x = app->maxWidth * DPIScaleX + widthExtra;
mmi->ptMaxTrackSize.y = app->maxHeight * DPIScaleY + heightExtra;
mmi->ptMaxSize.x = app->maxWidth * DPIScaleX;
mmi->ptMaxSize.y = app->maxHeight * DPIScaleY;
mmi->ptMaxTrackSize.x = app->maxWidth * DPIScaleX;
mmi->ptMaxTrackSize.y = app->maxHeight * DPIScaleY;
}
return 0;
}
@@ -294,7 +352,6 @@ void completed(struct Application* app) {
messageFromWindowCallback(readyMessage.c_str());
}
//
bool initWebView2(struct Application *app, int debugEnabled, messageCallback cb) {
@@ -331,6 +388,7 @@ bool initWebView2(struct Application *app, int debugEnabled, messageCallback cb)
}
// Fix for invisible webview
if( app->startHidden ) {}
controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
flag.clear();
}));
if (!SUCCEEDED(res))
@@ -384,6 +442,7 @@ bool initWebView2(struct Application *app, int debugEnabled, messageCallback cb)
}
app->webviewController = controller;
app->webview = webview;
// Resize WebView to fit the bounds of the parent window
RECT bounds;
GetClientRect(app->window, &bounds);
@@ -395,7 +454,64 @@ bool initWebView2(struct Application *app, int debugEnabled, messageCallback cb)
LPCWSTR html = (LPCWSTR) cstrToLPWSTR((char*)assets[0]);
app->webview->Navigate(html);
if( app->webviewIsTranparent ) {
wchar_t szBuff[64];
ICoreWebView2Controller2 *wc2;
wc2 = nullptr;
app->webviewController->QueryInterface(IID_ICoreWebView2Controller2, (void**)&wc2);
COREWEBVIEW2_COLOR wvColor;
wvColor.R = app->backgroundColour.R;
wvColor.G = app->backgroundColour.G;
wvColor.B = app->backgroundColour.B;
wvColor.A = app->backgroundColour.A == 0 ? 0 : 255;
if( app->WindowIsTranslucent ) {
wvColor.A = 0;
}
HRESULT result = wc2->put_DefaultBackgroundColor(wvColor);
if (!SUCCEEDED(result))
{
switch (result)
{
case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
{
MessageBox(
app->window,
L"Couldn't find Edge installation. "
"Do you have a version installed that's compatible with this "
"WebView2 SDK version?",
nullptr, MB_OK);
}
break;
case HRESULT_FROM_WIN32(ERROR_FILE_EXISTS):
{
MessageBox(
app->window, L"User data folder cannot be created because a file with the same name already exists.", nullptr, MB_OK);
}
break;
case E_ACCESSDENIED:
{
MessageBox(
app->window, L"Unable to create user data folder, Access Denied.", nullptr, MB_OK);
}
break;
case E_FAIL:
{
MessageBox(
app->window, L"Edge runtime unable to start", nullptr, MB_OK);
}
break;
default:
{
MessageBox(app->window, L"Failed to create WebView2 environment", nullptr, MB_OK);
}
}
}
}
messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}");
return true;
}
@@ -422,7 +538,7 @@ void Run(struct Application* app, int argc, char **argv) {
// Configure translucency
DWORD dwExStyle = 0;
if ( app->windowBackgroundIsTranslucent) {
if ( app->WindowIsTranslucent) {
dwExStyle = WS_EX_NOREDIRECTIONBITMAP;
wc.hbrBackground = CreateSolidBrush(RGB(255,255,255));
}
@@ -474,7 +590,7 @@ void Run(struct Application* app, int argc, char **argv) {
SetWindowPos(nullptr, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
}
if ( app->windowBackgroundIsTranslucent ) {
if ( app->WindowIsTranslucent ) {
// Enable the translucent background effect
enableTranslucentBackground(app);
@@ -492,61 +608,6 @@ void Run(struct Application* app, int argc, char **argv) {
// Add webview2
initWebView2(app, debug, initialCallback);
if( app->webviewIsTranparent ) {
wchar_t szBuff[64];
ICoreWebView2Controller2 *wc2;
wc2 = nullptr;
app->webviewController->QueryInterface(IID_ICoreWebView2Controller2, (void**)&wc2);
COREWEBVIEW2_COLOR wvColor;
wvColor.R = app->backgroundColour.R;
wvColor.G = app->backgroundColour.G;
wvColor.B = app->backgroundColour.B;
wvColor.A = app->backgroundColour.A == 0 ? 0 : 255;
if( app->windowBackgroundIsTranslucent ) {
wvColor.A = 0;
}
HRESULT result = wc2->put_DefaultBackgroundColor(wvColor);
if (!SUCCEEDED(result))
{
switch (result)
{
case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
{
MessageBox(
app->window,
L"Couldn't find Edge installation. "
"Do you have a version installed that's compatible with this "
"WebView2 SDK version?",
nullptr, MB_OK);
}
break;
case HRESULT_FROM_WIN32(ERROR_FILE_EXISTS):
{
MessageBox(
app->window, L"User data folder cannot be created because a file with the same name already exists.", nullptr, MB_OK);
}
break;
case E_ACCESSDENIED:
{
MessageBox(
app->window, L"Unable to create user data folder, Access Denied.", nullptr, MB_OK);
}
break;
case E_FAIL:
{
MessageBox(
app->window, L"Edge runtime unable to start", nullptr, MB_OK);
}
break;
default:
{
MessageBox(app->window, L"Failed to create WebView2 environment", nullptr, MB_OK);
}
}
}
}
// Main event loop
MSG msg;
@@ -816,8 +877,8 @@ void WebviewIsTransparent(struct Application *app) {
app->webviewIsTranparent = true;
}
void WindowBackgroundIsTranslucent(struct Application *app) {
app->windowBackgroundIsTranslucent = true;
void WindowIsTranslucent(struct Application *app) {
app->WindowIsTranslucent = true;
}

View File

@@ -13,9 +13,30 @@ extern void DisableWindowIcon(struct Application* app);
*/
import "C"
import (
"github.com/ztrue/tracerr"
"os"
"github.com/wailsapp/wails/v2/pkg/menu"
)
// Setup the global caches
var globalCheckboxCache = NewCheckboxCache()
var globalRadioGroupCache = NewRadioGroupCache()
var globalRadioGroupMap = NewRadioGroupMap()
var globalApplicationMenu *Menu
type menuType string
const (
appMenuType menuType = "ApplicationMenu"
contextMenuType
trayMenuType
)
func (a *Application) processPlatformSettings() error {
menuManager = a.menuManager
config := a.config.Windows
if config == nil {
return nil
@@ -26,20 +47,17 @@ func (a *Application) processPlatformSettings() error {
C.WebviewIsTransparent(a.app)
}
if config.WindowBackgroundIsTranslucent {
C.WindowBackgroundIsTranslucent(a.app)
if config.WindowIsTranslucent {
C.WindowIsTranslucent(a.app)
}
if config.DisableWindowIcon {
C.DisableWindowIcon(a.app)
}
//// Process menu
////applicationMenu := options.GetApplicationMenu(a.config)
//applicationMenu := a.menuManager.GetApplicationMenuJSON()
//if applicationMenu != "" {
// C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
//}
// Unfortunately, we need to store this in the package variable so the C callback can see it
applicationMenu = a.menuManager.GetProcessedApplicationMenu()
//
//// Process tray
//trays, err := a.menuManager.GetTrayMenus()
@@ -70,3 +88,111 @@ func (a *Application) processPlatformSettings() error {
return nil
}
func (c *Client) updateApplicationMenu() {
applicationMenu = c.app.menuManager.GetProcessedApplicationMenu()
createApplicationMenu(uintptr(C.GetWindowHandle(c.app.app)))
}
/* ---------------------------------------------------------------------------------
Application Menu
----------------
There's only 1 application menu and this is where we create it. This method
is called from C after the window is created and the WM_CREATE message has
been sent.
*/
func checkFatal(err error) {
if err != nil {
tracerr.PrintSourceColor(err)
globalRadioGroupCache.Dump()
globalRadioGroupMap.Dump()
os.Exit(1)
}
}
//export createApplicationMenu
func createApplicationMenu(hwnd uintptr) {
if applicationMenu == nil {
return
}
var err error
window := win32Window(hwnd)
if globalApplicationMenu != nil {
checkFatal(globalApplicationMenu.Destroy())
}
globalApplicationMenu, err = createMenu(applicationMenu, appMenuType)
checkFatal(err)
err = setWindowMenu(window, globalApplicationMenu.menu)
checkFatal(err)
}
//export handleKeypressInGo
func handleKeypressInGo(keycode uint16, modifiers uint8) {
//fmt.Printf("Key code: %#x\n", keycode)
menuID, menuType := getCallbackForKeyPress(keycode, modifiers)
if menuID == "" {
return
}
err := menuManager.ProcessClick(menuID, "", string(menuType), "")
if err != nil {
println(err.Error())
}
}
/*
This method is called by C when a menu item is pressed
*/
//export menuClicked
func menuClicked(id uint32) {
win32MenuID := win32MenuItemID(id)
//println("Got click from menu id", win32MenuID)
// Get the menu from the cache
menuItemDetails := getMenuCacheEntry(win32MenuID)
wailsMenuID := wailsMenuItemID(menuItemDetails.item.ID)
//println("Got click from menu id", win32MenuID, "- wails menu ID", wailsMenuID)
//spew.Dump(menuItemDetails)
switch menuItemDetails.item.Type {
case menu.CheckboxType:
// Determine if the menu is set or not
res, _, err := win32GetMenuState.Call(uintptr(menuItemDetails.parent), uintptr(id), uintptr(MF_BYCOMMAND))
if int(res) == -1 {
checkFatal(err)
}
flag := MF_CHECKED
if uint32(res) == MF_CHECKED {
flag = MF_UNCHECKED
}
for _, menuid := range globalCheckboxCache.win32MenuIDsForWailsMenuID(wailsMenuID) {
//println("setting menuid", menuid, "with flag", flag)
menuItemDetails := getMenuCacheEntry(menuid)
res, _, err = win32CheckMenuItem.Call(uintptr(menuItemDetails.parent), uintptr(menuid), uintptr(flag))
if int(res) == -1 {
checkFatal(err)
}
}
case menu.RadioType:
err := selectRadioItemFromWailsMenuID(wailsMenuID, win32MenuID)
checkFatal(err)
}
// Print the click error - it's not fatal
err := menuManager.ProcessClick(menuItemDetails.item.ID, "", string(menuItemDetails.menuType), "")
if err != nil {
println(err.Error())
}
}

View File

@@ -43,7 +43,7 @@ struct Application{
int frame;
char *startupURL;
bool webviewIsTranparent;
bool windowBackgroundIsTranslucent;
bool WindowIsTranslucent;
COREWEBVIEW2_COLOR backgroundColour;
bool disableWindowIcon;
@@ -55,6 +55,10 @@ struct Application{
// placeholders
char* bindings;
char* initialCode;
// DPI
UINT dpix;
UINT dpiy;
};
#define ON_MAIN_THREAD(code) dispatch( [=]{ code; } )
@@ -75,11 +79,17 @@ void loadAssets(struct Application* app);
// called when the application assets have been loaded into the DOM
void completed(struct Application* app);
// Processes the given keycode
void processKeyPress(UINT key);
// Callback
extern "C" {
void DisableWindowIcon(struct Application* app);
void messageFromWindowCallback(const char *);
void* GetWindowHandle(struct Application*);
void createApplicationMenu(HWND hwnd);
void menuClicked(UINT id);
void handleKeypressInGo(UINT, BYTE);
}
#endif

View File

@@ -1,10 +1,11 @@
package wv2runtime
import (
"github.com/leaanthony/go-webview2/webviewloader"
"github.com/leaanthony/webview2runtime"
)
const minimumRuntimeVersion string = "91.0.864.48"
const MinimumRuntimeVersion string = "91.0.864.48"
type installationStatus int
@@ -14,21 +15,21 @@ const (
installed
)
func Process() error {
func Process() (*webview2runtime.Info, error) {
installStatus := needsInstalling
installedVersion := webview2runtime.GetInstalledVersion()
if installedVersion != nil {
installStatus = installed
updateRequired, err := installedVersion.IsOlderThan(minimumRuntimeVersion)
compareResult, err := webviewloader.CompareBrowserVersions(installedVersion.Version, MinimumRuntimeVersion)
if err != nil {
_ = webview2runtime.Error(err.Error(), "Error")
return err
return nil, err
}
updateRequired := compareResult == -1
// Installed and does not require updating
if !updateRequired {
return nil
return installedVersion, nil
}
installStatus = needsUpdating
}
return doInstallationStrategy(installStatus)
return installedVersion, doInstallationStrategy(installStatus)
}

View File

@@ -0,0 +1,93 @@
//+build windows
package ffenestri
import (
"fmt"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/menumanager"
"os"
"sync"
"text/tabwriter"
)
/* ---------------------------------------------------------------------------------
Checkbox Cache
--------------
The checkbox cache keeps a list of IDs that are associated with the same checkbox menu item.
This can happen when a checkbox is used in an application menu and a tray menu, eg "start at login".
The cache is used to bulk toggle the menu items when one is clicked.
*/
type CheckboxCache struct {
cache map[*menumanager.ProcessedMenu]map[wailsMenuItemID][]win32MenuItemID
mutex sync.RWMutex
}
func NewCheckboxCache() *CheckboxCache {
return &CheckboxCache{
cache: make(map[*menumanager.ProcessedMenu]map[wailsMenuItemID][]win32MenuItemID),
}
}
func (c *CheckboxCache) Dump() {
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
println("---------------- Checkbox", c, "Dump ----------------")
for _, processedMenu := range c.cache {
println("Menu", processedMenu)
for wailsMenuItemID, win32menus := range processedMenu {
println(" WailsMenu: ", wailsMenuItemID)
menus := slicer.String()
for _, win32menu := range win32menus {
menus.Add(fmt.Sprintf("%v", win32menu))
}
_, _ = fmt.Fprintf(w, "%s\t%s\n", wailsMenuItemID, menus.Join(", "))
_ = w.Flush()
}
}
}
func (c *CheckboxCache) addToCheckboxCache(menu *menumanager.ProcessedMenu, item wailsMenuItemID, menuID win32MenuItemID) {
// Get map for menu
if c.cache[menu] == nil {
c.cache[menu] = make(map[wailsMenuItemID][]win32MenuItemID)
}
menuMap := c.cache[menu]
// Ensure we have a slice
if menuMap[item] == nil {
menuMap[item] = []win32MenuItemID{}
}
c.mutex.Lock()
menuMap[item] = append(menuMap[item], menuID)
c.mutex.Unlock()
}
func (c *CheckboxCache) removeMenuFromCheckboxCache(menu *menumanager.ProcessedMenu) {
c.mutex.Lock()
delete(c.cache, menu)
c.mutex.Unlock()
}
// win32MenuIDsForWailsMenuID returns all win32menuids that are used for a wails menu item id across
// all menus
func (c *CheckboxCache) win32MenuIDsForWailsMenuID(item wailsMenuItemID) []win32MenuItemID {
c.mutex.Lock()
result := []win32MenuItemID{}
for _, menu := range c.cache {
ids := menu[item]
if ids != nil {
result = append(result, ids...)
}
}
c.mutex.Unlock()
return result
}

View File

@@ -0,0 +1,28 @@
//+build windows,debug
package ffenestri
import (
"fmt"
"github.com/ztrue/tracerr"
"runtime"
"strings"
)
func wall(err error, inputs ...interface{}) error {
if err == nil {
return nil
}
pc, _, _, _ := runtime.Caller(1)
funcName := runtime.FuncForPC(pc).Name()
splitName := strings.Split(funcName, ".")
message := "[" + splitName[len(splitName)-1] + "]"
if len(inputs) > 0 {
params := []string{}
for _, param := range inputs {
params = append(params, fmt.Sprintf("%v", param))
}
message += "(" + strings.Join(params, " ") + ")"
}
return tracerr.Errorf(message)
}

View File

@@ -0,0 +1,47 @@
// +build windows,!debug
package ffenestri
import "C"
import (
"fmt"
"golang.org/x/sys/windows"
"log"
"os"
"runtime"
"strings"
"syscall"
)
func wall(err error, inputs ...interface{}) error {
if err == nil {
return nil
}
pc, _, _, _ := runtime.Caller(1)
funcName := runtime.FuncForPC(pc).Name()
splitName := strings.Split(funcName, ".")
message := "[" + splitName[len(splitName)-1] + "]"
if len(inputs) > 0 {
params := []string{}
for _, param := range inputs {
params = append(params, fmt.Sprintf("%v", param))
}
message += "(" + strings.Join(params, " ") + ")"
}
title, err := syscall.UTF16PtrFromString("Fatal Error")
if err != nil {
log.Fatal(err)
}
text, err := syscall.UTF16PtrFromString("There has been a fatal error. Details:\n" + message)
if err != nil {
log.Fatal(err)
}
var flags uint32 = windows.MB_ICONERROR | windows.MB_OK
_, err = windows.MessageBox(0, text, title, flags|windows.MB_SYSTEMMODAL)
os.Exit(1)
return err
}

View File

@@ -0,0 +1,232 @@
//+build windows
package ffenestri
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
"runtime"
"strings"
)
//-------------------- Types ------------------------
type win32MenuItemID uint32
type win32Menu uintptr
type win32Window uintptr
type wailsMenuItemID string // The internal menu ID
type Menu struct {
wailsMenu *menumanager.WailsMenu
menu win32Menu
menuType menuType
// A list of all checkbox and radio menuitems we
// create for this menu
checkboxes []win32MenuItemID
radioboxes []win32MenuItemID
initiallySelectedRadioItems []win32MenuItemID
}
func createMenu(wailsMenu *menumanager.WailsMenu, menuType menuType) (*Menu, error) {
mainMenu, err := createWin32Menu()
if err != nil {
return nil, err
}
result := &Menu{
wailsMenu: wailsMenu,
menu: mainMenu,
menuType: menuType,
}
// Process top level menus
for _, toplevelmenu := range applicationMenu.Menu.Items {
err := result.processMenuItem(result.menu, toplevelmenu)
if err != nil {
return nil, err
}
}
err = result.processRadioGroups()
if err != nil {
return nil, err
}
return result, nil
}
func (m *Menu) processMenuItem(parent win32Menu, menuItem *menumanager.ProcessedMenuItem) error {
// Ignore hidden items
if menuItem.Hidden {
return nil
}
// Calculate the flags for this menu item
flags := uintptr(calculateFlags(menuItem))
switch menuItem.Type {
case menu.SubmenuType:
submenu, err := createWin32PopupMenu()
if err != nil {
return err
}
for _, submenuItem := range menuItem.SubMenu.Items {
err = m.processMenuItem(submenu, submenuItem)
if err != nil {
return err
}
}
err = appendWin32MenuItem(parent, flags, uintptr(submenu), menuItem.Label)
if err != nil {
return err
}
case menu.TextType, menu.CheckboxType, menu.RadioType:
win32ID := addMenuCacheEntry(parent, m.menuType, menuItem, m.wailsMenu.Menu)
if menuItem.Accelerator != nil {
m.processAccelerator(menuItem)
}
label := menuItem.Label
//label := fmt.Sprintf("%s (%d)", menuItem.Label, win32ID)
err := appendWin32MenuItem(parent, flags, uintptr(win32ID), label)
if err != nil {
return err
}
if menuItem.Type == menu.CheckboxType {
// We need to maintain a list of this menu's checkboxes
m.checkboxes = append(m.checkboxes, win32ID)
globalCheckboxCache.addToCheckboxCache(m.wailsMenu.Menu, wailsMenuItemID(menuItem.ID), win32ID)
}
if menuItem.Type == menu.RadioType {
// We need to maintain a list of this menu's radioitems
m.radioboxes = append(m.radioboxes, win32ID)
globalRadioGroupMap.addRadioGroupMapping(m.wailsMenu.Menu, wailsMenuItemID(menuItem.ID), win32ID)
if menuItem.Checked {
m.initiallySelectedRadioItems = append(m.initiallySelectedRadioItems, win32ID)
}
}
case menu.SeparatorType:
err := appendWin32MenuItem(parent, flags, 0, "")
if err != nil {
return err
}
}
return nil
}
func (m *Menu) processRadioGroups() error {
for _, rg := range applicationMenu.RadioGroups {
startWailsMenuID := wailsMenuItemID(rg.Members[0])
endWailsMenuID := wailsMenuItemID(rg.Members[len(rg.Members)-1])
startIDs := globalRadioGroupMap.getRadioGroupMapping(startWailsMenuID)
endIDs := globalRadioGroupMap.getRadioGroupMapping(endWailsMenuID)
var radioGroupMaps = []*radioGroupStartEnd{}
for index := range startIDs {
startID := startIDs[index]
endID := endIDs[index]
thisRadioGroup := &radioGroupStartEnd{
startID: startID,
endID: endID,
}
radioGroupMaps = append(radioGroupMaps, thisRadioGroup)
}
// Set this for each member
for _, member := range rg.Members {
id := wailsMenuItemID(member)
globalRadioGroupCache.addToRadioGroupCache(m.wailsMenu.Menu, id, radioGroupMaps)
}
}
// Enable all initially checked radio items
for _, win32MenuID := range m.initiallySelectedRadioItems {
menuItemDetails := getMenuCacheEntry(win32MenuID)
wailsMenuID := wailsMenuItemID(menuItemDetails.item.ID)
err := selectRadioItemFromWailsMenuID(wailsMenuID, win32MenuID)
if err != nil {
return err
}
}
return nil
}
func (m *Menu) Destroy() error {
// Release the MenuIDs
releaseMenuIDsForProcessedMenu(m.wailsMenu.Menu)
// Unload this menu's checkboxes from the cache
globalCheckboxCache.removeMenuFromCheckboxCache(m.wailsMenu.Menu)
// Unload this menu's radio groups from the cache
globalRadioGroupCache.removeMenuFromRadioBoxCache(m.wailsMenu.Menu)
globalRadioGroupMap.removeMenuFromRadioGroupMapping(m.wailsMenu.Menu)
// Free up callbacks
resetCallbacks()
// Delete menu
return destroyWin32Menu(m.menu)
}
func (m *Menu) processAccelerator(menuitem *menumanager.ProcessedMenuItem) {
// Add in shortcut to label if there is no "\t" override
if !strings.Contains(menuitem.Label, "\t") {
menuitem.Label += "\t" + keys.Stringify(menuitem.Accelerator, runtime.GOOS)
}
// Calculate the modifier
var modifiers uint8
for _, mod := range menuitem.Accelerator.Modifiers {
switch mod {
case keys.ControlKey, keys.CmdOrCtrlKey:
modifiers |= 1
case keys.OptionOrAltKey:
modifiers |= 2
case keys.ShiftKey:
modifiers |= 4
//case keys.SuperKey:
// modifiers |= 8
}
}
var keycode = calculateKeycode(strings.ToLower(menuitem.Accelerator.Key))
if keycode == 0 {
fmt.Printf("WARNING: Key '%s' is unsupported in windows. Cannot bind callback.", menuitem.Accelerator.Key)
return
}
addMenuCallback(keycode, modifiers, menuitem.ID, m.menuType)
}
var flagMap = map[menu.Type]uint32{
menu.TextType: MF_STRING,
menu.SeparatorType: MF_SEPARATOR,
menu.SubmenuType: MF_STRING | MF_POPUP,
menu.CheckboxType: MF_STRING,
menu.RadioType: MF_STRING,
}
func calculateFlags(menuItem *menumanager.ProcessedMenuItem) uint32 {
result := flagMap[menuItem.Type]
if menuItem.Disabled {
result |= MF_DISABLED
}
if menuItem.Type == menu.CheckboxType && menuItem.Checked {
result |= MF_CHECKED
}
return result
}

View File

@@ -0,0 +1,74 @@
//+build windows
package ffenestri
import (
"github.com/leaanthony/idgen"
"github.com/wailsapp/wails/v2/internal/menumanager"
"sync"
)
/**
MenuCache
---------
When windows calls back to Go (when an item is clicked), we need to
be able to retrieve information about the menu item:
- The menu that the menuitem is part of (parent)
- The original processed menu item
- The type of the menu (application, context or tray)
This cache is built up when a menu is created.
*/
// TODO: Make this like the other caches
type menuCacheEntry struct {
parent win32Menu
menuType menuType
item *menumanager.ProcessedMenuItem
processedMenu *menumanager.ProcessedMenu
}
var idGenerator = idgen.New()
var menuCache = map[win32MenuItemID]*menuCacheEntry{}
var menuCacheLock sync.RWMutex
var wailsMenuIDtoWin32IDMap = map[wailsMenuItemID]win32MenuItemID{}
// This releases the menuIDs back to the id generator
var winIDsOwnedByProcessedMenu = map[*menumanager.ProcessedMenu][]win32MenuItemID{}
func releaseMenuIDsForProcessedMenu(processedMenu *menumanager.ProcessedMenu) {
for _, menuID := range winIDsOwnedByProcessedMenu[processedMenu] {
idGenerator.ReleaseID(uint(menuID))
}
delete(winIDsOwnedByProcessedMenu, processedMenu)
}
func addMenuCacheEntry(parent win32Menu, typ menuType, wailsMenuItem *menumanager.ProcessedMenuItem, processedMenu *menumanager.ProcessedMenu) win32MenuItemID {
menuCacheLock.Lock()
defer menuCacheLock.Unlock()
id, err := idGenerator.NewID()
checkFatal(err)
menuID := win32MenuItemID(id)
menuCache[menuID] = &menuCacheEntry{
parent: parent,
menuType: typ,
item: wailsMenuItem,
processedMenu: processedMenu,
}
// save the mapping
wailsMenuIDtoWin32IDMap[wailsMenuItemID(wailsMenuItem.ID)] = menuID
// keep track of menuids owned by this menu (so we can release the ids)
winIDsOwnedByProcessedMenu[processedMenu] = append(winIDsOwnedByProcessedMenu[processedMenu], menuID)
return menuID
}
func getMenuCacheEntry(id win32MenuItemID) *menuCacheEntry {
menuCacheLock.Lock()
defer menuCacheLock.Unlock()
return menuCache[id]
}

View File

@@ -0,0 +1,126 @@
package ffenestri
type callbackData struct {
menuID string
menuType menuType
}
var callbacks = map[uint16]map[uint8]callbackData{}
func addMenuCallback(key uint16, modifiers uint8, menuID string, menutype menuType) {
if callbacks[key] == nil {
callbacks[key] = make(map[uint8]callbackData)
}
callbacks[key][modifiers] = callbackData{
menuID: menuID,
menuType: menutype,
}
}
func resetCallbacks() {
callbacks = map[uint16]map[uint8]callbackData{}
}
func getCallbackForKeyPress(key uint16, modifiers uint8) (string, menuType) {
if callbacks[key] == nil {
return "", ""
}
result := callbacks[key][modifiers]
return result.menuID, result.menuType
}
func calculateKeycode(key string) uint16 {
return keymap[key]
}
// TODO: Complete this list
var keymap = map[string]uint16{
"0": 0x30,
"1": 0x31,
"2": 0x32,
"3": 0x33,
"4": 0x34,
"5": 0x35,
"6": 0x36,
"7": 0x37,
"8": 0x38,
"9": 0x39,
"a": 0x41,
"b": 0x42,
"c": 0x43,
"d": 0x44,
"e": 0x45,
"f": 0x46,
"g": 0x47,
"h": 0x48,
"i": 0x49,
"j": 0x4A,
"k": 0x4B,
"l": 0x4C,
"m": 0x4D,
"n": 0x4E,
"o": 0x4F,
"p": 0x50,
"q": 0x51,
"r": 0x52,
"s": 0x53,
"t": 0x54,
"u": 0x55,
"v": 0x56,
"w": 0x57,
"x": 0x58,
"y": 0x59,
"z": 0x5A,
"backspace": 0x08,
"tab": 0x09,
"return": 0x0D,
"enter": 0x0D,
"escape": 0x1B,
"left": 0x25,
"right": 0x27,
"up": 0x26,
"down": 0x28,
"space": 0x20,
"delete": 0x2E,
"home": 0x24,
"end": 0x23,
"page up": 0x21,
"page down": 0x22,
"f1": 0x70,
"f2": 0x71,
"f3": 0x72,
"f4": 0x73,
"f5": 0x74,
"f6": 0x75,
"f7": 0x76,
"f8": 0x77,
"f9": 0x78,
"f10": 0x79,
"f11": 0x7A,
"f12": 0x7B,
"f13": 0x7C,
"f14": 0x7D,
"f15": 0x7E,
"f16": 0x7F,
"f17": 0x80,
"f18": 0x81,
"f19": 0x82,
"f20": 0x83,
"f21": 0x84,
"f22": 0x85,
"f23": 0x86,
"f24": 0x87,
// Windows doesn't have these apparently so use 0 for unsupported
"f25": 0,
"f26": 0,
"f27": 0,
"f28": 0,
"f29": 0,
"f30": 0,
"f31": 0,
"f32": 0,
"f33": 0,
"f34": 0,
"f35": 0,
}

View File

@@ -0,0 +1,194 @@
//+build windows
package ffenestri
import (
"fmt"
"github.com/leaanthony/slicer"
"os"
"sync"
"text/tabwriter"
"github.com/wailsapp/wails/v2/internal/menumanager"
)
/* ---------------------------------------------------------------------------------
Radio Groups
------------
Radio groups are stored by the ProcessedMenu as a list of menu ids.
Windows only cares about the start and end ids of the group so we
preprocess the radio groups and store this data in a radioGroupMap.
When a radio button is clicked, we use the menu id to read in the
radio group data and call CheckMenuRadioItem to update the group.
*/
type radioGroupStartEnd struct {
startID win32MenuItemID
endID win32MenuItemID
}
type RadioGroupCache struct {
cache map[*menumanager.ProcessedMenu]map[wailsMenuItemID][]*radioGroupStartEnd
mutex sync.RWMutex
}
func NewRadioGroupCache() *RadioGroupCache {
return &RadioGroupCache{
cache: make(map[*menumanager.ProcessedMenu]map[wailsMenuItemID][]*radioGroupStartEnd),
}
}
func (c *RadioGroupCache) Dump() {
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
println("---------------- RadioGroupCache", c, "Dump ----------------")
for menu, processedMenu := range c.cache {
println("Menu", menu)
_, _ = fmt.Fprintf(w, "Wails ID \tWindows ID Pairs\n")
for wailsMenuItemID, radioGroupStartEnd := range processedMenu {
menus := slicer.String()
for _, se := range radioGroupStartEnd {
menus.Add(fmt.Sprintf("[%d -> %d]", se.startID, se.endID))
}
_, _ = fmt.Fprintf(w, "%s\t%s\n", wailsMenuItemID, menus.Join(", "))
_ = w.Flush()
}
}
}
func (c *RadioGroupCache) addToRadioGroupCache(menu *menumanager.ProcessedMenu, item wailsMenuItemID, radioGroupMaps []*radioGroupStartEnd) {
c.mutex.Lock()
// Get map for menu
if c.cache[menu] == nil {
c.cache[menu] = make(map[wailsMenuItemID][]*radioGroupStartEnd)
}
menuMap := c.cache[menu]
// Ensure we have a slice
if menuMap[item] == nil {
menuMap[item] = []*radioGroupStartEnd{}
}
menuMap[item] = radioGroupMaps
c.mutex.Unlock()
}
func (c *RadioGroupCache) removeMenuFromRadioBoxCache(menu *menumanager.ProcessedMenu) {
c.mutex.Lock()
delete(c.cache, menu)
c.mutex.Unlock()
}
func (c *RadioGroupCache) getRadioGroupMappings(wailsMenuID wailsMenuItemID) []*radioGroupStartEnd {
c.mutex.Lock()
result := []*radioGroupStartEnd{}
for _, menugroups := range c.cache {
groups := menugroups[wailsMenuID]
if groups != nil {
result = append(result, groups...)
}
}
c.mutex.Unlock()
return result
}
type RadioGroupMap struct {
cache map[*menumanager.ProcessedMenu]map[wailsMenuItemID][]win32MenuItemID
mutex sync.RWMutex
}
func NewRadioGroupMap() *RadioGroupMap {
return &RadioGroupMap{
cache: make(map[*menumanager.ProcessedMenu]map[wailsMenuItemID][]win32MenuItemID),
}
}
func (c *RadioGroupMap) Dump() {
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
println("---------------- RadioGroupMap", c, "Dump ----------------")
for _, processedMenu := range c.cache {
_, _ = fmt.Fprintf(w, "Menu\tWails ID \tWindows IDs\n")
for wailsMenuItemID, win32menus := range processedMenu {
menus := slicer.String()
for _, win32menu := range win32menus {
menus.Add(fmt.Sprintf("%v", win32menu))
}
_, _ = fmt.Fprintf(w, "%p\t%s\t%s\n", processedMenu, wailsMenuItemID, menus.Join(", "))
_ = w.Flush()
}
}
}
func (m *RadioGroupMap) addRadioGroupMapping(menu *menumanager.ProcessedMenu, item wailsMenuItemID, win32ID win32MenuItemID) {
m.mutex.Lock()
// Get map for menu
if m.cache[menu] == nil {
m.cache[menu] = make(map[wailsMenuItemID][]win32MenuItemID)
}
menuMap := m.cache[menu]
// Ensure we have a slice
if menuMap[item] == nil {
menuMap[item] = []win32MenuItemID{}
}
menuMap[item] = append(menuMap[item], win32ID)
m.mutex.Unlock()
}
func (m *RadioGroupMap) removeMenuFromRadioGroupMapping(menu *menumanager.ProcessedMenu) {
m.mutex.Lock()
delete(m.cache, menu)
m.mutex.Unlock()
}
func (m *RadioGroupMap) getRadioGroupMapping(wailsMenuID wailsMenuItemID) []win32MenuItemID {
m.mutex.Lock()
result := []win32MenuItemID{}
for _, menuids := range m.cache {
ids := menuids[wailsMenuID]
if ids != nil {
result = append(result, ids...)
}
}
m.mutex.Unlock()
return result
}
func selectRadioItemFromWailsMenuID(wailsMenuID wailsMenuItemID, win32MenuID win32MenuItemID) error {
radioItemGroups := globalRadioGroupCache.getRadioGroupMappings(wailsMenuID)
// Figure out offset into group
var offset win32MenuItemID = 0
for _, radioItemGroup := range radioItemGroups {
if win32MenuID >= radioItemGroup.startID && win32MenuID <= radioItemGroup.endID {
offset = win32MenuID - radioItemGroup.startID
break
}
}
for _, radioItemGroup := range radioItemGroups {
selectedMenuID := radioItemGroup.startID + offset
menuItemDetails := getMenuCacheEntry(selectedMenuID)
if menuItemDetails != nil {
if menuItemDetails.parent != 0 {
err := selectRadioItem(selectedMenuID, radioItemGroup.startID, radioItemGroup.endID, menuItemDetails.parent)
if err != nil {
return err
}
}
}
}
return nil
}

View File

@@ -0,0 +1,130 @@
//+build windows
package ffenestri
import (
"unsafe"
"github.com/wailsapp/wails/v2/internal/menumanager"
"golang.org/x/sys/windows"
)
var (
// DLL stuff
user32 = windows.NewLazySystemDLL("User32.dll")
win32CreateMenu = user32.NewProc("CreateMenu")
win32DestroyMenu = user32.NewProc("DestroyMenu")
win32CreatePopupMenu = user32.NewProc("CreatePopupMenu")
win32AppendMenuW = user32.NewProc("AppendMenuW")
win32SetMenu = user32.NewProc("SetMenu")
win32CheckMenuItem = user32.NewProc("CheckMenuItem")
win32GetMenuState = user32.NewProc("GetMenuState")
win32CheckMenuRadioItem = user32.NewProc("CheckMenuRadioItem")
applicationMenu *menumanager.WailsMenu
menuManager *menumanager.Manager
)
const MF_BITMAP uint32 = 0x00000004
const MF_CHECKED uint32 = 0x00000008
const MF_DISABLED uint32 = 0x00000002
const MF_ENABLED uint32 = 0x00000000
const MF_GRAYED uint32 = 0x00000001
const MF_MENUBARBREAK uint32 = 0x00000020
const MF_MENUBREAK uint32 = 0x00000040
const MF_OWNERDRAW uint32 = 0x00000100
const MF_POPUP uint32 = 0x00000010
const MF_SEPARATOR uint32 = 0x00000800
const MF_STRING uint32 = 0x00000000
const MF_UNCHECKED uint32 = 0x00000000
const MF_BYCOMMAND uint32 = 0x00000000
const MF_BYPOSITION uint32 = 0x00000400
const WM_SIZE = 5
const WM_GETMINMAXINFO = 36
type Win32Rect struct {
Left int32
Top int32
Right int32
Bottom int32
}
// ------------------- win32 calls -----------------------
func createWin32Menu() (win32Menu, error) {
res, _, err := win32CreateMenu.Call()
if res == 0 {
return 0, wall(err)
}
return win32Menu(res), nil
}
func destroyWin32Menu(menu win32Menu) error {
res, _, err := win32DestroyMenu.Call(uintptr(menu))
if res == 0 {
return wall(err, "Menu:", menu)
}
return nil
}
func createWin32PopupMenu() (win32Menu, error) {
res, _, err := win32CreatePopupMenu.Call()
if res == 0 {
return 0, wall(err)
}
return win32Menu(res), nil
}
func appendWin32MenuItem(menu win32Menu, flags uintptr, submenuOrID uintptr, label string) error {
menuText, err := windows.UTF16PtrFromString(label)
if err != nil {
return err
}
res, _, err := win32AppendMenuW.Call(
uintptr(menu),
flags,
submenuOrID,
uintptr(unsafe.Pointer(menuText)),
)
if res == 0 {
return wall(err, "Menu", menu, "Flags", flags, "submenuOrID", submenuOrID, "label", label)
}
return nil
}
func setWindowMenu(window win32Window, menu win32Menu) error {
res, _, err := win32SetMenu.Call(uintptr(window), uintptr(menu))
if res == 0 {
return wall(err, "window", window, "menu", menu)
}
return nil
}
func selectRadioItem(selectedMenuID, startMenuItemID, endMenuItemID win32MenuItemID, parent win32Menu) error {
res, _, err := win32CheckMenuRadioItem.Call(uintptr(parent), uintptr(startMenuItemID), uintptr(endMenuItemID), uintptr(selectedMenuID), uintptr(MF_BYCOMMAND))
if int(res) == 0 {
return wall(err, selectedMenuID, startMenuItemID, endMenuItemID, parent)
}
return nil
}
//
//func getWindowRect(window win32Window) (*Win32Rect, error) {
// var windowRect Win32Rect
// res, _, err := win32GetWindowRect.Call(uintptr(window), uintptr(unsafe.Pointer(&windowRect)))
// if res == 0 {
// return nil, err
// }
// return &windowRect, nil
//}
//
//func getClientRect(window win32Window) (*Win32Rect, error) {
// var clientRect Win32Rect
// res, _, err := win32GetClientRect.Call(uintptr(window), uintptr(unsafe.Pointer(&clientRect)))
// if res == 0 {
// return nil, err
// }
// return &clientRect, nil
//}
//

View File

@@ -12,7 +12,8 @@ class wv2ComHandler
: public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
public ICoreWebView2WebMessageReceivedEventHandler,
public ICoreWebView2PermissionRequestedEventHandler
public ICoreWebView2PermissionRequestedEventHandler,
public ICoreWebView2AcceleratorKeyPressedEventHandler
{
struct Application *app;
@@ -44,6 +45,7 @@ class wv2ComHandler
ICoreWebView2 *webview;
::EventRegistrationToken token;
controller->get_CoreWebView2(&webview);
controller->add_AcceleratorKeyPressed(this, &token);
webview->add_WebMessageReceived(this, &token);
webview->add_PermissionRequested(this, &token);
@@ -51,6 +53,28 @@ class wv2ComHandler
return S_OK;
}
// This is our keyboard callback method
HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2Controller *controller, ICoreWebView2AcceleratorKeyPressedEventArgs * args) {
// Prevent WebView2 from processing the key
args->put_Handled(TRUE);
COREWEBVIEW2_KEY_EVENT_KIND kind;
args->get_KeyEventKind(&kind);
if (kind == COREWEBVIEW2_KEY_EVENT_KIND_KEY_DOWN ||
kind == COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_DOWN)
{
UINT key;
args->get_VirtualKey(&key);
COREWEBVIEW2_PHYSICAL_KEY_STATUS status;
args->get_PhysicalKeyStatus(&status);
if (!status.WasKeyDown)
{
processKeyPress(key);
}
}
return S_OK;
}
// This is called when JS posts a message back to webkit
HRESULT STDMETHODCALLTYPE Invoke(
ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) {

View File

@@ -0,0 +1,142 @@
package assetserver
import (
"bytes"
"context"
"embed"
"fmt"
"github.com/leaanthony/debme"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/logger"
"io/fs"
"path/filepath"
"strings"
)
type DesktopAssetServer struct {
assets debme.Debme
indexFile []byte
runtimeJS []byte
assetdir string
logger *logger.Logger
}
func NewDesktopAssetServer(ctx context.Context, assets embed.FS, bindingsJSON string) (*DesktopAssetServer, error) {
result := &DesktopAssetServer{}
_logger := ctx.Value("logger")
if _logger != nil {
result.logger = _logger.(*logger.Logger)
}
_assetdir := ctx.Value("assetdir")
if _assetdir != nil {
result.assetdir = _assetdir.(string)
absdir, err := filepath.Abs(result.assetdir)
if err != nil {
return nil, err
}
result.LogInfo("Loading assets from: %s", absdir)
}
var buffer bytes.Buffer
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
buffer.Write(runtime.RuntimeDesktopJS)
result.runtimeJS = buffer.Bytes()
err := result.init(assets)
return result, err
}
func (d *DesktopAssetServer) LogInfo(message string, args ...interface{}) {
if d.logger != nil {
d.logger.Info("[DesktopAssetServer] "+message, args...)
}
}
func (d *DesktopAssetServer) SetAssetDir(assetdir string) {
d.assetdir = assetdir
}
func PathToIndexHTML(assets embed.FS) (string, error) {
stat, err := fs.Stat(assets, "index.html")
if stat != nil {
return ".", nil
}
var indexFiles slicer.StringSlicer
err = fs.WalkDir(assets, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, "index.html") {
indexFiles.Add(path)
}
return nil
})
if err != nil {
return "", err
}
if indexFiles.Length() > 1 {
return "", fmt.Errorf("multiple 'index.html' files found in assets")
}
path, _ := filepath.Split(indexFiles.AsSlice()[0])
return path, nil
}
func processAssets(assets embed.FS) (debme.Debme, error) {
result, err := debme.FS(assets, ".")
if err != nil {
return result, err
}
// Find index.html
path, err := PathToIndexHTML(assets)
if err != nil {
return debme.Debme{}, err
}
return debme.FS(assets, path)
}
func (a *DesktopAssetServer) init(assets embed.FS) error {
var err error
a.assets, err = processAssets(assets)
if err != nil {
return err
}
indexHTML, err := a.assets.ReadFile("index.html")
if err != nil {
return err
}
a.indexFile, err = injectHTML(string(indexHTML), `<script src="/wails/runtime.js"></script>`)
if err != nil {
return err
}
a.indexFile, err = injectHTML(string(a.indexFile), `<script src="/wails/ipc.js"></script>`)
if err != nil {
return err
}
return nil
}
func (a *DesktopAssetServer) Load(filename string) ([]byte, string, error) {
var content []byte
var err error
switch filename {
case "/":
content = a.indexFile
case "/wails/runtime.js":
content = a.runtimeJS
case "/wails/ipc.js":
content = runtime.DesktopIPC
default:
content, err = a.ReadFile(filename)
}
if err != nil {
return nil, "", err
}
mimeType := GetMimetype(filename, content)
return content, mimeType, nil
}

View File

@@ -0,0 +1,13 @@
//go:build dev
package assetserver
import (
"os"
"path/filepath"
)
func (a *DesktopAssetServer) ReadFile(filename string) ([]byte, error) {
a.LogInfo("Loading file from disk: %s", filename)
return os.ReadFile(filepath.Join(a.assetdir, filename))
}

View File

@@ -0,0 +1,7 @@
//go:build production
package assetserver
func (a *DesktopAssetServer) ReadFile(filename string) ([]byte, error) {
return a.assets.ReadFile(filename)
}

View File

@@ -0,0 +1,93 @@
//go:build dev
// +build dev
package assetserver
import (
"bytes"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/pkg/options"
"path/filepath"
)
/*
The assetserver for dev serves assets from disk.
It injects a websocket based IPC script into `index.html`.
*/
import (
"os"
)
type AssetServer struct {
indexFile []byte
runtimeJS []byte
assetdir string
appOptions *options.App
}
func NewAssetServer(assetdir string, bindingsJSON string, appOptions *options.App) (*AssetServer, error) {
result := &AssetServer{
assetdir: assetdir,
appOptions: appOptions,
}
err := result.init()
if err != nil {
return nil, err
}
var buffer bytes.Buffer
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
buffer.Write(runtime.RuntimeDesktopJS)
result.runtimeJS = buffer.Bytes()
err = result.init()
return result, err
}
func (a *AssetServer) loadFileFromDisk(filename string) ([]byte, error) {
return os.ReadFile(filepath.Join(a.assetdir, filename))
}
func (a *AssetServer) init() error {
var err error
a.indexFile, err = a.loadFileFromDisk("index.html")
if err != nil {
return err
}
a.indexFile, err = injectHTML(string(a.indexFile), `<div id="wails-spinner"></div>`)
if err != nil {
return err
}
a.indexFile, err = injectHTML(string(a.indexFile), `<script src="/wails/ipc.js"></script>`)
if err != nil {
return err
}
a.indexFile, err = injectHTML(string(a.indexFile), `<script src="/wails/runtime.js"></script>`)
if err != nil {
return err
}
return nil
}
func (a *AssetServer) Load(filename string) ([]byte, string, error) {
var content []byte
var err error
switch filename {
case "/":
content = a.indexFile
case "/wails/runtime.js":
content = a.runtimeJS
case "/wails/ipc.js":
content = runtime.WebsocketIPC
default:
content, err = a.loadFileFromDisk(filename)
}
if err != nil {
return nil, "", err
}
mimeType := GetMimetype(filename, content)
return content, mimeType, nil
}

View File

@@ -0,0 +1,21 @@
package assetserver
import (
"bytes"
"fmt"
"strings"
)
func injectHTML(input string, html string) ([]byte, error) {
splits := strings.Split(input, "</body>")
if len(splits) != 2 {
return nil, fmt.Errorf("unable to locate a </body> tag in your html")
}
var result bytes.Buffer
result.WriteString(splits[0])
result.WriteString(html)
result.WriteString("</body>")
result.WriteString(splits[1])
return result.Bytes(), nil
}

View File

@@ -0,0 +1,42 @@
package assetserver
import (
"net/http"
"path/filepath"
"strings"
"sync"
)
import "github.com/gabriel-vasile/mimetype"
var (
cache = map[string]string{}
mutex sync.Mutex
)
func GetMimetype(filename string, data []byte) string {
mutex.Lock()
defer mutex.Unlock()
result := cache[filename]
if result != "" {
return result
}
detect := mimetype.Detect(data)
if detect == nil {
result = http.DetectContentType(data)
} else {
result = detect.String()
}
if filepath.Ext(filename) == ".css" && strings.HasPrefix(result, "text/plain") {
result = strings.Replace(result, "text/plain", "text/css", 1)
}
if result == "" {
result = "application/octet-stream"
}
cache[filename] = result
return result
}

View File

@@ -0,0 +1,25 @@
package assetserver
import "testing"
func TestGetMimetype(t *testing.T) {
type args struct {
filename string
data []byte
}
tests := []struct {
name string
args args
want string
}{
// TODO: Add test cases.
{"css", args{"test.css", []byte("body{margin:0;padding:0;background-color:#d579b2}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;background-color:#ededed}#nav{padding:30px}#nav a{font-weight:700;color:#2c\n3e50}#nav a.router-link-exact-active{color:#42b983}.hello[data-v-4e26ad49]{margin:10px 0}")}, "text/css; charset=utf-8"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetMimetype(tt.args.filename, tt.args.data); got != tt.want {
t.Errorf("GetMimetype() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,8 @@
<html>
<head>
<link href="/main.css" rel="stylesheet">
</head>
<body data-wails-drag>
<div id="logo"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
import {ready} from '@wails/runtime';
ready(() => {
// Get input + focus
let nameElement = document.getElementById("name");
nameElement.focus();
// Setup the greet function
window.greet = function () {
// Get name
let name = nameElement.value;
// Call App.Greet(name)
window.backend.main.App.Greet(name).then((result) => {
// Update result with data back from App.Greet()
document.getElementById("result").innerText = result;
});
};
});

View File

@@ -0,0 +1,8 @@
<html>
<head>
<link href="/main.css" rel="stylesheet">
</head>
<body data-wails-drag>
<div id="logo"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
import {ready} from '@wails/runtime';
ready(() => {
// Get input + focus
let nameElement = document.getElementById("name");
nameElement.focus();
// Setup the greet function
window.greet = function () {
// Get name
let name = nameElement.value;
// Call App.Greet(name)
window.backend.main.App.Greet(name).then((result) => {
// Update result with data back from App.Greet()
document.getElementById("result").innerText = result;
});
};
});

View File

@@ -0,0 +1,6 @@
package testdata
import "embed"
//go:embed index.html main.css main.js
var TopLevelFS embed.FS

View File

@@ -0,0 +1,5 @@
package frontend
type Calls interface {
Callback(string)
}

View File

@@ -0,0 +1,16 @@
//go:build windows
package desktop
import (
"context"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
return windows.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
}

View File

@@ -0,0 +1,11 @@
package windows
import (
"github.com/pkg/browser"
)
// BrowserOpenURL Use the default browser to open the url
func (f *Frontend) BrowserOpenURL(url string) {
// Specific method implementation
_ = browser.OpenURL(url)
}

View File

@@ -0,0 +1,154 @@
//go:build windows
package windows
import "C"
import (
"github.com/leaanthony/go-common-file-dialog/cfd"
"github.com/wailsapp/wails/v2/internal/frontend"
"golang.org/x/sys/windows"
"syscall"
)
// OpenDirectoryDialog prompts the user to select a directory
func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) {
config := cfd.DialogConfig{
Title: options.Title,
Role: "PickFolder",
Folder: options.DefaultDirectory,
}
thisDialog, err := cfd.NewSelectFolderDialog(config)
if err != nil {
return "", err
}
thisDialog.SetParentWindowHandle(f.mainWindow.Handle())
defer func(thisDialog cfd.SelectFolderDialog) {
err := thisDialog.Release()
if err != nil {
println("ERROR: Unable to release dialog:", err.Error())
}
}(thisDialog)
result, err := thisDialog.ShowAndGetResult()
if err != nil && err != cfd.ErrorCancelled {
return "", err
}
return result, nil
}
// OpenFileDialog prompts the user to select a file
func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
config := cfd.DialogConfig{
Folder: options.DefaultDirectory,
FileFilters: convertFilters(options.Filters),
FileName: options.DefaultFilename,
Title: options.Title,
}
thisdialog, err := cfd.NewOpenFileDialog(config)
if err != nil {
return "", err
}
thisdialog.SetParentWindowHandle(f.mainWindow.Handle())
defer func(thisdialog cfd.OpenFileDialog) {
err := thisdialog.Release()
if err != nil {
println("ERROR: Unable to release dialog:", err.Error())
}
}(thisdialog)
result, err := thisdialog.ShowAndGetResult()
if err != nil && err != cfd.ErrorCancelled {
return "", err
}
return result, nil
}
// OpenMultipleFilesDialog prompts the user to select a file
func (f *Frontend) OpenMultipleFilesDialog(dialogOptions frontend.OpenDialogOptions) ([]string, error) {
config := cfd.DialogConfig{
Title: dialogOptions.Title,
Role: "OpenMultipleFiles",
FileFilters: convertFilters(dialogOptions.Filters),
FileName: dialogOptions.DefaultFilename,
Folder: dialogOptions.DefaultDirectory,
}
thisdialog, err := cfd.NewOpenMultipleFilesDialog(config)
if err != nil {
return nil, err
}
thisdialog.SetParentWindowHandle(f.mainWindow.Handle())
defer func(thisdialog cfd.OpenMultipleFilesDialog) {
err := thisdialog.Release()
if err != nil {
println("ERROR: Unable to release dialog:", err.Error())
}
}(thisdialog)
result, err := thisdialog.ShowAndGetResults()
if err != nil && err != cfd.ErrorCancelled {
return nil, err
}
return result, nil
}
// SaveFileDialog prompts the user to select a file
func (f *Frontend) SaveFileDialog(dialogOptions frontend.SaveDialogOptions) (string, error) {
saveDialog, err := cfd.NewSaveFileDialog(cfd.DialogConfig{
Title: dialogOptions.Title,
Role: "SaveFile",
FileFilters: convertFilters(dialogOptions.Filters),
FileName: dialogOptions.DefaultFilename,
Folder: dialogOptions.DefaultDirectory,
})
if err != nil {
return "", err
}
saveDialog.SetParentWindowHandle(f.mainWindow.Handle())
err = saveDialog.Show()
if err != nil {
return "", err
}
result, err := saveDialog.GetResult()
if err != nil && err != cfd.ErrorCancelled {
return "", err
}
return result, nil
}
// MessageDialog show a message dialog to the user
func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
title, err := syscall.UTF16PtrFromString(options.Title)
if err != nil {
return "", err
}
message, err := syscall.UTF16PtrFromString(options.Message)
if err != nil {
return "", err
}
var flags uint32
switch options.Type {
case frontend.InfoDialog:
flags = windows.MB_OK | windows.MB_ICONINFORMATION
case frontend.ErrorDialog:
flags = windows.MB_ICONERROR | windows.MB_OK
case frontend.QuestionDialog:
flags = windows.MB_YESNO
case frontend.WarningDialog:
flags = windows.MB_OK | windows.MB_ICONWARNING
}
button, _ := windows.MessageBox(windows.HWND(f.mainWindow.Handle()), message, title, flags|windows.MB_SYSTEMMODAL)
// This maps MessageBox return values to strings
responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"}
result := "Error"
if int(button) < len(responses) {
result = responses[button]
}
return result, nil
}
func convertFilters(filters []frontend.FileFilter) []cfd.FileFilter {
var result []cfd.FileFilter
for _, filter := range filters {
result = append(result, cfd.FileFilter(filter))
}
return result
}

View File

@@ -0,0 +1,427 @@
//go:build windows
package windows
import (
"context"
"encoding/json"
"fmt"
"log"
"runtime"
"strconv"
"strings"
"github.com/leaanthony/slicer"
"github.com/leaanthony/go-webview2/pkg/edge"
"github.com/leaanthony/winc"
"github.com/leaanthony/winc/w32"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
type Frontend struct {
// Context
ctx context.Context
frontendOptions *options.App
logger *logger.Logger
chromium *edge.Chromium
debug bool
// Assets
assets *assetserver.DesktopAssetServer
// main window handle
mainWindow *Window
minWidth, minHeight, maxWidth, maxHeight int
bindings *binding.Bindings
dispatcher frontend.Dispatcher
servingFromDisk bool
}
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
result := &Frontend{
frontendOptions: appoptions,
logger: myLogger,
bindings: appBindings,
dispatcher: dispatcher,
ctx: ctx,
minHeight: appoptions.MinHeight,
minWidth: appoptions.MinWidth,
maxHeight: appoptions.MaxHeight,
maxWidth: appoptions.MaxWidth,
}
// Check if we have been given a directory to serve assets from.
// If so, this means we are in dev mode and are serving assets off disk.
// We indicate this through the `servingFromDisk` flag to ensure requests
// aren't cached by WebView2 in dev mode
_assetdir := ctx.Value("assetdir")
if _assetdir != nil {
result.servingFromDisk = true
}
bindingsJSON, err := appBindings.ToJSON()
if err != nil {
log.Fatal(err)
}
assets, err := assetserver.NewDesktopAssetServer(ctx, appoptions.Assets, bindingsJSON)
if err != nil {
log.Fatal(err)
}
result.assets = assets
return result
}
func (f *Frontend) WindowReload() {
f.ExecJS("runtime.WindowReload();")
}
func (f *Frontend) Run(ctx context.Context) error {
f.ctx = context.WithValue(ctx, "frontend", f)
mainWindow := NewWindow(nil, f.frontendOptions)
f.mainWindow = mainWindow
var _debug = ctx.Value("debug")
if _debug != nil {
f.debug = _debug.(bool)
}
f.WindowCenter()
f.setupChromium()
mainWindow.OnSize().Bind(func(arg *winc.Event) {
f.chromium.Resize()
})
mainWindow.OnClose().Bind(func(arg *winc.Event) {
if f.frontendOptions.HideWindowOnClose {
f.WindowHide()
} else {
f.Quit()
}
})
// TODO: Move this into a callback from frontend
go func() {
if f.frontendOptions.OnStartup != nil {
f.frontendOptions.OnStartup(f.ctx)
}
}()
mainWindow.Run()
return nil
}
func (f *Frontend) WindowCenter() {
runtime.LockOSThread()
f.mainWindow.Center()
}
func (f *Frontend) WindowSetPos(x, y int) {
runtime.LockOSThread()
f.mainWindow.SetPos(x, y)
}
func (f *Frontend) WindowGetPos() (int, int) {
runtime.LockOSThread()
return f.mainWindow.Pos()
}
func (f *Frontend) WindowSetSize(width, height int) {
runtime.LockOSThread()
f.mainWindow.SetSize(width, height)
}
func (f *Frontend) WindowGetSize() (int, int) {
runtime.LockOSThread()
return f.mainWindow.Size()
}
func (f *Frontend) WindowSetTitle(title string) {
runtime.LockOSThread()
f.mainWindow.SetText(title)
}
func (f *Frontend) WindowFullscreen() {
runtime.LockOSThread()
f.mainWindow.SetMaxSize(0, 0)
f.mainWindow.SetMinSize(0, 0)
f.mainWindow.Fullscreen()
}
func (f *Frontend) WindowUnFullscreen() {
runtime.LockOSThread()
f.mainWindow.UnFullscreen()
f.mainWindow.SetMaxSize(f.maxWidth, f.maxHeight)
f.mainWindow.SetMinSize(f.minWidth, f.minHeight)
}
func (f *Frontend) WindowShow() {
runtime.LockOSThread()
f.mainWindow.Show()
}
func (f *Frontend) WindowHide() {
runtime.LockOSThread()
f.mainWindow.Hide()
}
func (f *Frontend) WindowMaximise() {
runtime.LockOSThread()
f.mainWindow.Maximise()
}
func (f *Frontend) WindowUnmaximise() {
runtime.LockOSThread()
f.mainWindow.Restore()
}
func (f *Frontend) WindowMinimise() {
runtime.LockOSThread()
f.mainWindow.Minimise()
}
func (f *Frontend) WindowUnminimise() {
runtime.LockOSThread()
f.mainWindow.Restore()
}
func (f *Frontend) WindowSetMinSize(width int, height int) {
runtime.LockOSThread()
f.minWidth = width
f.minHeight = height
f.mainWindow.SetMinSize(width, height)
}
func (f *Frontend) WindowSetMaxSize(width int, height int) {
runtime.LockOSThread()
f.maxWidth = width
f.maxHeight = height
f.mainWindow.SetMaxSize(width, height)
}
func (f *Frontend) WindowSetRGBA(col *options.RGBA) {
runtime.LockOSThread()
if col == nil {
return
}
f.mainWindow.Dispatch(func() {
controller := f.chromium.GetController()
controller2 := controller.GetICoreWebView2Controller2()
backgroundCol := edge.COREWEBVIEW2_COLOR{
A: col.A,
R: col.R,
G: col.G,
B: col.B,
}
// Webview2 only has 0 and 255 as valid values.
if backgroundCol.A > 0 && backgroundCol.A < 255 {
backgroundCol.A = 255
}
if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.WebviewIsTransparent {
backgroundCol.A = 0
}
err := controller2.PutDefaultBackgroundColor(backgroundCol)
if err != nil {
log.Fatal(err)
}
})
}
func (f *Frontend) Quit() {
winc.Exit()
}
const (
ctrlZ int = 90
ctrlX = 88
ctrlC = 67
ctrlV = 86
ctrlA = 65
arrowUp = 38
arrowDown = 40
arrowRight = 39
arrowLeft = 37
keyDel = 46
)
func (f *Frontend) setupChromium() {
chromium := edge.NewChromium()
f.chromium = chromium
chromium.MessageCallback = f.processMessage
chromium.WebResourceRequestedCallback = f.processRequest
chromium.NavigationCompletedCallback = f.navigationCompleted
acceleratorsWebviewShouldProcess := slicer.Int([]int{ctrlV, ctrlC, ctrlX, ctrlZ, ctrlA, arrowLeft, arrowRight, arrowUp, arrowDown, keyDel})
chromium.AcceleratorKeyCallback = func(vkey uint) bool {
// We want webview to handle ctrl-C, ctrl-Z, ctrl-v, ctrl-x
if acceleratorsWebviewShouldProcess.Contains(int(vkey)) {
return false
}
// Post keypress
w32.PostMessage(f.mainWindow.Handle(), w32.WM_KEYDOWN, uintptr(vkey), 0)
return true
}
chromium.Embed(f.mainWindow.Handle())
chromium.Resize()
settings, err := chromium.GetSettings()
if err != nil {
log.Fatal(err)
}
err = settings.PutAreDefaultContextMenusEnabled(f.debug)
if err != nil {
log.Fatal(err)
}
err = settings.PutAreDevToolsEnabled(f.debug)
if err != nil {
log.Fatal(err)
}
err = settings.PutIsZoomControlEnabled(false)
if err != nil {
log.Fatal(err)
}
err = settings.PutIsStatusBarEnabled(false)
if err != nil {
log.Fatal(err)
}
err = settings.PutIsStatusBarEnabled(false)
if err != nil {
log.Fatal(err)
}
// Set background colour
f.WindowSetRGBA(f.frontendOptions.RGBA)
chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)
chromium.Navigate("file://wails/")
}
type EventNotify struct {
Name string `json:"name"`
Data []interface{} `json:"data"`
}
func (f *Frontend) Notify(name string, data ...interface{}) {
notification := EventNotify{
Name: name,
Data: data,
}
payload, err := json.Marshal(notification)
if err != nil {
f.logger.Error(err.Error())
return
}
f.ExecJS(`window.wails.EventsNotify('` + string(payload) + `');`)
}
func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) {
//Get the request
uri, _ := req.GetUri()
// Translate URI
uri = strings.TrimPrefix(uri, "file://wails")
if !strings.HasPrefix(uri, "/") {
return
}
// Load file from asset store
content, mimeType, err := f.assets.Load(uri)
if err != nil {
return
}
env := f.chromium.Environment()
headers := "Content-Type: " + mimeType
if f.servingFromDisk {
headers += "\nPragma: no-cache"
}
response, err := env.CreateWebResourceResponse(content, 200, "OK", headers)
if err != nil {
return
}
// Send response back
err = args.PutResponse(response)
if err != nil {
return
}
return
}
func (f *Frontend) processMessage(message string) {
if message == "drag" {
err := f.startDrag()
if err != nil {
f.logger.Error(err.Error())
}
return
}
result, err := f.dispatcher.ProcessMessage(message, f)
if err != nil {
f.logger.Error(err.Error())
f.Callback(result)
return
}
if result == "" {
return
}
switch result[0] {
case 'c':
// Callback from a method call
f.Callback(result[1:])
default:
f.logger.Info("Unknown message returned from dispatcher: %+v", result)
}
}
func (f *Frontend) Callback(message string) {
f.mainWindow.Dispatch(func() {
f.chromium.Eval(`window.wails.Callback(` + strconv.Quote(message) + `);`)
})
}
func (f *Frontend) startDrag() error {
if !w32.ReleaseCapture() {
return fmt.Errorf("unable to release mouse capture")
}
w32.SendMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0)
return nil
}
func (f *Frontend) ExecJS(js string) {
f.mainWindow.Dispatch(func() {
f.chromium.Eval(js)
})
}
func (f *Frontend) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) {
if f.frontendOptions.OnDomReady != nil {
go f.frontendOptions.OnDomReady(f.ctx)
}
// If you want to start hidden, return
if f.frontendOptions.StartHidden {
return
}
// Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026
err := f.chromium.Hide()
if err != nil {
log.Fatal(err)
}
err = f.chromium.Show()
if err != nil {
log.Fatal(err)
}
f.mainWindow.Show()
}

View File

@@ -0,0 +1,202 @@
//go:build windows
package windows
import (
"github.com/leaanthony/winc"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
"strings"
)
var ModifierMap = map[keys.Modifier]winc.Modifiers{
keys.ShiftKey: winc.ModShift,
keys.ControlKey: winc.ModControl,
keys.OptionOrAltKey: winc.ModAlt,
keys.CmdOrCtrlKey: winc.ModControl,
}
func acceleratorToWincShortcut(accelerator *keys.Accelerator) winc.Shortcut {
if accelerator == nil {
return winc.NoShortcut
}
inKey := strings.ToUpper(accelerator.Key)
key, exists := keyMap[inKey]
if !exists {
return winc.NoShortcut
}
var modifiers winc.Modifiers
if _, exists := shiftMap[inKey]; exists {
modifiers = winc.ModShift
}
for _, mod := range accelerator.Modifiers {
modifiers |= ModifierMap[mod]
}
return winc.Shortcut{
Modifiers: modifiers,
Key: key,
}
}
var shiftMap = map[string]struct{}{
"~": {},
")": {},
"!": {},
"@": {},
"#": {},
"$": {},
"%": {},
"^": {},
"&": {},
"*": {},
"(": {},
"_": {},
"PLUS": {},
"<": {},
">": {},
"?": {},
":": {},
`"`: {},
"{": {},
"}": {},
"|": {},
}
var keyMap = map[string]winc.Key{
"0": winc.Key0,
"1": winc.Key1,
"2": winc.Key2,
"3": winc.Key3,
"4": winc.Key4,
"5": winc.Key5,
"6": winc.Key6,
"7": winc.Key7,
"8": winc.Key8,
"9": winc.Key9,
"A": winc.KeyA,
"B": winc.KeyB,
"C": winc.KeyC,
"D": winc.KeyD,
"E": winc.KeyE,
"F": winc.KeyF,
"G": winc.KeyG,
"H": winc.KeyH,
"I": winc.KeyI,
"J": winc.KeyJ,
"K": winc.KeyK,
"L": winc.KeyL,
"M": winc.KeyM,
"N": winc.KeyN,
"O": winc.KeyO,
"P": winc.KeyP,
"Q": winc.KeyQ,
"R": winc.KeyR,
"S": winc.KeyS,
"T": winc.KeyT,
"U": winc.KeyU,
"V": winc.KeyV,
"W": winc.KeyW,
"X": winc.KeyX,
"Y": winc.KeyY,
"Z": winc.KeyZ,
"F1": winc.KeyF1,
"F2": winc.KeyF2,
"F3": winc.KeyF3,
"F4": winc.KeyF4,
"F5": winc.KeyF5,
"F6": winc.KeyF6,
"F7": winc.KeyF7,
"F8": winc.KeyF8,
"F9": winc.KeyF9,
"F10": winc.KeyF10,
"F11": winc.KeyF11,
"F12": winc.KeyF12,
"F13": winc.KeyF13,
"F14": winc.KeyF14,
"F15": winc.KeyF15,
"F16": winc.KeyF16,
"F17": winc.KeyF17,
"F18": winc.KeyF18,
"F19": winc.KeyF19,
"F20": winc.KeyF20,
"F21": winc.KeyF21,
"F22": winc.KeyF22,
"F23": winc.KeyF23,
"F24": winc.KeyF24,
"`": winc.KeyOEM3,
",": winc.KeyOEMComma,
".": winc.KeyOEMPeriod,
"/": winc.KeyOEM2,
";": winc.KeyOEM1,
"'": winc.KeyOEM7,
"[": winc.KeyOEM4,
"]": winc.KeyOEM6,
`\`: winc.KeyOEM5,
"~": winc.KeyOEM3, //
")": winc.Key0,
"!": winc.Key1,
"@": winc.Key2,
"#": winc.Key3,
"$": winc.Key4,
"%": winc.Key5,
"^": winc.Key6,
"&": winc.Key7,
"*": winc.Key8,
"(": winc.Key9,
"_": winc.KeyOEMMinus,
"PLUS": winc.KeyOEMPlus,
"<": winc.KeyOEMComma,
">": winc.KeyOEMPeriod,
"?": winc.KeyOEM2,
":": winc.KeyOEM1,
`"`: winc.KeyOEM7,
"{": winc.KeyOEM4,
"}": winc.KeyOEM6,
"|": winc.KeyOEM5,
"SPACE": winc.KeySpace,
"TAB": winc.KeyTab,
"CAPSLOCK": winc.KeyCapital,
"NUMLOCK": winc.KeyNumlock,
"SCROLLLOCK": winc.KeyScroll,
"BACKSPACE": winc.KeyBack,
"DELETE": winc.KeyDelete,
"INSERT": winc.KeyInsert,
"RETURN": winc.KeyReturn,
"ENTER": winc.KeyReturn,
"UP": winc.KeyUp,
"DOWN": winc.KeyDown,
"LEFT": winc.KeyLeft,
"RIGHT": winc.KeyRight,
"HOME": winc.KeyHome,
"END": winc.KeyEnd,
"PAGEUP": winc.KeyPrior,
"PAGEDOWN": winc.KeyNext,
"ESCAPE": winc.KeyEscape,
"ESC": winc.KeyEscape,
"VOLUMEUP": winc.KeyVolumeUp,
"VOLUMEDOWN": winc.KeyVolumeDown,
"VOLUMEMUTE": winc.KeyVolumeMute,
"MEDIANEXTTRACK": winc.KeyMediaNextTrack,
"MEDIAPREVIOUSTRACK": winc.KeyMediaPrevTrack,
"MEDIASTOP": winc.KeyMediaStop,
"MEDIAPLAYPAUSE": winc.KeyMediaPlayPause,
"PRINTSCREEN": winc.KeyPrint,
"NUM0": winc.KeyNumpad0,
"NUM1": winc.KeyNumpad1,
"NUM2": winc.KeyNumpad2,
"NUM3": winc.KeyNumpad3,
"NUM4": winc.KeyNumpad4,
"NUM5": winc.KeyNumpad5,
"NUM6": winc.KeyNumpad6,
"NUM7": winc.KeyNumpad7,
"NUM8": winc.KeyNumpad8,
"NUM9": winc.KeyNumpad9,
"nummult": winc.KeyMultiply,
"numadd": winc.KeyAdd,
"numsub": winc.KeySubtract,
"numdec": winc.KeyDecimal,
"numdiv": winc.KeyDivide,
}

View File

@@ -0,0 +1,129 @@
//go:build windows
package windows
import (
"github.com/leaanthony/winc"
"github.com/wailsapp/wails/v2/pkg/menu"
)
var checkboxMap = map[*menu.MenuItem][]*winc.MenuItem{}
var radioGroupMap = map[*menu.MenuItem][]*winc.MenuItem{}
func toggleCheckBox(menuItem *menu.MenuItem) {
menuItem.Checked = !menuItem.Checked
for _, wincMenu := range checkboxMap[menuItem] {
wincMenu.SetChecked(menuItem.Checked)
}
}
func addCheckBoxToMap(menuItem *menu.MenuItem, wincMenuItem *winc.MenuItem) {
if checkboxMap[menuItem] == nil {
checkboxMap[menuItem] = []*winc.MenuItem{}
}
checkboxMap[menuItem] = append(checkboxMap[menuItem], wincMenuItem)
}
func toggleRadioItem(menuItem *menu.MenuItem) {
menuItem.Checked = !menuItem.Checked
for _, wincMenu := range radioGroupMap[menuItem] {
wincMenu.SetChecked(menuItem.Checked)
}
}
func addRadioItemToMap(menuItem *menu.MenuItem, wincMenuItem *winc.MenuItem) {
if radioGroupMap[menuItem] == nil {
radioGroupMap[menuItem] = []*winc.MenuItem{}
}
radioGroupMap[menuItem] = append(radioGroupMap[menuItem], wincMenuItem)
}
func (w *Window) SetApplicationMenu(menu *menu.Menu) {
w.applicationMenu = menu
processMenu(w, menu)
}
func processMenu(window *Window, menu *menu.Menu) {
mainMenu := window.NewMenu()
for _, menuItem := range menu.Items {
submenu := mainMenu.AddSubMenu(menuItem.Label)
for _, menuItem := range menuItem.SubMenu.Items {
processMenuItem(submenu, menuItem)
}
}
mainMenu.Show()
}
func processMenuItem(parent *winc.MenuItem, menuItem *menu.MenuItem) {
if menuItem.Hidden {
return
}
switch menuItem.Type {
case menu.SeparatorType:
parent.AddSeparator()
case menu.TextType:
shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
newItem := parent.AddItem(menuItem.Label, shortcut)
if menuItem.Tooltip != "" {
newItem.SetToolTip(menuItem.Tooltip)
}
if menuItem.Click != nil {
newItem.OnClick().Bind(func(e *winc.Event) {
menuItem.Click(&menu.CallbackData{
MenuItem: menuItem,
})
})
}
newItem.SetEnabled(!menuItem.Disabled)
case menu.CheckboxType:
shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
newItem := parent.AddItem(menuItem.Label, shortcut)
newItem.SetCheckable(true)
newItem.SetChecked(menuItem.Checked)
if menuItem.Tooltip != "" {
newItem.SetToolTip(menuItem.Tooltip)
}
if menuItem.Click != nil {
newItem.OnClick().Bind(func(e *winc.Event) {
toggleCheckBox(menuItem)
menuItem.Click(&menu.CallbackData{
MenuItem: menuItem,
})
})
}
newItem.SetEnabled(!menuItem.Disabled)
addCheckBoxToMap(menuItem, newItem)
case menu.RadioType:
shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
newItem := parent.AddItemRadio(menuItem.Label, shortcut)
newItem.SetCheckable(true)
newItem.SetChecked(menuItem.Checked)
if menuItem.Tooltip != "" {
newItem.SetToolTip(menuItem.Tooltip)
}
if menuItem.Click != nil {
newItem.OnClick().Bind(func(e *winc.Event) {
toggleRadioItem(menuItem)
menuItem.Click(&menu.CallbackData{
MenuItem: menuItem,
})
})
}
newItem.SetEnabled(!menuItem.Disabled)
addRadioItemToMap(menuItem, newItem)
case menu.SubmenuType:
submenu := parent.AddSubMenu(menuItem.Label)
for _, menuItem := range menuItem.SubMenu.Items {
processMenuItem(submenu, menuItem)
}
}
}
func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
f.mainWindow.SetApplicationMenu(menu)
}
func (f *Frontend) MenuUpdateApplicationMenu() {
processMenu(f.mainWindow, f.mainWindow.applicationMenu)
}

View File

@@ -0,0 +1,116 @@
//go:build windows
package windows
import (
"github.com/leaanthony/winc"
"github.com/leaanthony/winc/w32"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
"sync"
)
type Window struct {
winc.Form
frontendOptions *options.App
applicationMenu *menu.Menu
m sync.Mutex
dispatchq []func()
}
func NewWindow(parent winc.Controller, options *options.App) *Window {
result := new(Window)
result.frontendOptions = options
result.SetIsForm(true)
var exStyle int
if options.Windows != nil {
exStyle = w32.WS_EX_CONTROLPARENT | w32.WS_EX_APPWINDOW
if options.Windows.WindowIsTranslucent {
exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP
}
}
var dwStyle = w32.WS_OVERLAPPEDWINDOW
if options.Frameless {
dwStyle = w32.WS_POPUP
}
winc.RegClassOnlyOnce("wailsWindow")
result.SetHandle(winc.CreateWindow("wailsWindow", parent, uint(exStyle), uint(dwStyle)))
result.SetParent(parent)
loadIcon := true
if options.Windows != nil && options.Windows.DisableWindowIcon == true {
loadIcon = false
}
if loadIcon {
if ico, err := winc.NewIconFromResource(winc.GetAppInstance(), uint16(winc.AppIconID)); err == nil {
result.SetIcon(0, ico)
}
}
result.SetSize(options.Width, options.Height)
result.SetText(options.Title)
result.EnableSizable(!options.DisableResize)
result.EnableMaxButton(!options.DisableResize)
result.SetMinSize(options.MinWidth, options.MinHeight)
result.SetMaxSize(options.MaxWidth, options.MaxHeight)
if options.Windows != nil {
if options.Windows.WindowIsTranslucent {
result.SetTranslucentBackground()
}
if options.Windows.DisableWindowIcon {
result.DisableIcon()
}
}
// Dlg forces display of focus rectangles, as soon as the user starts to type.
w32.SendMessage(result.Handle(), w32.WM_CHANGEUISTATE, w32.UIS_INITIALIZE, 0)
winc.RegMsgHandler(result)
result.SetFont(winc.DefaultFont)
if options.Fullscreen {
result.Fullscreen()
}
if options.Menu != nil {
result.SetApplicationMenu(options.Menu)
}
return result
}
func (w *Window) Run() int {
var m w32.MSG
for w32.GetMessage(&m, 0, 0, 0) != 0 {
if m.Message == w32.WM_APP {
// Credit: https://github.com/jchv/go-webview2
w.m.Lock()
q := append([]func(){}, w.dispatchq...)
w.dispatchq = []func(){}
w.m.Unlock()
for _, v := range q {
v()
}
}
if !w.PreTranslateMessage(&m) {
w32.TranslateMessage(&m)
w32.DispatchMessage(&m)
}
}
w32.GdiplusShutdown()
return int(m.WParam)
}
func (w *Window) Dispatch(f func()) {
w.m.Lock()
w.dispatchq = append(w.dispatchq, f)
w.m.Unlock()
w32.PostMainThreadMessage(w32.WM_APP, 0, 0)
}

View File

@@ -0,0 +1,369 @@
//go:build dev
// Package devserver provides a web-based frontend so that
// it is possible to run a Wails app in a browsers.
package devserver
import (
"context"
"encoding/json"
"fmt"
"io/fs"
"log"
"path/filepath"
"strings"
"sync"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
)
type DevWebServer struct {
server *fiber.App
ctx context.Context
appoptions *options.App
logger *logger.Logger
appBindings *binding.Bindings
dispatcher frontend.Dispatcher
assetServer *assetserver.AssetServer
socketMutex sync.Mutex
websocketClients map[*websocket.Conn]struct{}
menuManager *menumanager.Manager
starttime string
// Desktop frontend
desktopFrontend frontend.Frontend
}
func (d *DevWebServer) WindowReload() {
d.broadcast("reload")
}
func (d *DevWebServer) Run(ctx context.Context) error {
d.ctx = ctx
d.server.Get("/wails/reload", func(fctx *fiber.Ctx) error {
d.WindowReload()
d.desktopFrontend.WindowReload()
return nil
})
d.server.Get("/wails/ipc", websocket.New(func(c *websocket.Conn) {
d.newWebsocketSession(c)
// websocket.Conn bindings https://pkg.go.dev/github.com/fasthttp/websocket?tab=doc#pkg-index
var (
mt int
msg []byte
err error
)
for {
if mt, msg, err = c.ReadMessage(); err != nil {
break
}
// d.logger.Info("[%p] %s", c, msg)
if string(msg) == "drag" {
continue
}
// Notify the other browsers
if len(msg) > 2 && strings.HasPrefix(string(msg), "EE") {
d.notifyExcludingSender(msg, c)
}
// Send the message to dispatch to the frontend
result, err := d.dispatcher.ProcessMessage(string(msg), d)
if err != nil {
d.logger.Error(err.Error())
}
if result != "" {
if err = c.WriteMessage(mt, []byte(result)); err != nil {
log.Println("write:", err)
break
}
}
}
}))
_assetdir := ctx.Value("assetdir")
if _assetdir == nil {
return fmt.Errorf("no assetdir provided")
}
if _assetdir != nil {
assetdir := _assetdir.(string)
bindingsJSON, err := d.appBindings.ToJSON()
if err != nil {
log.Fatal(err)
}
d.assetServer, err = assetserver.NewAssetServer(assetdir, bindingsJSON, d.appoptions)
if err != nil {
log.Fatal(err)
}
absdir, err := filepath.Abs(assetdir)
if err != nil {
return err
}
d.LogInfo("Serving assets from: %s", absdir)
}
d.server.Get("*", d.loadAsset)
// Start server
go func(server *fiber.App, log *logger.Logger) {
err := server.Listen(":34115")
if err != nil {
log.Error(err.Error())
}
d.LogInfo("Shutdown completed")
}(d.server, d.logger)
d.LogInfo("Serving application at http://localhost:34115")
// Launch desktop app
err := d.desktopFrontend.Run(ctx)
d.LogInfo("Starting shutdown")
err2 := d.server.Shutdown()
if err2 != nil {
d.logger.Error(err.Error())
}
return err
}
func (d *DevWebServer) Quit() {
d.desktopFrontend.Quit()
}
func (d *DevWebServer) OpenFileDialog(dialogOptions frontend.OpenDialogOptions) (string, error) {
return d.desktopFrontend.OpenFileDialog(dialogOptions)
}
func (d *DevWebServer) OpenMultipleFilesDialog(dialogOptions frontend.OpenDialogOptions) ([]string, error) {
return d.OpenMultipleFilesDialog(dialogOptions)
}
func (d *DevWebServer) OpenDirectoryDialog(dialogOptions frontend.OpenDialogOptions) (string, error) {
return d.OpenDirectoryDialog(dialogOptions)
}
func (d *DevWebServer) SaveFileDialog(dialogOptions frontend.SaveDialogOptions) (string, error) {
return d.desktopFrontend.SaveFileDialog(dialogOptions)
}
func (d *DevWebServer) MessageDialog(dialogOptions frontend.MessageDialogOptions) (string, error) {
return d.desktopFrontend.MessageDialog(dialogOptions)
}
func (d *DevWebServer) WindowSetTitle(title string) {
d.desktopFrontend.WindowSetTitle(title)
}
func (d *DevWebServer) WindowShow() {
d.desktopFrontend.WindowShow()
}
func (d *DevWebServer) WindowHide() {
d.desktopFrontend.WindowHide()
}
func (d *DevWebServer) WindowCenter() {
d.desktopFrontend.WindowCenter()
}
func (d *DevWebServer) WindowMaximise() {
d.desktopFrontend.WindowMaximise()
}
func (d *DevWebServer) WindowUnmaximise() {
d.desktopFrontend.WindowUnmaximise()
}
func (d *DevWebServer) WindowMinimise() {
d.desktopFrontend.WindowMinimise()
}
func (d *DevWebServer) WindowUnminimise() {
d.desktopFrontend.WindowUnminimise()
}
func (d *DevWebServer) WindowSetPos(x int, y int) {
d.desktopFrontend.WindowSetPos(x, y)
}
func (d *DevWebServer) WindowGetPos() (int, int) {
return d.desktopFrontend.WindowGetPos()
}
func (d *DevWebServer) WindowSetSize(width int, height int) {
d.desktopFrontend.WindowSetSize(width, height)
}
func (d *DevWebServer) WindowGetSize() (int, int) {
return d.desktopFrontend.WindowGetSize()
}
func (d *DevWebServer) WindowSetMinSize(width int, height int) {
d.desktopFrontend.WindowSetMinSize(width, height)
}
func (d *DevWebServer) WindowSetMaxSize(width int, height int) {
d.desktopFrontend.WindowSetMaxSize(width, height)
}
func (d *DevWebServer) WindowFullscreen() {
d.desktopFrontend.WindowFullscreen()
}
func (d *DevWebServer) WindowUnFullscreen() {
d.desktopFrontend.WindowUnFullscreen()
}
func (d *DevWebServer) WindowSetRGBA(col *options.RGBA) {
d.desktopFrontend.WindowSetRGBA(col)
}
func (d *DevWebServer) MenuSetApplicationMenu(menu *menu.Menu) {
d.desktopFrontend.MenuSetApplicationMenu(menu)
}
func (d *DevWebServer) MenuUpdateApplicationMenu() {
d.desktopFrontend.MenuUpdateApplicationMenu()
}
// BrowserOpenURL uses the system default browser to open the url
func (d *DevWebServer) BrowserOpenURL(url string) {
d.desktopFrontend.BrowserOpenURL(url)
}
func (d *DevWebServer) Notify(name string, data ...interface{}) {
d.notify(name, data...)
}
func (d *DevWebServer) loadAsset(ctx *fiber.Ctx) error {
data, mimetype, err := d.assetServer.Load(ctx.Path())
if err != nil {
_, ok := err.(*fs.PathError)
if !ok {
return err
}
err := ctx.SendStatus(404)
if err != nil {
return err
}
return nil
}
err = ctx.SendStatus(200)
if err != nil {
return err
}
ctx.Set("Content-Type", mimetype)
err = ctx.Send(data)
if err != nil {
return err
}
return nil
}
func (d *DevWebServer) LogInfo(message string, args ...interface{}) {
d.logger.Info("[DevWebServer] "+message, args...)
}
func (d *DevWebServer) newWebsocketSession(c *websocket.Conn) {
d.socketMutex.Lock()
defer d.socketMutex.Unlock()
c.SetCloseHandler(func(code int, text string) error {
d.socketMutex.Lock()
defer d.socketMutex.Unlock()
delete(d.websocketClients, c)
d.LogInfo(fmt.Sprintf("Websocket client %p disconnected", c))
return nil
})
d.websocketClients[c] = struct{}{}
d.LogInfo(fmt.Sprintf("Websocket client %p connected", c))
}
type EventNotify struct {
Name string `json:"name"`
Data []interface{} `json:"data"`
}
func (d *DevWebServer) broadcast(message string) {
d.socketMutex.Lock()
defer d.socketMutex.Unlock()
for client := range d.websocketClients {
err := client.WriteMessage(websocket.TextMessage, []byte(message))
if err != nil {
d.logger.Error(err.Error())
return
}
}
}
func (d *DevWebServer) notify(name string, data ...interface{}) {
// Notify
notification := EventNotify{
Name: name,
Data: data,
}
payload, err := json.Marshal(notification)
if err != nil {
d.logger.Error(err.Error())
return
}
d.broadcast("n" + string(payload))
}
func (d *DevWebServer) broadcastExcludingSender(message string, sender *websocket.Conn) {
d.socketMutex.Lock()
defer d.socketMutex.Unlock()
for client := range d.websocketClients {
if client == sender {
continue
}
err := client.WriteMessage(websocket.TextMessage, []byte(message))
if err != nil {
d.logger.Error(err.Error())
return
}
}
}
func (d *DevWebServer) notifyExcludingSender(eventMessage []byte, sender *websocket.Conn) {
message := "n" + string(eventMessage[2:])
d.broadcastExcludingSender(message, sender)
var notifyMessage EventNotify
err := json.Unmarshal(eventMessage[2:], &notifyMessage)
if err != nil {
d.logger.Error(err.Error())
return
}
d.desktopFrontend.Notify(notifyMessage.Name, notifyMessage.Data...)
}
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher, menuManager *menumanager.Manager, desktopFrontend frontend.Frontend) *DevWebServer {
result := &DevWebServer{
ctx: ctx,
desktopFrontend: desktopFrontend,
appoptions: appoptions,
logger: myLogger,
appBindings: appBindings,
dispatcher: dispatcher,
server: fiber.New(fiber.Config{
ReadTimeout: time.Second * 5,
DisableStartupMessage: true,
}),
menuManager: menuManager,
websocketClients: make(map[*websocket.Conn]struct{}),
}
return result
}

View File

@@ -0,0 +1,5 @@
package frontend
type Dispatcher interface {
ProcessMessage(message string, sender Frontend) (string, error)
}

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