Compare commits

..

122 Commits

Author SHA1 Message Date
Lea Anthony
c13257b9e9 [v2] v2.0.0-alpha.66 2021-06-05 14:37:12 +10:00
Lea Anthony
3f773f80ac [linux] Fix header signatures 2021-06-05 14:22:37 +10:00
Lea Anthony
5b23122b35 [v2] Make user + email discovery optional 2021-06-05 14:18:42 +10:00
Lea Anthony
d5cbfa6749 [linux] Support wails build and wails dev 2021-06-04 20:36:56 +10:00
Lea Anthony
3ad92192a9 [linux] Add linux runtime to wails dev 2021-06-04 20:34:55 +10:00
Lea Anthony
466676d99f [linux] Create Linux runtime 2021-06-04 20:32:40 +10:00
Lea Anthony
a1d5412465 [v2] Remove redundant apps package 2021-06-03 21:32:19 +10:00
Lea Anthony
9fdd5148ca [mac] Update runtime to use window.wailsInvoke 2021-06-03 21:01:14 +10:00
Lea Anthony
ac957b27cb [v1] Upgrade vanilla template to use runtime v1.3.19 2021-06-03 21:00:13 +10:00
Lea Anthony
20bc332720 [v1] Fix vanilla template package-lock.json 2021-06-03 20:33:25 +10:00
Lea Anthony
d2507660c2 [linux] Get CLI working. yum->dnf. doctor improvements. 2021-06-02 20:02:45 +10:00
Lea Anthony
64264c6378 [v2] Add README to vanilla template 2021-06-01 21:40:35 +10:00
Lea Anthony
ddadcdc18f [v2] Rebuild runtime 2021-05-31 20:09:04 +10:00
Lea Anthony
4738a0e0a8 [v2] Remove common.js 2021-05-31 20:08:11 +10:00
Lea Anthony
d2e4a0b0d2 [windows] Support wails dev 2021-05-31 20:01:40 +10:00
Lea Anthony
9f365ed85e [windows] Support rollup in vanilla template 2021-05-30 20:05:07 +10:00
Lea Anthony
5e733657c2 [v2] Updated runtime 2021-05-30 20:03:28 +10:00
Lea Anthony
93d9b61366 [windows] Fix bridge payload for 'wails dev' 2021-05-30 20:03:01 +10:00
Lea Anthony
b5d289bda9 [v2] Published @wails/runtime v1.3.14 2021-05-30 11:24:15 +10:00
Lea Anthony
319b7a3755 [v2] Change import to require for bridge for compatibility reasons 2021-05-30 11:22:40 +10:00
Lea Anthony
410d23b4d9 [windows] wails doctor docker support Better output 2021-05-30 11:06:38 +10:00
Lea Anthony
003e505c4a [mac] detect upx 2021-05-30 10:19:27 +10:00
Lea Anthony
22f09772d3 [mac] better wails doctor 2021-05-30 10:18:36 +10:00
Lea Anthony
a300172adc [windows] Better wails doctor installation details 2021-05-30 09:41:47 +10:00
Lea Anthony
1eba408f64 [windows] Better wails doctor diagnostics 2021-05-30 09:34:07 +10:00
Lea Anthony
be39b293b5 [mac] better wails doctor 2021-05-26 21:18:52 +10:00
Travis McLane
31cf04a944 fix shadowed err (#718) 2021-05-19 05:55:05 +10:00
Lea Anthony
f5912d29b6 Updated README.md 2021-05-18 21:29:03 +10:00
Lea Anthony
caad3a6b00 [windows] add icon to main window 2021-05-18 21:25:34 +10:00
Lea Anthony
08f4476087 Support for upx and more go:embed. 2021-05-18 21:25:16 +10:00
Lea Anthony
46ea3e6074 Better handling of process in wails dev 2021-05-18 21:22:52 +10:00
Lea Anthony
8be2a39daf [windows] Generate syso file for windows builds 2021-05-17 19:51:43 +10:00
Lea Anthony
7591b45ffa [windows] fix for no windows config 2021-05-17 19:50:12 +10:00
Travis McLane
f5056e7232 hack: enable builds on non-Windows 2021-05-16 13:51:14 +10:00
Alexander Hudek
101d344303 Merge pull request #715
* Fixed multi-line tags being minified incorrectly. Fixed self closing …

* Improved html whitespace minification fix.
2021-05-16 13:22:44 +10:00
Lea Anthony
6ab1a4adb0 Support passing arguments when using wails dev 2021-05-14 14:22:25 +10:00
Lea Anthony
5a30425091 [windows] Fix broken WebView2Loader.dll 2021-05-14 09:09:52 +10:00
Lea Anthony
e9deb248f9 Move assets -> build dir. Bundle assets in CLI binary. 2021-05-13 20:12:19 +10:00
Lea Anthony
7d0ff8b1a2 [windows] Updated webview.dll 2021-05-13 20:03:39 +10:00
Alexander Hudek
8399cc1e57 Fix width and height being set to zero when no max width and max height is set. (#709) 2021-05-13 18:49:53 +10:00
Alexander Hudek
6b8370daad Implement server to client notify support. (#700) 2021-05-02 13:13:18 +10:00
Alexander Hudek
3c4f874db5 Implement server to client notify support. (#700) 2021-05-02 13:12:29 +10:00
Alexander Hudek
0966c96ef0 Fixed generated typescript type for []byte. (#701) 2021-05-02 13:08:49 +10:00
Lea Anthony
6b919808c9 [windows] Added options, WindowBackgroundIsTranslucent 2021-05-02 12:04:29 +10:00
Lea Anthony
590d4fcdbb [windows] Updated WebView2 to Microsoft.Web.WebView2.1.0.818.41 2021-05-02 11:13:27 +10:00
Lea Anthony
4acb3f83bf [windows] Support Startup hook 2021-05-01 07:38:30 +10:00
Lea Anthony
df32393215 [windows] Make initial loading a sync operation 2021-05-01 07:20:36 +10:00
Lea Anthony
ff3e03220e [windows] Backend calls now working 2021-05-01 06:44:07 +10:00
Lea Anthony
5da198de7e [windows] App assets loading correctly 2021-04-30 21:14:09 +10:00
Lea Anthony
247df54ef0 update vanilla template css 2021-04-30 17:44:40 +10:00
Lea Anthony
161eeca62b [windows] update eslint ecmaversion 2021-04-30 16:05:29 +10:00
Lea Anthony
0b8056bcf4 [windows] Windows runtime 2021-04-30 16:04:24 +10:00
Lea Anthony
62bfe953a1 [windows] Add CGO_CXXFLAGS 2021-04-30 16:00:09 +10:00
Lea Anthony
67611d5ae7 [windows] Support -compress flag for UPX compression 2021-04-29 21:53:17 +10:00
Lea Anthony
77b5235c9f [windows] Compressed DLLs 2021-04-29 21:36:26 +10:00
Lea Anthony
09755d5bfe [windows] Adjust when resizing 2021-04-29 21:25:20 +10:00
Lea Anthony
0224228c46 [windows] Add DPI awareness 2021-04-29 21:25:03 +10:00
Lea Anthony
168cd96f56 Fix some warnings 2021-04-29 07:36:59 +10:00
Lea Anthony
6f6e810432 [windows] WebView2 component working 2021-04-29 07:36:40 +10:00
Lea Anthony
944e1c99ff [windows] Closer to webview2 integration 2021-04-28 15:45:36 +10:00
Lea Anthony
7f54f7bb7e fix frameless option 2021-04-28 15:44:55 +10:00
Lea Anthony
2881a5bc0d [windows] output required DLLs on build 2021-04-28 15:44:29 +10:00
Lea Anthony
ee05884c9c [windows] ON_MAIN_THEAD macro. Misc updates 2021-04-27 21:09:04 +10:00
Lea Anthony
385988989b fix: min/max constraint logic 2021-04-27 19:32:25 +10:00
Lea Anthony
b98c7dd49f [windows] Move to C++. Support unicode titles. Add test. 2021-04-26 20:21:11 +10:00
Lea Anthony
b3c0cc86d3 Merge remote-tracking branch 'origin/v2-alpha' into v2-alpha 2021-04-26 19:09:04 +10:00
Lea Anthony
ad34e55a67 [windows] wrap ffenestri.h for c++ 2021-04-26 18:55:04 +10:00
Lea Anthony
1bdbf68ca9 [windows] Main thread dispatcher. Small refactor. 2021-04-26 07:14:24 +10:00
Lea Anthony
84f37c66ba [windows] Center 2021-04-25 21:16:27 +10:00
Lea Anthony
f7c65b1705 [windows] Show,Hide,Maximise,Minimise,Unmaximise,Unminimise,ToggleMaximise,ToggleMinimise 2021-04-25 15:53:48 +10:00
Lea Anthony
134b41e4be [windows] StartHidden 2021-04-25 15:27:09 +10:00
Lea Anthony
df41e8eb3a [windows] DisableResize 2021-04-25 15:20:30 +10:00
Lea Anthony
0f668a9038 Min/Max size + setTitle 2021-04-25 15:03:14 +10:00
Lea Anthony
dea6d261ad Ensure min/max window sizes are valid 2021-04-25 14:52:42 +10:00
Lea Anthony
97592fad5c [win] Initial window working 2021-04-24 20:59:00 +10:00
Lea Anthony
8caf277bf1 v2.0.0-alpha.65 2021-04-20 19:44:27 +10:00
Lea Anthony
4b480bb085 Move templates to CLI dir. Use go:embed 2021-04-18 17:10:12 +10:00
Lea Anthony
c1d63aff34 Refactor vanilla template to work with wails dev out of the box 2021-04-18 07:25:02 +10:00
Lea Anthony
5fc89c4cad Fix Promise<void> return type. Tidy up. 2021-04-18 07:24:29 +10:00
Lea Anthony
5e96bb5a32 [windows] Improve wails doctor 2021-04-17 13:40:27 +10:00
Lea Anthony
c5e76c50b0 v2.0.0-alpha.64 2021-04-13 21:07:24 +10:00
Lea Anthony
e40226ff7a ANSI support for tray labels and submenus 2021-04-13 21:05:56 +10:00
Lea Anthony
53a3638fa8 ANSI support for tray labels 2021-04-13 05:43:37 +10:00
Lea Anthony
1344638c52 Add OS detection 2021-04-12 06:25:15 +10:00
Lea Anthony
6fdc87454a Initial support for desktop notifications 2021-04-07 06:32:21 +10:00
Lea Anthony
c36f9501a9 Dialog warning on JS Error. Remove shutdown hack. Optional dialog callback. 2021-04-06 20:18:29 +10:00
Lea Anthony
c23b43c352 Improve vanilla app JS 2021-04-04 15:47:03 +10:00
Lea Anthony
a76851463b Font smoothing for vanilla app 2021-04-04 15:45:24 +10:00
Lea Anthony
e17b432c8f Tidy up templates 2021-04-04 15:36:55 +10:00
Lea Anthony
5d444cd6dd Support user tags 2021-04-04 13:42:48 +10:00
Lea Anthony
be43049fc6 Remove debug code 2021-04-04 05:25:21 +10:00
Lea Anthony
2e01710412 v2.0.0-alpha.63 2021-04-04 05:15:09 +10:00
Lea Anthony
1b0193161c Improvements for handling Info.plist 2021-04-04 05:14:16 +10:00
Lea Anthony
1b377fb575 Support enter as alias for return 2021-04-03 16:52:54 +11:00
Lea Anthony
1203ae64b8 v2.0.0-alpha.62 2021-03-31 15:44:28 +11:00
Lea Anthony
26ed8002b9 Menu bar font fix 2021-03-31 15:43:28 +11:00
Lea Anthony
cf23bffc67 v2.0.0-alpha.61 2021-03-30 22:22:48 +11:00
Lea Anthony
d70f6fffe7 Remove autorelease of colour. Fixes high sierra. 2021-03-30 22:21:49 +11:00
Lea Anthony
54c99fc386 Move some methods to main thread 2021-03-29 20:45:50 +11:00
Lea Anthony
86c1ea5e6a Initial support for compression 2021-03-27 20:59:14 +11:00
Lea Anthony
b394c1914c v2.0.0-alpha.60 2021-03-26 20:54:42 +11:00
Lea Anthony
91c2ddf155 Allow styling of menu items that have submenus 2021-03-26 20:54:19 +11:00
Lea Anthony
712ad96d2a v2.0.0-alpha.59 2021-03-26 18:30:00 +11:00
Lea Anthony
86b4a4f2f5 Don't clean directory when doing universal builds 2021-03-26 18:29:28 +11:00
Lea Anthony
4b9786abc9 v2.0.0-alpha.58 2021-03-26 18:13:10 +11:00
Lea Anthony
fd96ebc050 Better verbose output 2021-03-26 18:12:42 +11:00
Lea Anthony
939e0f5975 Use default broken image for invalid images 2021-03-26 17:51:49 +11:00
Lea Anthony
6a7a288a0f Limit StartsAtLogin to app bundles 2021-03-26 16:52:59 +11:00
Lea Anthony
0564d0aa98 v2.0.0-alpha.57 2021-03-26 16:32:10 +11:00
Lea Anthony
3a136a73ca Add package for mac functions 2021-03-26 15:57:30 +11:00
Lea Anthony
50c219307f Add clean flag 2021-03-26 14:10:25 +11:00
Lea Anthony
de3038b302 v2.0.0-alpha.56 2021-03-25 21:13:40 +11:00
Lea Anthony
6eb4b0a419 Fix packaging universal builds 2021-03-25 21:12:29 +11:00
Lea Anthony
41d2158375 Support building arm64 & universal binaries. 2021-03-25 21:12:29 +11:00
Lea Anthony
5d7f57e80b Initial ARM support! 2021-03-25 21:12:29 +11:00
Lea Anthony
4ce3e1d1bf v2.0.0-alpha.55 2021-03-21 20:59:23 +11:00
Lea Anthony
e5f2746810 Better font name support 2021-03-21 20:57:41 +11:00
Lea Anthony
92ebf506dd Get app compiling 2021-03-20 18:32:09 +11:00
Lea Anthony
9ab06152c5 Refactor doctor for windows 2021-03-20 16:27:35 +11:00
Lea Anthony
bf36b6a59d Doctor working for Windows 2021-03-20 16:24:02 +11:00
Lea Anthony
4b9f6c4fb1 Update CONTRIBUTORS.md 2021-03-20 15:55:14 +11:00
Amaury Tobias Quiroz
b1a42c8dea fix: support rel="modulepreload" on assetbundle as javascript (#621)
In some bundler enviroments like vite the output on index.html uses link
tag with rel="modulepreload" to load javascript, add support to handle
this files on assetbundle, more info
https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/modulepreload

fix #620

Co-authored-by: amaury-tobias <amaury.tobiasqr@gmai.com>
2021-03-20 15:51:57 +11:00
266 changed files with 23328 additions and 5348 deletions

4
.gitignore vendored
View File

@@ -27,3 +27,7 @@ v2/pkg/parser/testproject/frontend/wails
v2/test/kitchensink/frontend/public
v2/test/kitchensink/build/darwin/desktop/kitchensink
v2/test/kitchensink/frontend/package.json.md5
/v2/internal/ffenestri/windows/test/cmake-build-debug/
!v2/internal/ffenestri/windows/x64/webview2.dll
!v2/internal/ffenestri/windows/x64/WebView2Loader.dll
.idea/

View File

@@ -40,4 +40,5 @@ Wails is what it is because of the time and effort given by these great people.
* [Balakrishna Prasad Ganne](https://github.com/aayush420)
* [Charaf Rezrazi](https://github.com/Rezrazi)
* [misitebao](https://github.com/misitebao)
* [Elie Grenon](https://github.com/DrunkenPoney)
* [Elie Grenon](https://github.com/DrunkenPoney)
* [Amaury Tobias Quiroz](https://github.com/amaury-tobias)

View File

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

View File

@@ -0,0 +1,54 @@
# Build
The build command processes the Wails project and generates an application binary.
## Usage
`wails build <flags>`
### Flags
| Flag | Details | Default |
| :------------- | :----------- | :------ |
| -clean | Clean the bin directory before building | |
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
| -ldflags "custom ld flags" | Use given ldflags | |
| -o path/to/binary | Compile to given path/filename | |
| -k | Keep generated assets | |
| -package | Create a platform specific package | |
| -production | Compile in production mode: -ldflags="-w -s" + "-h windows" on Windows | |
| -tags | Build tags to pass to Go compiler (quoted and space separated) | |
| -upx | Compress final binary with UPX (if installed) | |
| -upxflags "custom flags" | Flags to pass to upx | |
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
## The Build Process
The build process is as follows:
- The flags are processed, and an Options struct built containing the build context.
- The type of target is determined, and a custom build process is followed for target.
### Desktop Target
- The frontend dependencies are installed. The command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
- The frontend is then built. This command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
- The project directory is checked to see if the `build` directory exists. If not, it is created and default project assets are copied to it.
- An asset bundle is then created by reading the `html` key from `wails.json` and loading the referenced file. This is then parsed, looking for local Javascript and CSS references. Those files are in turn loaded into memory, converted to C data and saved into the asset bundle located at `build/assets.h`, which also includes the original HTML.
- The application icon is then processed: if there is no `build/appicon.png`, a default icon is copied. On Windows, an `app.ico` file is generated from this png. On Mac, `icons.icns` is generated.
- If there are icons in the `build/tray` directory, these are processed, converted to C data and saved as `build/trayicons.h`, ready for the compilation step.
- If there are icons in the `build/dialog` directory, these are processed, converted to C data and saved as `build/userdialogicons.h`, ready for the compilation step.
- If the `-package` flag is given for a Windows target, the Windows assets in the `build/windows` directory are processed: manifest + icons compiled to a `.syso` file (deleted after compilation).
- If we are building a universal binary for Mac, the application is compiled for both `arm64` and `amd64`. The `lipo` tool is then executed to create the universal binary.
- If we are not building a universal binary for Mac, the application is built using `go build`, using build tags to indicate type of application and build mode (debug/production).
- If the `-upx` flag was provided, `upx` is invoked to compress the binary. Custom flags may be provided using the `-upxflags` flag.
- If the `package` flag is given for a non Windows target, the application is bundled for the platform. On Mac, this creates a `.app` with the processed icons, the `Info.plist` in `build/darwin` and the compiled binary.
### Server Target
TBD
### Hybrid Target
TBD

View File

@@ -2,19 +2,21 @@ package build
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"text/tabwriter"
"time"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
// AddBuildSubcommand adds the `build` command for the Wails application
func AddBuildSubcommand(app *clir.Cli) {
func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
outputType := "desktop"
@@ -22,77 +24,157 @@ func AddBuildSubcommand(app *clir.Cli) {
command := app.NewSubCommand("build", "Builds the application")
// Setup target type flag
description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
// Setup production flag
production := false
command.BoolFlag("production", "Build in production mode", &production)
// Setup pack flag
pack := false
command.BoolFlag("pack", "Create a platform specific package", &pack)
command.BoolFlag("package", "Create a platform specific package", &pack)
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
compress := false
command.BoolFlag("upx", "Compress final binary with UPX (if installed)", &compress)
compressFlags := ""
command.StringFlag("upxflags", "Flags to pass to upx", &compressFlags)
// Setup Platform flag
platform := runtime.GOOS
command.StringFlag("platform", "Platform to target", &platform)
// Quiet Build
quiet := false
command.BoolFlag("q", "Supress output to console", &quiet)
// Verbosity
verbosity := 1
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
// ldflags to pass to `go`
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
// Log to file
logFile := ""
command.StringFlag("l", "Log to file", &logFile)
// tags to pass to `go`
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)
// Clean build directory
cleanBuildDirectory := false
command.BoolFlag("clean", "Clean the build directory before building", &cleanBuildDirectory)
command.Action(func() error {
// Create logger
logger := logger.New()
quiet := verbosity == 0
if !quiet {
logger.AddOutput(os.Stdout)
}
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
// Validate output type
if !validTargetTypes.Contains(outputType) {
logger.Fatal(fmt.Sprintf("Output type '%s' is not valid.", outputType))
os.Exit(1)
return fmt.Errorf("output type '%s' is not valid", outputType)
}
if !quiet {
app.PrintBanner()
}
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
logger.Writeln(task)
logger.Writeln(strings.Repeat("-", len(task)))
// Setup mode
mode := build.Debug
if production {
mode = build.Production
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
"darwin/amd64",
"darwin/arm64",
"darwin/universal",
"linux",
//"linux/amd64",
//"linux/arm-7",
"windows",
"windows/amd64",
})
if !validPlatformArch.Contains(platform) {
return fmt.Errorf("platform %s is not supported", platform)
}
if compress && platform == "darwin/universal" {
println("Warning: compress flag unsupported for universal binaries. Ignoring.")
compress = false
}
// Tags
userTags := []string{}
for _, tag := range strings.Split(tags, " ") {
thisTag := strings.TrimSpace(tag)
if thisTag != "" {
userTags = append(userTags, thisTag)
}
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Mode: mode,
Pack: pack,
Platform: platform,
LDFlags: ldflags,
Compiler: compilerCommand,
Logger: logger,
OutputType: outputType,
OutputFile: outputFilename,
CleanBuildDirectory: cleanBuildDirectory,
Mode: mode,
Pack: pack,
LDFlags: ldflags,
Compiler: compilerCommand,
KeepAssets: keepAssets,
Verbosity: verbosity,
Compress: compress,
CompressFlags: compressFlags,
UserTags: userTags,
}
// Calculate platform and arch
platformSplit := strings.Split(platform, "/")
buildOptions.Platform = platformSplit[0]
buildOptions.Arch = runtime.GOARCH
if len(platformSplit) == 2 {
buildOptions.Arch = platformSplit[1]
}
// 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", buildOptions.Compiler)
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 {
fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
}
fmt.Fprintf(w, "\n")
w.Flush()
return doBuild(buildOptions)
})
}
@@ -107,11 +189,12 @@ func doBuild(buildOptions *build.Options) error {
if err != nil {
return err
}
// Output stats
elapsed := time.Since(start)
buildOptions.Logger.Writeln("")
buildOptions.Logger.Writeln(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Writeln("")
buildOptions.Logger.Println("")
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Println("")
return nil
}

View File

@@ -0,0 +1,22 @@
# Dev
The dev command allows you to develop your application through a standard browser.
## Usage
`wails dev <flags>`
### Flags
| Flag | Details | Default |
| :------------- | :----------- | :------ |
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
| -ldflags "custom ld flags" | Use given ldflags | |
| -e list,of,extensions | File extensions to trigger rebuilds | go |
| -w | Show warnings | false |
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
| -loglevel | Loglevel to pass to the application - Trace, Debug, Info, Warning, Error | Debug |
## How it works
The project is built using a special mode that starts a webserver and starts listening to port 34115. When the frontend project is run independently, so long as the JS is wrapped with the runtime method `ready`, then the frontend will connect to the backend code via websockets. The interface should be present in your browser, and you should be able to interact with the backend as you would in a desktop app.

View File

@@ -2,35 +2,46 @@ package dev
import (
"fmt"
"io"
"log"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
func LogGreen(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.Green(text))
}
func LogRed(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.Red(text))
}
func LogDarkYellow(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.DarkYellow(text))
}
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli) error {
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("dev", "Development mode")
outputType := "desktop"
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
// Setup target type flag
description := "Type of application to develop. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
// Passthrough ldflags
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
@@ -41,18 +52,23 @@ func AddSubcommand(app *clir.Cli) error {
// extensions to trigger rebuilds
extensions := "go"
command.StringFlag("m", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
// extensions to trigger rebuilds
showWarnings := false
command.BoolFlag("w", "Show warnings", &showWarnings)
// Verbosity
verbosity := 1
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
loglevel := ""
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
command.Action(func() error {
// Validate inputs
if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType)
}
// Create logger
logger := logger.New()
logger.AddOutput(os.Stdout)
logger := clilogger.New(w)
app.PrintBanner()
// TODO: Check you are in a project directory
@@ -61,10 +77,14 @@ func AddSubcommand(app *clir.Cli) error {
if err != nil {
return err
}
defer watcher.Close()
defer func(watcher *fsnotify.Watcher) {
err := watcher.Close()
if err != nil {
log.Fatal(err)
}
}(watcher)
var debugBinaryProcess *process.Process = nil
var buildFrontend bool = true
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
// Setup signal handler
@@ -73,20 +93,35 @@ func AddSubcommand(app *clir.Cli) error {
debounceQuit := make(chan bool, 1)
// Do initial build
logger.Info("Building application for development...")
debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
var passthruArgs []string
//if len(os.Args) > 2 {
// passthruArgs = os.Args[2:]
//}
// Do initial build
logger.Println("Building application for development...")
newProcess, appBinary, err := restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
if newProcess != nil {
debugBinaryProcess = newProcess
}
if err != nil {
return err
}
var newBinaryProcess *process.Process
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Info("event: %+v", 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") {
watcher.Add(event.Name)
logger.Info("Watching directory: %s", event.Name)
err := watcher.Add(event.Name)
if err != nil {
logger.Fatal("%s", err.Error())
}
LogGreen("[New Directory] Watching new directory: %s", event.Name)
}
}
return
@@ -95,38 +130,33 @@ func AddSubcommand(app *clir.Cli) error {
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
// logger.Info("modified file: %s", event.Name)
var rebuild bool = false
// Iterate all file patterns
for _, pattern := range extensionsThatTriggerARebuild {
rebuild = strings.HasSuffix(event.Name, pattern)
if err != nil {
logger.Fatal(err.Error())
}
if rebuild {
// Only build frontend when the file isn't a Go file
buildFrontend = !strings.HasSuffix(event.Name, "go")
if strings.HasSuffix(event.Name, pattern) {
rebuild = true
break
}
}
if !rebuild {
logger.Info("Filename change: %s did not match extension list %s", event.Name, extensions)
if showWarnings {
LogDarkYellow("[File change] %s did not match extension list (%s)", event.Name, extensions)
}
return
}
if buildFrontend {
logger.Info("Full rebuild triggered: %s updated", event.Name)
} else {
logger.Info("Partial build triggered: %s updated", event.Name)
}
LogGreen("[Attempting rebuild] %s updated", event.Name)
// Do a rebuild
// Try and build the app
newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
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
@@ -136,23 +166,28 @@ func AddSubcommand(app *clir.Cli) error {
})
// Get project dir
dir, err := os.Getwd()
projectDir, err := os.Getwd()
if err != nil {
return err
}
// Get all subdirectories
dirs, err := fs.GetSubdirectories(dir)
dirs, err := fs.GetSubdirectories(projectDir)
if err != nil {
return err
}
LogGreen("Watching (sub)/directory: %s", projectDir)
// Setup a watcher for non-node_modules directories
dirs.Each(func(dir string) {
if strings.Contains(dir, "node_modules") {
return
}
logger.Info("Watching directory: %s", dir)
// Ignore build directory
if strings.HasPrefix(dir, filepath.Join(projectDir, "build")) {
return
}
err = watcher.Add(dir)
if err != nil {
logger.Fatal(err.Error())
@@ -164,7 +199,7 @@ func AddSubcommand(app *clir.Cli) error {
for quit == false {
select {
case <-quitChannel:
println()
LogGreen("\nCaught quit")
// Notify debouncer to quit
debounceQuit <- true
quit = true
@@ -173,10 +208,19 @@ func AddSubcommand(app *clir.Cli) error {
// Kill the current program if running
if debugBinaryProcess != nil {
debugBinaryProcess.Kill()
err := debugBinaryProcess.Kill()
if err != nil {
return err
}
}
logger.Info("Development mode exited")
// Remove dev binary
err = os.Remove(appBinary)
if err != nil {
return err
}
LogGreen("Development mode exited")
return nil
})
@@ -203,15 +247,15 @@ exit:
}
}
func restartApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
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, outputType, ldflags, compilerCommand, buildFrontend)
appBinary, err := buildApp(logger, ldflags, compilerCommand, verbosity)
println()
if err != nil {
logger.Error("Build Failed: %s", err.Error())
return nil
LogRed("Build error - continuing to run current version")
LogDarkYellow(err.Error())
return nil, "", nil
}
logger.Info("Build new binary: %s", appBinary)
// Kill existing binary if need be
if debugBinaryProcess != nil {
@@ -226,34 +270,45 @@ func restartApp(logger *logger.Logger, outputType string, ldflags string, compil
// TODO: Generate `backend.js`
// Start up new binary
newProcess := process.NewProcess(logger, appBinary)
// Start up new binary with correct args
var args = []string{"-loglevel", loglevel}
if len(passthruArgs) > 0 {
args = append(args, passthruArgs...)
}
newProcess := process.NewProcess(logger, appBinary, args...)
err = newProcess.Start()
if err != nil {
// Remove binary
fs.DeleteFile(appBinary)
deleteError := fs.DeleteFile(appBinary)
if deleteError != nil {
logger.Fatal("Unable to delete app binary: " + appBinary)
}
logger.Fatal("Unable to start application: %s", err.Error())
}
return newProcess
return newProcess, appBinary, nil
}
func buildApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
func buildApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, verbosity int) (string, error) {
// Create random output file
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
outputFile := "wailsdev"
if runtime.GOOS == "windows" {
outputFile += ".exe"
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
OutputType: "dev",
Mode: build.Debug,
Pack: false,
Platform: runtime.GOOS,
LDFlags: ldflags,
Compiler: compilerCommand,
OutputFile: outputFile,
IgnoreFrontend: !buildFrontend,
IgnoreFrontend: true,
Verbosity: verbosity,
}
return build.Build(buildOptions)

View File

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

View File

@@ -9,7 +9,7 @@ import (
"github.com/wailsapp/wails/v2/pkg/parser"
)
// AddSubcommand adds the `dev` command for the Wails application
// AddSubcommand adds the `generate` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("generate", "Code Generation Tools")
@@ -19,7 +19,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Quiet Init
quiet := false
backendAPI.BoolFlag("q", "Supress output to console", &quiet)
backendAPI.BoolFlag("q", "Suppress output to console", &quiet)
backendAPI.Action(func() error {
@@ -85,7 +85,4 @@ func logPackage(pkg *parser.Package, logger *clilogger.CLILogger) {
}
}
logger.Println("")
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
}

View File

@@ -2,17 +2,21 @@ package initialise
import (
"fmt"
"os"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"io"
"strings"
"time"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise/templates"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/templates"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/git"
)
// AddSubcommand adds the `init` command for the Wails application
func AddSubcommand(app *clir.Cli) error {
func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Load the template shortnames
validShortNames, err := templates.TemplateShortNames()
@@ -32,12 +36,23 @@ func AddSubcommand(app *clir.Cli) error {
command.StringFlag("n", "Name of project", &projectName)
// Setup project directory
projectDirectory := "."
projectDirectory := ""
command.StringFlag("d", "Project directory", &projectDirectory)
// Quiet Init
quiet := false
command.BoolFlag("q", "Supress output to console", &quiet)
command.BoolFlag("q", "Suppress output to console", &quiet)
initGit := false
gitInstalled := git.IsInstalled()
if gitInstalled {
// Git Init
command.BoolFlag("g", "Initialise git repository", &initGit)
}
// VSCode project files
vscode := false
command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
// List templates
list := false
@@ -46,32 +61,29 @@ func AddSubcommand(app *clir.Cli) error {
command.Action(func() error {
// Create logger
logger := logger.New()
if !quiet {
logger.AddOutput(os.Stdout)
}
logger := clilogger.New(w)
logger.Mute(quiet)
// Are we listing templates?
if list {
app.PrintBanner()
err := templates.OutputList(logger)
logger.Writeln("")
logger.Println("")
return err
}
// Validate output type
if !validShortNames.Contains(templateName) {
logger.Write(fmt.Sprintf("ERROR: Template '%s' is not valid", templateName))
logger.Writeln("")
logger.Print(fmt.Sprintf("[ERROR] Template '%s' is not valid", templateName))
logger.Println("")
command.PrintHelp()
return nil
}
// Validate name
if len(projectName) == 0 {
logger.Writeln("ERROR: Project name required")
logger.Writeln("")
logger.Println("ERROR: Project name required")
logger.Println("")
command.PrintHelp()
return nil
}
@@ -81,17 +93,22 @@ func AddSubcommand(app *clir.Cli) error {
}
task := fmt.Sprintf("Initialising Project %s", strings.Title(projectName))
logger.Writeln(task)
logger.Writeln(strings.Repeat("-", len(task)))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
// Create Template Options
options := &templates.Options{
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
GenerateVSCode: vscode,
InitGit: initGit,
}
// Try to discover author details from git config
findAuthorDetails(options)
return initProject(options)
})
@@ -110,11 +127,59 @@ func initProject(options *templates.Options) error {
return err
}
// Install the default assets
err = buildassets.Install(options.TargetDir, options.ProjectName)
if err != nil {
return err
}
if options.InitGit {
err = initGit(options)
if err != nil {
return err
}
}
// Output stats
elapsed := time.Since(start)
options.Logger.Writeln("")
options.Logger.Writeln(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Writeln("")
options.Logger.Println("")
options.Logger.Println("Project Name: " + options.ProjectName)
options.Logger.Println("Project Directory: " + options.TargetDir)
options.Logger.Println("Project Template: " + options.TemplateName)
if options.GenerateVSCode {
options.Logger.Println("VSCode config files generated.")
}
if options.InitGit {
options.Logger.Println("Git repository initialised.")
}
options.Logger.Println("")
options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Println("")
return nil
}
func initGit(options *templates.Options) error {
err := git.InitRepo(options.TargetDir)
if err != nil {
return errors.Wrap(err, "Unable to initialise git repository:")
}
return nil
}
// findAuthorDetails tries to find the user's name and email
// from gitconfig. If it finds them, it stores them in the project options
func findAuthorDetails(options *templates.Options) {
if git.IsInstalled() {
name, err := git.Name()
if err == nil {
options.AuthorName = strings.TrimSpace(name)
}
email, err := git.Email()
if err == nil {
options.AuthorEmail = strings.TrimSpace(email)
}
}
}

View File

@@ -1,41 +1,58 @@
package templates
import (
"embed"
"encoding/json"
"fmt"
"io/ioutil"
gofs "io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/leaanthony/debme"
"github.com/leaanthony/gosod"
"github.com/leaanthony/slicer"
"github.com/olekukonko/tablewriter"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
//go:embed templates
var templates embed.FS
//go:embed ides/*
var ides embed.FS
// Cahce for the templates
// We use this because we need different views of the same data
var templateCache []Template = nil
// Data contains the data we wish to embed during template installation
type Data struct {
ProjectName string
BinaryName string
WailsVersion string
NPMProjectName string
Author string
WailsDirectory string
ProjectName string
BinaryName string
WailsVersion string
NPMProjectName string
AuthorName string
AuthorEmail string
AuthorNameAndEmail string
WailsDirectory string
}
// Options for installing a template
type Options struct {
ProjectName string
TemplateName string
BinaryName string
TargetDir string
Logger *logger.Logger
ProjectName string
TemplateName string
BinaryName string
TargetDir string
Logger *clilogger.CLILogger
GenerateVSCode bool
PathToDesktopBinary string
PathToServerBinary string
InitGit bool
AuthorName string
AuthorEmail string
}
// Template holds data relating to a template
@@ -50,20 +67,21 @@ type Template struct {
HelpURL string `json:"helpurl"`
// Other data
Directory string `json:"-"`
FS gofs.FS `json:"-"`
}
func parseTemplate(directory string) (Template, error) {
templateJSON := filepath.Join(directory, "template.json")
func parseTemplate(template gofs.FS) (Template, error) {
var result Template
data, err := ioutil.ReadFile(templateJSON)
data, err := gofs.ReadFile(template, "template.json")
if err != nil {
return result, err
}
result.Directory = directory
err = json.Unmarshal(data, &result)
return result, err
if err != nil {
return result, err
}
result.FS = template
return result, nil
}
// TemplateShortNames returns a slicer of short template names
@@ -125,11 +143,13 @@ func getTemplateByShortname(shortname string) (Template, error) {
// Loads the template cache
func loadTemplateCache() error {
// Get local template directory
templateDir := fs.RelativePath("templates")
templatesFS, err := debme.FS(templates, "templates")
if err != nil {
return err
}
// Get directories
files, err := ioutil.ReadDir(templateDir)
files, err := templatesFS.ReadDir(".")
if err != nil {
return err
}
@@ -139,8 +159,11 @@ func loadTemplateCache() error {
for _, file := range files {
if file.IsDir() {
templateDir := filepath.Join(templateDir, file.Name())
template, err := parseTemplate(templateDir)
templateFS, err := templatesFS.FS(file.Name())
if err != nil {
return err
}
template, err := parseTemplate(templateFS)
if err != nil {
// Cannot parse this template, continue
continue
@@ -154,7 +177,6 @@ func loadTemplateCache() error {
// Install the given template
func Install(options *Options) error {
// Get cwd
cwd, err := os.Getwd()
if err != nil {
@@ -162,9 +184,24 @@ func Install(options *Options) error {
}
// Did the user want to install in current directory?
if options.TargetDir == "." {
// Yes - use cwd
options.TargetDir = cwd
if options.TargetDir == "" {
// If the current directory is empty, use it
isEmpty, err := fs.DirIsEmpty(cwd)
if err != nil {
return err
}
if isEmpty {
// Yes - use cwd
options.TargetDir = cwd
} else {
options.TargetDir = filepath.Join(cwd, options.ProjectName)
if fs.DirExists(options.TargetDir) {
return fmt.Errorf("cannot create project directory. Dir exists: %s", options.TargetDir)
}
}
} else {
// Get the absolute path of the given directory
targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir))
@@ -187,61 +224,108 @@ func Install(options *Options) error {
}
// Use Gosod to install the template
installer, err := gosod.TemplateDir(template.Directory)
if err != nil {
return err
}
installer := gosod.New(template.FS)
// Ignore template.json files
installer.IgnoreFilename("template.json")
installer.IgnoreFile("template.json")
// Setup the data.
// We use the directory name for the binary name, like Go
BinaryName := filepath.Base(options.TargetDir)
NPMProjectName := strings.ToLower(strings.ReplaceAll(BinaryName, " ", ""))
localWailsDirectory := fs.RelativePath("../..")
localWailsDirectory := fs.RelativePath("../../../../../..")
templateData := &Data{
ProjectName: options.ProjectName,
BinaryName: filepath.Base(options.TargetDir),
NPMProjectName: NPMProjectName,
WailsDirectory: localWailsDirectory,
AuthorEmail: options.AuthorEmail,
AuthorName: options.AuthorName,
}
// Create a formatted name and email combo.
if options.AuthorName != "" {
templateData.AuthorNameAndEmail = options.AuthorName + " "
}
if options.AuthorEmail != "" {
templateData.AuthorNameAndEmail += "<" + options.AuthorEmail + ">"
}
templateData.AuthorNameAndEmail = strings.TrimSpace(templateData.AuthorNameAndEmail)
// Extract the template
err = installer.Extract(options.TargetDir, templateData)
if err != nil {
return err
}
// Calculate the directory name
err = generateIDEFiles(options)
if err != nil {
return err
}
return nil
}
// OutputList prints the list of available tempaltes to the given logger
func OutputList(logger *logger.Logger) error {
func OutputList(logger *clilogger.CLILogger) error {
templates, err := List()
if err != nil {
return err
}
for _, writer := range logger.Writers() {
table := tablewriter.NewWriter(writer)
table.SetHeader([]string{"Template", "Short Name", "Description"})
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding("\t") // pad with tabs
table.SetNoWhiteSpace(true)
for _, template := range templates {
table.Append([]string{template.Name, template.ShortName, template.Description})
}
table.Render()
table := tablewriter.NewWriter(logger.Writer)
table.SetHeader([]string{"Template", "Short Name", "Description"})
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding("\t") // pad with tabs
table.SetNoWhiteSpace(true)
for _, template := range templates {
table.Append([]string{template.Name, template.ShortName, template.Description})
}
table.Render()
return nil
}
func generateIDEFiles(options *Options) error {
if options.GenerateVSCode {
return generateVSCodeFiles(options)
}
return nil
}
func generateVSCodeFiles(options *Options) error {
targetDir := filepath.Join(options.TargetDir, ".vscode")
source, err := debme.FS(ides, "ides/vscode")
if err != nil {
return err
}
// Use gosod to install the template
installer := gosod.New(source)
binaryName := filepath.Base(options.TargetDir)
if runtime.GOOS == "windows" {
// yay windows
binaryName += ".exe"
}
options.PathToDesktopBinary = filepath.Join("build", runtime.GOOS, "desktop", binaryName)
options.PathToServerBinary = filepath.Join("build", runtime.GOOS, "server", binaryName)
err = installer.Extract(targetDir, options)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,16 @@
# README
## 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.
## 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.

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
wails "github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2"
)
// Basic application struct
@@ -11,21 +11,20 @@ type Basic struct {
runtime *wails.Runtime
}
// newBasic creates a new Basic application struct
func newBasic() *Basic {
// NewBasic creates a new Basic application struct
func NewBasic() *Basic {
return &Basic{}
}
// WailsInit is called at application startup
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
// startup is called at application startup
func (b *Basic) startup(runtime *wails.Runtime) {
// Perform your setup here
b.runtime = runtime
runtime.Window.SetTitle("{{.ProjectName}}")
return nil
}
// WailsShutdown is called at application termination
func (b *Basic) WailsShutdown() {
// shutdown is called at application termination
func (b *Basic) shutdown() {
// Perform your teardown here
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
{
"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.19",
"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

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

@@ -4,7 +4,7 @@
<link rel="stylesheet" href="/main.css">
</head>
<body>
<body data-wails-drag>
<div id="logo"></div>
<div id="input">
<input id="name" type="text"></input>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,21 @@
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 Basic.Greet(name)
window.backend.main.Basic.Greet(name).then((result) => {
// Update result with data back from Basic.Greet()
document.getElementById("result").innerText = result;
});
};
});

View File

@@ -1,6 +1,6 @@
module test
go 1.13
go 1.16
require (
github.com/wailsapp/wails/v2 v2.0.0-alpha

View File

@@ -0,0 +1,39 @@
package main
import (
"log"
"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 := NewBasic()
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 800,
Height: 600,
DisableResize: 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

@@ -2,8 +2,10 @@
"name": "{{.ProjectName}}",
"outputfilename": "{{.BinaryName}}",
"html": "frontend/dist/index.html",
"js": "frontend/dist/app.js",
"css": "frontend/dist/app.css",
"frontend:build": "npm run build",
"frontend:install": "npm install"
}
"frontend:install": "npm ci",
"author": {
"name": "{{.AuthorName}}",
"email": "{{.AuthorEmail}}"
}
}

View File

@@ -0,0 +1,46 @@
package templates
import (
"fmt"
"testing"
"github.com/matryer/is"
)
func TestList(t *testing.T) {
is2 := is.New(t)
templates, err := List()
is2.NoErr(err)
println("Found these templates:")
for _, template := range templates {
fmt.Printf("%+v\n", template)
}
}
func TestShortname(t *testing.T) {
is2 := is.New(t)
template, err := getTemplateByShortname("vanilla")
is2.NoErr(err)
println("Found this template:")
fmt.Printf("%+v\n", template)
}
func TestInstall(t *testing.T) {
is2 := is.New(t)
options := &Options{
ProjectName: "test",
TemplateName: "vanilla",
AuthorName: "Lea Anthony",
AuthorEmail: "lea.anthony@gmail.com",
}
err := Install(options)
is2.NoErr(err)
}

View File

@@ -1,11 +1,19 @@
package main
import (
"fmt"
"os"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
)
@@ -14,19 +22,44 @@ func fatal(message string) {
os.Exit(1)
}
func banner(_ *clir.Cli) string {
return fmt.Sprintf("%s %s", colour.Yellow("Wails CLI"), colour.DarkRed(version))
}
func main() {
var err error
version := "v2.0.0-alpha"
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
app := clir.NewCli("Wails", "Go/HTML Appkit", version)
build.AddBuildSubcommand(app)
err = initialise.AddSubcommand(app)
app.SetBannerFunction(banner)
build.AddBuildSubcommand(app, os.Stdout)
err = initialise.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = doctor.AddSubcommand(app)
err = debug.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = doctor.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = dev.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = generate.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = update.AddSubcommand(app, os.Stdout, version)
if err != nil {
fatal(err.Error())
}
@@ -34,5 +67,6 @@ func main() {
err = app.Run()
if err != nil {
println("\n\nERROR: " + err.Error())
os.Exit(1)
}
}

View File

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

View File

@@ -1,14 +1,31 @@
module github.com/wailsapp/wails/v2
go 1.13
go 1.16
require (
github.com/leaanthony/clir v1.0.2
github.com/leaanthony/gosod v0.0.3
github.com/leaanthony/slicer v1.4.1
github.com/matryer/is v1.3.0
github.com/Masterminds/semver v1.5.0
github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gorilla/websocket v1.4.1
github.com/imdario/mergo v0.3.11
github.com/jackmordaunt/icns v1.0.0
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/debme v1.2.0
github.com/leaanthony/go-ansi-parser v1.0.1
github.com/leaanthony/gosod v1.0.1
github.com/leaanthony/slicer v1.5.0
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0
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/errors v0.9.1
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.6 // indirect
github.com/wzshiming/ctc v1.2.3
github.com/xyproto/xpm v1.2.1
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
nhooyr.io/websocket v1.7.4
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
nhooyr.io/websocket v1.8.6
)

142
v2/go.sum
View File

@@ -1,57 +1,133 @@
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leaanthony/clir v1.0.2 h1:Ham9Ep7NH95il0x/ci5s2ExJa/K4B2dE8L2uaSj2cxo=
github.com/leaanthony/clir v1.0.2/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/gosod v0.0.3 h1:VlPilye0zoH4I0WihShyoiTHyiAN1v6dcBW4mMvGJao=
github.com/leaanthony/gosod v0.0.3/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/slicer v1.4.1 h1:X/SmRIDhkUAolP79mSTO0jTcVX1k504PJBqvV6TwP0w=
github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/matryer/is v1.3.0 h1:9qiso3jaJrOe6qBRJRBt2Ldht05qDiFP9le0JOIhRSI=
github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/debme v1.1.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/debme v1.2.0 h1:i7JUQhuv7PtJ/7qV+SIa5QARTGBcojUZ+2eCiV9begU=
github.com/leaanthony/debme v1.2.0/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
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/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
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/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
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/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/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=
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82 h1:shxDsb9Dz27xzk3A0DxP0JuJnZMpKrdg8+E14eiUAX4=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
nhooyr.io/websocket v1.7.4 h1:w/LGB2sZT0RV8lZYR7nfyaYz4PUbYZ5oF7NBon2M0NY=
nhooyr.io/websocket v1.7.4/go.mod h1:PxYxCwFdFYQ0yRvtQz3s/dC+VEm7CSuC/4b9t8MQQxw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=

7
v2/init.go Normal file
View File

@@ -0,0 +1,7 @@
// +build !windows
package wails
func Init() error {
return nil
}

14
v2/init_windows.go Normal file
View File

@@ -0,0 +1,14 @@
package wails
import (
"fmt"
"syscall"
)
func Init() error {
status, r, err := syscall.NewLazyDLL("user32.dll").NewProc("SetProcessDPIAware").Call()
if status == 0 {
return fmt.Errorf("exit status %d: %v %v", status, r, err)
}
return nil
}

View File

@@ -1,4 +1,4 @@
// +build !desktop,!hybrid,!server
// +build !desktop,!hybrid,!server,!dev
package app
@@ -10,7 +10,9 @@ package app
import (
"os"
"github.com/wailsapp/wails/v2/internal/features"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
// App defines a Wails application structure
@@ -20,12 +22,15 @@ type App struct {
Height int
Resizable bool
Features *features.Features
// Indicates if the app is running in debug mode
debug bool
logger *logger.Logger
}
// CreateApp returns a null application
func CreateApp(options *Options) *App {
return &App{}
func CreateApp(_ *options.App) (*App, error) {
return &App{}, nil
}
// Run the application
@@ -34,8 +39,3 @@ func (a *App) Run() error {
os.Exit(1)
return nil
}
// Bind the dummy interface
func (a *App) Bind(dummy interface{}) error {
return nil
}

View File

@@ -3,117 +3,152 @@
package app
import (
"os"
"context"
"sync"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/features"
"github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/signal"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/pkg/options"
)
// App defines a Wails application structure
type App struct {
appType string
window *ffenestri.Application
servicebus *servicebus.ServiceBus
logger *logger.Logger
signal *signal.Manager
options *options.App
// Subsystems
log *subsystem.Log
runtime *subsystem.Runtime
event *subsystem.Event
binding *subsystem.Binding
log *subsystem.Log
runtime *subsystem.Runtime
event *subsystem.Event
//binding *subsystem.Binding
call *subsystem.Call
menu *subsystem.Menu
url *subsystem.URL
dispatcher *messagedispatcher.Dispatcher
menuManager *menumanager.Manager
// Indicates if the app is in debug mode
debug bool
// This is our binding DB
bindings *binding.Bindings
// Feature flags
Features *features.Features
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
}
// Create App
func CreateApp(options *Options) *App {
func CreateApp(appoptions *options.App) (*App, error) {
// Merge default options
options.mergeDefaults()
options.MergeDefaults(appoptions)
// Set up logger
myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.TRACE)
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
Title: options.Title,
Width: options.Width,
Height: options.Height,
MinWidth: options.MinWidth,
MinHeight: options.MinHeight,
MaxWidth: options.MaxWidth,
MaxHeight: options.MaxHeight,
Frameless: options.Frameless,
// Create the menu manager
menuManager := menumanager.NewManager()
// This should be controlled by the compile time flags...
DevTools: true,
// Process the application menu
menuManager.SetApplicationMenu(options.GetApplicationMenu(appoptions))
Resizable: !options.DisableResize,
Fullscreen: options.Fullscreen,
}, myLogger)
result := &App{
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
Features: features.New(),
// Process context menus
contextMenus := options.GetContextMenus(appoptions)
for _, contextMenu := range contextMenus {
menuManager.AddContextMenu(contextMenu)
}
return result
// Process tray menus
trayMenus := options.GetTrayMenus(appoptions)
for _, trayMenu := range trayMenus {
menuManager.AddTrayMenu(trayMenu)
}
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
result := &App{
appType: "desktop",
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
menuManager: menuManager,
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
}
result.options = appoptions
// Initialise the app
err := result.Init()
return result, err
}
// Run the application
func (a *App) Run() error {
// Setup signal handler
signal, err := signal.NewManager(a.servicebus, a.logger)
if err != nil {
return err
}
a.signal = signal
a.signal.Start()
var err error
// Setup a context
var subsystemWaitGroup sync.WaitGroup
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// Start the service bus
a.servicebus.Debug()
a.servicebus.Start()
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
err = a.servicebus.Start()
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime())
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
if err != nil {
return err
}
a.binding = binding
a.binding.Start()
a.runtime = runtimesubsystem
err = a.runtime.Start()
if err != nil {
return err
}
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
// Start the logging subsystem
log, err := subsystem.NewLog(a.servicebus, a.logger)
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
return err
}
a.log = log
a.log.Start()
err = a.log.Start()
if err != nil {
return err
}
// create the dispatcher
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
@@ -121,23 +156,56 @@ func (a *App) Run() error {
return err
}
a.dispatcher = dispatcher
dispatcher.Start()
err = dispatcher.Start()
if err != nil {
return err
}
if a.options.Mac.URLHandlers != nil {
// Start the url handler subsystem
url, err := subsystem.NewURL(a.servicebus, a.logger, a.options.Mac.URLHandlers)
if err != nil {
return err
}
a.url = url
err = a.url.Start()
if err != nil {
return err
}
}
// Start the eventing subsystem
event, err := subsystem.NewEvent(a.servicebus, a.logger)
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil {
return err
}
a.event = eventsubsystem
err = a.event.Start()
if err != nil {
return err
}
// Start the menu subsystem
menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
if err != nil {
return err
}
a.menu = menusubsystem
err = a.menu.Start()
if err != nil {
return err
}
a.event = event
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
a.call = callSubsystem
err = a.call.Start()
if err != nil {
return err
}
a.call = call
a.call.Start()
// Dump bindings as a debug
bindingDump, err := a.bindings.ToJSON()
@@ -145,20 +213,42 @@ func (a *App) Run() error {
return err
}
result := a.window.Run(dispatcher, bindingDump, a.Features)
a.logger.Trace("Ffenestri.Run() exited")
a.servicebus.Stop()
return result
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
return err
}
a.signal = signalsubsystem
a.signal.Start()
err = a.window.Run(dispatcher, bindingDump, a.debug)
a.logger.Trace("Ffenestri.Run() exited")
if err != nil {
return err
}
// Close down all the subsystems
a.logger.Trace("Cancelling subsystems")
cancel()
subsystemWaitGroup.Wait()
a.logger.Trace("Cancelling dispatcher")
dispatcher.Close()
// Close log
a.logger.Trace("Stopping log")
log.Close()
a.logger.Trace("Stopping Service bus")
err = a.servicebus.Stop()
if err != nil {
return err
}
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/features"
"github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
@@ -43,8 +42,7 @@ type App struct {
dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus
// Feature flags
Features *features.Features
debug bool
}
// Create App
@@ -58,17 +56,15 @@ func CreateApp(options *Options) *App {
myLogger.SetLogLevel(logger.INFO)
window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
Title: options.Title,
Width: options.Width,
Height: options.Height,
MinWidth: options.MinWidth,
MinHeight: options.MinHeight,
MaxWidth: options.MaxWidth,
MaxHeight: options.MaxHeight,
Frameless: options.Frameless,
// This should be controlled by the compile time flags...
DevTools: true,
Title: options.Title,
Width: options.Width,
Height: options.Height,
MinWidth: options.MinWidth,
MinHeight: options.MinHeight,
MaxWidth: options.MaxWidth,
MaxHeight: options.MaxHeight,
StartHidden: options.StartHidden,
DevTools: options.DevTools,
Resizable: !options.DisableResize,
Fullscreen: options.Fullscreen,
@@ -79,9 +75,12 @@ func CreateApp(options *Options) *App {
webserver: webserver.NewWebServer(myLogger),
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
bindings: binding.NewBindings(myLogger, options.Bind),
}
// Initialise the app
app.Init()
return app
}
@@ -185,7 +184,7 @@ func (a *App) Run() error {
}
}()
result := a.window.Run(dispatcher, bindingDump, a.Features)
result := a.window.Run(dispatcher, bindingDump)
a.servicebus.Stop()
return result
@@ -193,14 +192,3 @@ func (a *App) Run() error {
return cli.Run()
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

View File

@@ -1,34 +0,0 @@
package app
// Options for creating the App
type Options struct {
Title string
Width int
Height int
DisableResize bool
Fullscreen bool
Frameless bool
MinWidth int
MinHeight int
MaxWidth int
MaxHeight int
}
// mergeDefaults will set the minimum default values for an application
func (o *Options) mergeDefaults() {
// Create a default title
if len(o.Title) == 0 {
o.Title = "My Wails App"
}
// Default width
if o.Width == 0 {
o.Width = 1024
}
// Default height
if o.Height == 0 {
o.Height = 768
}
}

View File

@@ -6,10 +6,13 @@ import (
"os"
"path/filepath"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/internal/webserver"
@@ -17,36 +20,57 @@ import (
// App defines a Wails application structure
type App struct {
appType string
binding *subsystem.Binding
call *subsystem.Call
event *subsystem.Event
log *subsystem.Log
runtime *subsystem.Runtime
options *options.App
bindings *binding.Bindings
logger *logger.Logger
dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus
webserver *webserver.WebServer
debug bool
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
}
// Create App
func CreateApp(options *Options) *App {
options.mergeDefaults()
// We ignore the inputs (for now)
func CreateApp(appoptions *options.App) (*App, error) {
// TODO: Allow logger output override on CLI
myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.TRACE)
// Merge default options
options.MergeDefaults(appoptions)
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
result := &App{
bindings: binding.NewBindings(myLogger),
logger: myLogger,
servicebus: servicebus.New(myLogger),
webserver: webserver.NewWebServer(myLogger),
appType: "server",
bindings: binding.NewBindings(myLogger, options.Bind),
logger: myLogger,
servicebus: servicebus.New(myLogger),
webserver: webserver.NewWebServer(myLogger),
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
}
return result
// Initialise app
result.Init()
return result, nil
}
// Run the application
@@ -55,7 +79,7 @@ func (a *App) Run() error {
// Default app options
var port = 8080
var ip = "localhost"
var supressLogging = false
var SuppressLogging = false
var debugMode = false
// Create CLI
@@ -65,7 +89,7 @@ func (a *App) Run() error {
cli.IntFlag("p", "Port to serve on", &port)
cli.StringFlag("i", "IP to serve on", &ip)
cli.BoolFlag("d", "Debug mode", &debugMode)
cli.BoolFlag("q", "Supress logging", &supressLogging)
cli.BoolFlag("q", "Suppress logging", &SuppressLogging)
// Setup main action
cli.Action(func() error {
@@ -74,8 +98,8 @@ func (a *App) Run() error {
a.webserver.SetPort(port)
a.webserver.SetIP(ip)
a.webserver.SetBindings(a.bindings)
// Log information (if we aren't supressing it)
if !supressLogging {
// Log information (if we aren't Suppressing it)
if !SuppressLogging {
cli.PrintBanner()
a.logger.Info("Running server at %s", a.webserver.URL())
}
@@ -83,8 +107,21 @@ func (a *App) Run() error {
if debugMode {
a.servicebus.Debug()
}
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
a.servicebus.Start()
log, err := subsystem.NewLog(a.servicebus, a.logger)
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
return err
}
@@ -97,14 +134,6 @@ func (a *App) Run() error {
a.dispatcher = dispatcher
a.dispatcher.Start()
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
if err != nil {
@@ -122,7 +151,7 @@ func (a *App) Run() error {
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
@@ -142,14 +171,3 @@ func (a *App) Run() error {
return cli.Run()
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

View File

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

View File

@@ -1,9 +1,9 @@
package binding
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// BoundMethod defines all the data related to a Go method that is
@@ -16,58 +16,6 @@ type BoundMethod struct {
Method reflect.Value `json:"-"`
}
// IsWailsInit returns true if the method name is "WailsInit"
func (b *BoundMethod) IsWailsInit() bool {
return strings.HasSuffix(b.Name, "WailsInit")
}
// IsWailsShutdown returns true if the method name is "WailsShutdown"
func (b *BoundMethod) IsWailsShutdown() bool {
return strings.HasSuffix(b.Name, "WailsShutdown")
}
// VerifyWailsInit checks if the WailsInit signature is correct
func (b *BoundMethod) VerifyWailsInit() error {
// Must only have 1 input
if b.InputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check input type
if !b.Inputs[0].IsType("*goruntime.Runtime") {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Must only have 1 output
if b.OutputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check output type
if !b.Outputs[0].IsError() {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Input must be of type Runtime
return nil
}
// VerifyWailsShutdown checks if the WailsShutdown signature is correct
func (b *BoundMethod) VerifyWailsShutdown() error {
// Must have no inputs
if b.InputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Must have no outputs
if b.OutputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Input must be of type Runtime
return nil
}
// InputCount returns the number of inputs this bound method has
func (b *BoundMethod) InputCount() int {
return len(b.Inputs)
@@ -78,6 +26,29 @@ func (b *BoundMethod) OutputCount() int {
return len(b.Outputs)
}
// ParseArgs method converts the input json into the types expected by the method
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
result := make([]interface{}, b.InputCount())
if len(args) != b.InputCount() {
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
}
for index, arg := range args {
typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface()
err := json.Unmarshal(arg, inputValue)
if err != nil {
return nil, err
}
if inputValue == nil {
result[index] = reflect.Zero(typ).Interface()
} else {
result[index] = reflect.ValueOf(inputValue).Elem().Interface()
}
}
return result, nil
}
// Call will attempt to call this bound method with the given args
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Check inputs
@@ -94,17 +65,8 @@ func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Iterate over given arguments
for index, arg := range args {
// Attempt to convert the argument to the type expected by the method
value, err := convertArgToValue(arg, b.Inputs[index])
// If it fails, return a suitable error
if err != nil {
return nil, fmt.Errorf("%s (parameter %d): %s", b.Name, index+1, err.Error())
}
// Save the converted argument
callArgs[index] = value
callArgs[index] = reflect.ValueOf(arg)
}
// Do the call

View File

@@ -15,10 +15,6 @@ type DB struct {
// It used for performance gains at runtime
methodMap map[string]*BoundMethod
// These are slices of methods registered using WailsInit and WailsShutdown
wailsInitMethods []*BoundMethod
wailsShutdownMethods []*BoundMethod
// Lock to ensure sync access to the data
lock sync.RWMutex
}
@@ -94,38 +90,6 @@ func (d *DB) AddMethod(packageName string, structName string, methodName string,
}
// AddWailsInit checks the given method is a WailsInit method and if it
// is, adds it to the list of WailsInit methods
func (d *DB) AddWailsInit(method *BoundMethod) error {
err := method.VerifyWailsInit()
if err != nil {
return err
}
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
d.wailsInitMethods = append(d.wailsInitMethods, method)
return nil
}
// AddWailsShutdown checks the given method is a WailsInit method and if it
// is, adds it to the list of WailsShutdown methods
func (d *DB) AddWailsShutdown(method *BoundMethod) error {
err := method.VerifyWailsShutdown()
if err != nil {
return err
}
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
d.wailsShutdownMethods = append(d.wailsShutdownMethods, method)
return nil
}
// ToJSON converts the method map to JSON
func (d *DB) ToJSON() (string, error) {
@@ -138,13 +102,3 @@ func (d *DB) ToJSON() (string, error) {
// Return zero copy string as this string will be read only
return *(*string)(unsafe.Pointer(&bytes)), err
}
// WailsInitMethods returns the list of registered WailsInit methods
func (d *DB) WailsInitMethods() []*BoundMethod {
return d.wailsInitMethods
}
// WailsShutdownMethods returns the list of registered WailsInit methods
func (d *DB) WailsShutdownMethods() []*BoundMethod {
return d.wailsShutdownMethods
}

View File

@@ -14,30 +14,6 @@ import (
"github.com/leaanthony/slicer"
)
const _comment = `
const backend = {
main: {
"xbarApp": {
"GetCategories": () => {
window.backend.main.xbarApp.GetCategories.call(arguments);
},
/**
* @param {string} arg1
*/
"InstallPlugin": (arg1) => {
window.backend.main.xbarApp.InstallPlugin.call(arguments);
},
"GetPlugins": () => {
window.backend.main.xbarApp.GetPlugins.call(arguments);
}
}
}
}
export default backend;`
//go:embed assets/package.json
var packageJSON []byte
@@ -100,6 +76,8 @@ const backend = {`)
}
returnType += ">"
returnTypeDetails = " - Go Type: " + methodDetails.Outputs[0].TypeName
} else {
returnType = "Promise<void>"
}
output.WriteString(" * @returns {" + returnType + "} " + returnTypeDetails + "\n")
output.WriteString(" */\n")
@@ -125,13 +103,14 @@ const backend = {`)
export default backend;`)
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.Mkdir(dirname)
err := fs.MkDirs(dirname)
if err != nil {
log.Fatal(err)
}
@@ -165,6 +144,8 @@ func goTypeToJSDocType(input string) string {
return "number"
case input == "bool":
return "boolean"
case input == "[]byte":
return "string"
case strings.HasPrefix(input, "[]"):
arrayType := goTypeToJSDocType(input[2:])
return "Array.<" + arrayType + ">"

View File

@@ -0,0 +1,87 @@
package binding
import (
"testing"
)
func Test_goTypeToJSDocType(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{
name: "string",
input: "string",
want: "string",
},
{
name: "error",
input: "error",
want: "Error",
},
{
name: "int",
input: "int",
want: "number",
},
{
name: "int32",
input: "int32",
want: "number",
},
{
name: "uint",
input: "uint",
want: "number",
},
{
name: "uint32",
input: "uint32",
want: "number",
},
{
name: "float32",
input: "float32",
want: "number",
},
{
name: "float64",
input: "float64",
want: "number",
},
{
name: "bool",
input: "bool",
want: "boolean",
},
{
name: "[]byte",
input: "[]byte",
want: "string",
},
{
name: "[]int",
input: "[]int",
want: "Array.<number>",
},
{
name: "[]bool",
input: "[]bool",
want: "Array.<boolean>",
},
{
name: "anything else",
input: "foo",
want: "any",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := goTypeToJSDocType(tt.input); got != tt.want {
t.Errorf("goTypeToJSDocType() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -3,6 +3,7 @@ package binding
import (
"fmt"
"reflect"
"runtime"
)
// isStructPtr returns true if the value given is a
@@ -12,14 +13,35 @@ func isStructPtr(value interface{}) bool {
reflect.ValueOf(value).Elem().Kind() == reflect.Struct
}
func getMethods(value interface{}) ([]*BoundMethod, error) {
// isFunction returns true if the given value is a function
func isFunction(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Func
}
// isStructPtr returns true if the value given is a struct
func isStruct(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Struct
}
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
// Create result placeholder
var result []*BoundMethod
// Check type
if !isStructPtr(value) {
return nil, fmt.Errorf("not a pointer to an interface")
if isStruct(value) {
name := reflect.ValueOf(value).Type().Name()
return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name)
}
if isFunction(value) {
name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name()
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name)
}
return nil, fmt.Errorf("not a pointer to a struct.")
}
// Process Struct
@@ -34,6 +56,11 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
fullMethodName := baseName + "." + methodName
method := structValue.MethodByName(methodName)
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
if b.exemptions.Contains(methodReflectName) {
continue
}
// Create new method
boundMethod := &BoundMethod{
Name: fullMethodName,
@@ -56,6 +83,8 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
boundMethod.Inputs = inputs
// Iterate outputs
// TODO: Determine what to do about limiting return types
// especially around errors.
outputParamCount := methodType.NumOut()
var outputs []*Parameter
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
@@ -71,39 +100,3 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
}
return result, nil
}
// convertArgToValue
func convertArgToValue(input interface{}, target *Parameter) (result reflect.Value, err error) {
// Catch type conversion panics thrown by convert
defer func() {
if r := recover(); r != nil {
// Modify error
err = fmt.Errorf("%s", r.(string)[23:])
}
}()
// Do the conversion
// Handle nil values
if input == nil {
switch target.reflectType.Kind() {
case reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Map,
reflect.Ptr,
reflect.Slice:
result = reflect.ValueOf(input).Convert(target.reflectType)
default:
return reflect.Zero(target.reflectType), fmt.Errorf("Unable to use null value")
}
} else {
result = reflect.ValueOf(input).Convert(target.reflectType)
}
// We don't like doing this but it's the only way to
// handle recover() correctly
return
}

View File

@@ -26,9 +26,8 @@ func (b BridgeClient) Quit() {
}
func (b BridgeClient) NotifyEvent(message string) {
//b.session.sendMessage("n" + message)
b.session.sendMessage("n" + message)
b.session.log.Info("NotifyEvent: %s", message)
b.session.log.Info("NotifyEvent unsupported in Bridge mode")
}
func (b BridgeClient) CallResult(message string) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -18,6 +18,12 @@ import (
//go:embed darwin.js
var darwinRuntime string
//go:embed windows.js
var windowsRuntime string
//go:embed linux.js
var linuxRuntime string
// session represents a single websocket session
type session struct {
bindings string
@@ -84,6 +90,10 @@ func (s *session) start(firstSession bool) {
switch runtime.GOOS {
case "darwin":
wailsRuntime = darwinRuntime
case "windows":
wailsRuntime = windowsRuntime
case "linux":
wailsRuntime = linuxRuntime
default:
log.Fatal("platform not supported")
}
@@ -92,7 +102,10 @@ func (s *session) start(firstSession bool) {
s.log.Info(bindingsMessage)
bootstrapMessage := bindingsMessage + wailsRuntime
s.sendMessage("b" + bootstrapMessage)
err := s.sendMessage("b" + bootstrapMessage)
if err != nil {
s.log.Error(err.Error())
}
// Send menus
traymenus, err := s.menumanager.GetTrayMenus()
@@ -101,7 +114,10 @@ func (s *session) start(firstSession bool) {
}
for _, trayMenu := range traymenus {
s.sendMessage("TS" + trayMenu)
err := s.sendMessage("TS" + trayMenu)
if err != nil {
s.log.Error(err.Error())
}
}
for {
@@ -130,7 +146,10 @@ func (s *session) start(firstSession bool) {
// Shutdown
func (s *session) Shutdown() {
s.conn.Close()
err := s.conn.Close()
if err != nil {
s.log.Error(err.Error())
}
s.done = true
s.log.Info("session %v exit", s.Identifier())
}
@@ -146,10 +165,16 @@ func (s *session) writePump() {
s.Shutdown()
return
case msg, ok := <-s.writeChan:
s.conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
err := s.conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
if err != nil {
s.log.Error(err.Error())
}
if !ok {
s.log.Debug("writeChan was closed!")
s.conn.WriteMessage(websocket.CloseMessage, []byte{})
err := s.conn.WriteMessage(websocket.CloseMessage, []byte{})
if err != nil {
s.log.Error(err.Error())
}
return
}

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +0,0 @@
package features
// Features holds generic and platform specific feature flags
type Features struct {
Linux *Linux
}
// New creates a new Features object
func New() *Features {
return &Features{
Linux: &Linux{},
}
}

View File

@@ -1,5 +0,0 @@
package features
// Linux holds linux specific feature flags
type Linux struct {
}

View File

@@ -1,5 +1,11 @@
# 3rd Party Licenses
## Webview
Whilst not using the library directly, there is certainly some code that is inspired by or used from the webview library.
Homepage: https://github.com/webview/webview
License: https://github.com/webview/webview/blob/master/LICENSE
## vec
Homepage: https://github.com/rxi/vec
License: https://github.com/rxi/vec/blob/master/LICENSE

View File

@@ -1,3 +1,6 @@
// +build !windows
//
// Created by Lea Anthony on 6/1/21.
//

View File

@@ -5,10 +5,6 @@
#ifndef COMMON_H
#define COMMON_H
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
#include <objc/objc-runtime.h>
#include <CoreGraphics/CoreGraphics.h>
#include <stdio.h>
#include <stdarg.h>
#include "string.h"

View File

@@ -82,10 +82,10 @@ void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *context
FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
// Grab the content view and show the menu
id contentView = msg(mainWindow, s("contentView"));
id contentView = msg_reg(mainWindow, s("contentView"));
// Get the triggering event
id menuEvent = msg(mainWindow, s("currentEvent"));
id menuEvent = msg_reg(mainWindow, s("currentEvent"));
if( contextMenu->nsmenu == NULL ) {
// GetMenu creates the NSMenu
@@ -93,7 +93,7 @@ void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *context
}
// Show popup
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
((id(*)(id, SEL, id, id, id))objc_msgSend)(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
}

View File

@@ -0,0 +1,64 @@
// Credit: https://gist.github.com/ysc3839/b08d2bff1c7dacde529bed1d37e85ccf
#pragma once
typedef enum _WINDOWCOMPOSITIONATTRIB
{
WCA_UNDEFINED = 0,
WCA_NCRENDERING_ENABLED = 1,
WCA_NCRENDERING_POLICY = 2,
WCA_TRANSITIONS_FORCEDISABLED = 3,
WCA_ALLOW_NCPAINT = 4,
WCA_CAPTION_BUTTON_BOUNDS = 5,
WCA_NONCLIENT_RTL_LAYOUT = 6,
WCA_FORCE_ICONIC_REPRESENTATION = 7,
WCA_EXTENDED_FRAME_BOUNDS = 8,
WCA_HAS_ICONIC_BITMAP = 9,
WCA_THEME_ATTRIBUTES = 10,
WCA_NCRENDERING_EXILED = 11,
WCA_NCADORNMENTINFO = 12,
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
WCA_VIDEO_OVERLAY_ACTIVE = 14,
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
WCA_DISALLOW_PEEK = 16,
WCA_CLOAK = 17,
WCA_CLOAKED = 18,
WCA_ACCENT_POLICY = 19,
WCA_FREEZE_REPRESENTATION = 20,
WCA_EVER_UNCLOAKED = 21,
WCA_VISUAL_OWNER = 22,
WCA_HOLOGRAPHIC = 23,
WCA_EXCLUDED_FROM_DDA = 24,
WCA_PASSIVEUPDATEMODE = 25,
WCA_USEDARKMODECOLORS = 26,
WCA_LAST = 27
} WINDOWCOMPOSITIONATTRIB;
typedef struct _WINDOWCOMPOSITIONATTRIBDATA
{
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
} WINDOWCOMPOSITIONATTRIBDATA;
typedef enum _ACCENT_STATE
{
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, // RS4 1803
ACCENT_ENABLE_HOSTBACKDROP = 5, // RS5 1809
ACCENT_INVALID_STATE = 6
} ACCENT_STATE;
typedef struct _ACCENT_POLICY
{
ACCENT_STATE AccentState;
DWORD AccentFlags;
DWORD GradientColor;
DWORD AnimationId;
} ACCENT_POLICY;
typedef BOOL (WINAPI *pfnGetWindowCompositionAttribute)(HWND, WINDOWCOMPOSITIONATTRIBDATA*);
typedef BOOL (WINAPI *pfnSetWindowCompositionAttribute)(HWND, WINDOWCOMPOSITIONATTRIBDATA*);

View File

@@ -1,23 +0,0 @@
// +build linux
package ffenestri
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
import "github.com/wailsapp/wails/v2/internal/features"
func (a *Application) processOSFeatureFlags(features *features.Features) {
// Process Linux features
// linux := features.Linux
}

View File

@@ -5,59 +5,38 @@ import (
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/internal/features"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/pkg/options"
)
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -lobjc
#cgo windows CXXFLAGS: -std=c++11
#cgo windows,amd64 LDFLAGS: -L./windows/x64 -lwebview -lWebView2Loader -lgdi32 -lole32 -lShlwapi -luser32 -loleaut32
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
// DEBUG is the global Ffenestri debug flag.
// TODO: move to compile time.
var DEBUG bool = true
// Config defines how our application should be configured
type Config struct {
Title string
Width int
Height int
MinWidth int
MinHeight int
MaxWidth int
MaxHeight int
DevTools bool
Resizable bool
Fullscreen bool
Frameless bool
}
var defaultConfig = &Config{
Title: "My Wails App",
Width: 800,
Height: 600,
DevTools: true,
Resizable: true,
Fullscreen: false,
Frameless: false,
}
// Application is our main application object
type Application struct {
config *Config
config *options.App
memory []unsafe.Pointer
// This is the main app pointer
app unsafe.Pointer
app *C.struct_Application
// Manages menus
menuManager *menumanager.Manager
// Logger
logger logger.CustomLogger
@@ -78,18 +57,11 @@ func init() {
}
// NewApplicationWithConfig creates a new application based on the given config
func NewApplicationWithConfig(config *Config, logger *logger.Logger) *Application {
func NewApplicationWithConfig(config *options.App, logger *logger.Logger, menuManager *menumanager.Manager) *Application {
return &Application{
config: config,
logger: logger.CustomLogger("Ffenestri"),
}
}
// NewApplication creates a new Application with the default config
func NewApplication(logger *logger.Logger) *Application {
return &Application{
config: defaultConfig,
logger: logger.CustomLogger("Ffenestri"),
config: config,
logger: logger.CustomLogger("Ffenestri"),
menuManager: menuManager,
}
}
@@ -121,18 +93,29 @@ type DispatchClient interface {
SendMessage(string)
}
func intToColour(colour int) (C.int, C.int, C.int, C.int) {
var alpha = C.int(colour & 0xFF)
var blue = C.int((colour >> 8) & 0xFF)
var green = C.int((colour >> 16) & 0xFF)
var red = C.int((colour >> 24) & 0xFF)
return red, green, blue, alpha
}
// Run the application
func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, features *features.Features) error {
func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug bool) error {
title := a.string2CString(a.config.Title)
width := C.int(a.config.Width)
height := C.int(a.config.Height)
resizable := a.bool2Cint(a.config.Resizable)
resizable := a.bool2Cint(!a.config.DisableResize)
devtools := a.bool2Cint(a.config.DevTools)
fullscreen := a.bool2Cint(a.config.Fullscreen)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen)
startHidden := a.bool2Cint(a.config.StartHidden)
logLevel := C.int(a.config.LogLevel)
hideWindowOnClose := a.bool2Cint(a.config.HideWindowOnClose)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel, hideWindowOnClose)
// Save app reference
a.app = unsafe.Pointer(app)
a.app = (*C.struct_Application)(app)
// Set Min Window Size
minWidth := C.int(a.config.MinWidth)
@@ -145,26 +128,34 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, featur
C.SetMaxWindowSize(a.app, maxWidth, maxHeight)
// Set debug if needed
C.SetDebug(app, a.bool2Cint(DEBUG))
C.SetDebug(app, a.bool2Cint(debug))
// Set Frameless
// TODO: Move frameless to Linux options
if a.config.Frameless {
C.DisableFrame(a.app)
}
if a.config.RGBA != 0 {
r, g, b, alpha := intToColour(a.config.RGBA)
C.SetColour(a.app, r, g, b, alpha)
}
// Escape bindings so C doesn't freak out
bindings = strings.ReplaceAll(bindings, `"`, `\"`)
// Set bindings
C.SetBindings(app, a.string2CString(bindings))
// Process feature flags
a.processFeatureFlags(features)
// save the dispatcher in a package variable so that the C callbacks
// can access it
dispatcher = incomingDispatcher.RegisterClient(newClient(a))
// Process platform settings
err := a.processPlatformSettings()
if err != nil {
return err
}
// Check we could initialise the application
if app != nil {
// Yes - Save memory reference and run app, cleaning up afterwards
@@ -174,7 +165,7 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, featur
// Oh no! We couldn't initialise the application
a.logger.Fatal("Cannot initialise Application.")
}
//println("\n\n\n\n\n\nhererererer\n\n\n\n")
a.freeMemory()
return nil
}
@@ -186,11 +177,3 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, featur
func messageFromWindowCallback(data *C.char) {
dispatcher.DispatchMessage(C.GoString(data))
}
func (a *Application) processFeatureFlags(features *features.Features) {
// Process generic features
// Process OS Specific flags
a.processOSFeatureFlags(features)
}

View File

@@ -1,24 +1,55 @@
#ifndef __FFENESTRI_H__
#define __FFENESTRI_H__
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
extern void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen);
extern void SetMinWindowSize(void *app, int minWidth, int minHeight);
extern void SetMaxWindowSize(void *app, int maxWidth, int maxHeight);
extern void Run(void *app, int argc, char **argv);
extern void DestroyApplication(void *app);
extern void SetDebug(void *app, int flag);
extern void SetBindings(void *app, const char *bindings);
extern void ExecJS(void *app, const char *script);
extern void Quit(void *app);
extern void SetTitle(void *app, const char *title);
extern void Fullscreen(void *app);
extern void UnFullscreen(void *app);
extern int SetColour(void *app, const char *colourString);
extern void DisableFrame(void *app);
extern char *SaveFileDialogOnMainThread(void *appPointer, char *title);
extern char *OpenFileDialogOnMainThread(void *appPointer, char *title);
extern char *OpenDirectoryDialogOnMainThread(void *appPointer, char *title);
#include <stdio.h>
struct Application;
extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose);
extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight);
extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight);
extern void Run(struct Application*, int argc, char **argv);
extern void DestroyApplication(struct Application*);
extern void SetDebug(struct Application*, int flag);
extern void SetBindings(struct Application*, const char *bindings);
extern void ExecJS(struct Application*, const char *script);
extern void Hide(struct Application*);
extern void Show(struct Application*);
extern void Center(struct Application*);
extern void Maximise(struct Application*);
extern void Unmaximise(struct Application*);
extern void ToggleMaximise(struct Application*);
extern void Minimise(struct Application*);
extern void Unminimise(struct Application*);
extern void ToggleMinimise(struct Application*);
extern void SetColour(struct Application*, int red, int green, int blue, int alpha);
extern void SetSize(struct Application*, int width, int height);
extern void SetPosition(struct Application*, int x, int y);
extern void Quit(struct Application*);
extern void SetTitle(struct Application*, const char *title);
extern void Fullscreen(struct Application*);
extern void UnFullscreen(struct Application*);
extern void ToggleFullscreen(struct Application*);
extern void DisableFrame(struct Application*);
extern void OpenDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
extern void SaveDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
extern void MessageDialog(struct Application*, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton);
extern void DarkModeEnabled(struct Application*, char *callbackID);
extern void SetApplicationMenu(struct Application*, const char *);
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
extern void DeleteTrayMenuByID(struct Application*, const char *id);
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*);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,19 +1,14 @@
package ffenestri
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
import (
"strconv"
"unsafe"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger"
)
@@ -66,44 +61,148 @@ func (c *Client) WindowUnFullscreen() {
C.UnFullscreen(c.app.app)
}
// WindowShow will show the window
func (c *Client) WindowShow() {
C.Show(c.app.app)
}
// WindowHide will hide the window
func (c *Client) WindowHide() {
C.Hide(c.app.app)
}
// WindowCenter will hide the window
func (c *Client) WindowCenter() {
C.Center(c.app.app)
}
// WindowMaximise will maximise the window
func (c *Client) WindowMaximise() {
C.Maximise(c.app.app)
}
// WindowMinimise will minimise the window
func (c *Client) WindowMinimise() {
C.Minimise(c.app.app)
}
// WindowUnmaximise will unmaximise the window
func (c *Client) WindowUnmaximise() {
C.Unmaximise(c.app.app)
}
// WindowUnminimise will unminimise the window
func (c *Client) WindowUnminimise() {
C.Unminimise(c.app.app)
}
// WindowPosition will position the window to x,y on the
// monitor that the window is mostly on
func (c *Client) WindowPosition(x int, y int) {
C.SetPosition(c.app.app, C.int(x), C.int(y))
}
// WindowSize will resize the window to the given
// width and height
func (c *Client) WindowSize(width int, height int) {
C.SetSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMinSize(width int, height int) {
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMaxSize(width int, height int) {
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour string) bool {
result := C.SetColour(c.app.app, c.app.string2CString(colour))
return result == 1
func (c *Client) WindowSetColour(colour int) {
r, g, b, a := intToColour(colour)
C.SetColour(c.app.app, r, g, b, a)
}
// OpenFileDialog will open a file dialog with the given title
func (c *Client) OpenFileDialog(title string) string {
cstring := C.OpenFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
var result string
if cstring != nil {
result = C.GoString(cstring)
// Free the C string that was allocated by the dialog
C.free(unsafe.Pointer(cstring))
}
return result
// OpenDialog will open a dialog with the given title and filter
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.AllowFiles),
c.app.bool2Cint(dialogOptions.AllowDirectories),
c.app.bool2Cint(dialogOptions.AllowMultiple),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.ResolvesAliases),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// SaveFileDialog will open a save file dialog with the given title
func (c *Client) SaveFileDialog(title string) string {
cstring := C.SaveFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
var result string
if cstring != nil {
result = C.GoString(cstring)
// Free the C string that was allocated by the dialog
C.free(unsafe.Pointer(cstring))
}
return result
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
C.SaveDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// OpenDirectoryDialog will open a directory dialog with the given title
func (c *Client) OpenDirectoryDialog(title string) string {
cstring := C.OpenDirectoryDialogOnMainThread(c.app.app, c.app.string2CString(title))
var result string
if cstring != nil {
result = C.GoString(cstring)
// Free the C string that was allocated by the dialog
C.free(unsafe.Pointer(cstring))
// MessageDialog will open a message dialog with the given options
func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
// Sanity check button length
if len(dialogOptions.Buttons) > 4 {
c.app.logger.Error("Given %d message dialog buttons. Maximum is 4", len(dialogOptions.Buttons))
return
}
return result
// Process buttons
buttons := []string{"", "", "", ""}
for i, button := range dialogOptions.Buttons {
buttons[i] = button
}
C.MessageDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(string(dialogOptions.Type)),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Message),
c.app.string2CString(dialogOptions.Icon),
c.app.string2CString(buttons[0]),
c.app.string2CString(buttons[1]),
c.app.string2CString(buttons[2]),
c.app.string2CString(buttons[3]),
c.app.string2CString(dialogOptions.DefaultButton),
c.app.string2CString(dialogOptions.CancelButton))
}
func (c *Client) DarkModeEnabled(callbackID string) {
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
}
func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
}
func (c *Client) SetTrayMenu(trayMenuJSON string) {
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
}
func (c *Client) UpdateTrayMenuLabel(JSON string) {
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
}
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
}
func (c *Client) DeleteTrayMenuByID(id string) {
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,21 @@
#include "hashmap.h"
#include "stdlib.h"
typedef struct {
long maj;
long min;
long patch;
} OSVersion;
// Macros to make it slightly more sane
#define msg objc_msgSend
#define msg_reg ((id(*)(id, SEL))objc_msgSend)
#define msg_id ((id(*)(id, SEL, id))objc_msgSend)
#define msg_id_id ((id(*)(id, SEL, id, id))objc_msgSend)
#define msg_bool ((id(*)(id, SEL, BOOL))objc_msgSend)
#define msg_int ((id(*)(id, SEL, int))objc_msgSend)
#define msg_uint ((id(*)(id, SEL, unsigned int))objc_msgSend)
#define msg_float ((id(*)(id, SEL, float))objc_msgSend)
#define kInternetEventClass 'GURL'
#define kAEGetURL 'GURL'
#define keyDirectObject '----'
@@ -21,18 +33,29 @@
#define c(str) (id)objc_getClass(str)
#define s(str) sel_registerName(str)
#define u(str) sel_getUid(str)
#define str(input) msg(c("NSString"), s("stringWithUTF8String:"), input)
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
#define cstr(input) (const char *)msg(input, s("UTF8String"))
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
#define ALLOC(classname) msg(c(classname), s("alloc"))
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
#define str(input) ((id(*)(id, SEL, const char *))objc_msgSend)(c("NSString"), s("stringWithUTF8String:"), input)
#define strunicode(input) ((id(*)(id, SEL, id, unsigned short))objc_msgSend)(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
#define cstr(input) (const char *)msg_reg(input, s("UTF8String"))
#define url(input) msg_id(c("NSURL"), s("fileURLWithPath:"), str(input))
#define ALLOC(classname) msg_reg(c(classname), s("alloc"))
#define ALLOC_INIT(classname) msg_reg(msg_reg(c(classname), s("alloc")), s("init"))
#if defined (__aarch64__)
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("frame"))
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("bounds"))
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend)(processInfo, s("operatingSystemVersion"));
#endif
#if defined (__x86_64__)
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))msg)(receiver, s("backingScaleFactor"))
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend_stret)(processInfo, s("operatingSystemVersion"));
#endif
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))objc_msgSend)(receiver, s("backingScaleFactor"))
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
#define MAIN_WINDOW_CALL(str) msg_reg(app->mainWindow, s((str)))
#define NSBackingStoreBuffered 2
@@ -102,6 +125,8 @@
#define NSAlertSecondButtonReturn 1001
#define NSAlertThirdButtonReturn 1002
#define BrokenImage "iVBORw0KGgoAAAANSUhEUgAAABAAAAASCAMAAABl5a5YAAABj1BMVEWopan///+koqSWk5P9/v3///////////+AgACMiovz8/PB0fG9z+3i4+WysbGBfX1Erh80rACLiYqBxolEsDhHlDEbqQDDx+CNho7W1tj4+/bw+O3P5Mn4/f/W1tbK6sX////b2dn////////////8/Pz6+vro6Ojj4+P////G1PL////EzNydmp2cmZnd3eDF1PHs8v/o8P/Q3vrS3vfE0vCdmpqZkpr19/3N2vXI1vPH1fOgnqDg6frP3PbCytvHx8irqq6HhIZtuGtjnlZetU1Xs0NWskBNsi7w9v/d6P7w9P3S4Pzr8Pvl7PrY5PrU4PjQ3fjD1Ozo6Om30NjGzNi7ubm34K+UxKmbnaWXlJeUjpSPi4tppF1TtjxSsTf2+f7L2PTr7e3H2+3V7+q+0uXg4OPg4eLR1uG7z+Hg4ODGzODV2N7V1trP5dmxzs65vcfFxMWq0cKxxr+/vr+0s7apxbWaxrCv2qao05+dlp2Uuo2Dn4F8vIB6xnyAoHmAym9zqGpctENLryNFsgoblJpnAAAAKnRSTlP+hP7+5ZRmYgL+/f39/f39/f38/Pz8/Pv69+7j083My8GocnBPTTMWEgjxeITOAAABEklEQVQY0y3KZXuCYBiG4ceYuu7u3nyVAaKOMBBQ7O5Yd3f3fvheDnd9u8/jBkGwNxP6sjOWVQvY/ftrzfT6bd3yEhCnYZqiaYoKiwX/gXkFiHySTcUTLJMsZ9v8nQvgssWYOEKedKpcOO6CUXD5IlGEY5hLUbyDAAZ6HRf1bnkoavOsFQibg+Q4nuNYL+ON5PHD5nBaraRVyxnzGf6BJzUi2QQCQgMyk8tleL7dg1owpJ17D5IkvV100EingeOopPyo6vfAuXF+9hbDTknZCIaUoeK4efKwG4iT6xDewd7imGlid7gGwv37b6Oh9jwaTdOf/Tc1qH7UZVmuP6G5qZfBr9cAGNy4KiDd4tXIs7tS+QO9aUKvPAIKuQAAAABJRU5ErkJggg=="
struct Application;
int releaseNSObject(void *const context, struct hashmap_element_s *const e);
void TitlebarAppearsTransparent(struct Application* app);
@@ -124,4 +149,6 @@ void* lookupStringConstant(id constantName);
void HasURLHandlers(struct Application* app);
id createImageFromBase64Data(const char *data, bool isTemplateImage);
#endif

View File

@@ -2,6 +2,7 @@
#ifndef __FFENESTRI_LINUX_H__
#define __FFENESTRI_LINUX_H__
#include "common.h"
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
#include <time.h>
@@ -11,9 +12,10 @@
#include <stdarg.h>
// References to assets
extern const unsigned char *assets[];
extern const unsigned char runtime;
extern const char *icon[];
#include "icon.h"
#include "assets.h"
// Constants
#define PRIMARY_MOUSE_BUTTON 1
@@ -23,17 +25,6 @@ extern const char *icon[];
// MAIN DEBUG FLAG
int debug;
// Credit: https://stackoverflow.com/a/8465083
char *concat(const char *s1, const char *s2)
{
const size_t len1 = strlen(s1);
const size_t len2 = strlen(s2);
char *result = malloc(len1 + len2 + 1);
memcpy(result, s1, len1);
memcpy(result + len1, s2, len2 + 1);
return result;
}
// Debug works like sprintf but mutes if the global debug flag is true
// Credit: https://stackoverflow.com/a/20639708
void Debug(char *message, ...)
@@ -80,6 +71,7 @@ struct Application
int height;
int resizable;
int devtools;
int startHidden;
int fullscreen;
int minWidth;
int minHeight;
@@ -100,7 +92,7 @@ struct Application
int lock;
};
void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen)
void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden)
{
// Setup main application struct
struct Application *result = malloc(sizeof(struct Application));
@@ -115,15 +107,11 @@ void *NewApplication(const char *title, int width, int height, int resizable, in
result->maxWidth = 0;
result->maxHeight = 0;
result->frame = 1;
result->startHidden = startHidden;
// Default drag button is PRIMARY
result->dragButton = PRIMARY_MOUSE_BUTTON;
// printf("\n\nWidth: %d\n", result->width);
// printf("Height: %d\n\n", result->height);
// Features
result->sendMessageToBackend = (ffenestriCallback)messageFromWindowCallback;
// Create a unique ID based on the current unix timestamp
@@ -139,10 +127,9 @@ void *NewApplication(const char *title, int width, int height, int resizable, in
return (void *)result;
}
void DestroyApplication(void *appPointer)
void DestroyApplication(struct Application *app)
{
Debug("Destroying Application");
struct Application *app = (struct Application *)appPointer;
g_application_quit(G_APPLICATION(app->application));
@@ -171,9 +158,11 @@ void DestroyApplication(void *appPointer)
// Disconnect signal handlers
WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager((WebKitWebView *)app->webView);
g_signal_handler_disconnect(manager, app->signalInvoke);
g_signal_handler_disconnect(manager, app->signalWindowDrag);
g_signal_handler_disconnect(app->webView, app->signalButtonPressed);
g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
if( app->frame == 0) {
g_signal_handler_disconnect(manager, app->signalWindowDrag);
g_signal_handler_disconnect(app->webView, app->signalButtonPressed);
g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
}
g_signal_handler_disconnect(app->webView, app->signalLoadChanged);
// Release the main GTK Application
@@ -191,45 +180,34 @@ void DestroyApplication(void *appPointer)
// Quit will stop the gtk application and free up all the memory
// used by the application
void Quit(void *appPointer)
void Quit(struct Application *app)
{
Debug("Quit Called");
struct Application *app = (struct Application *)appPointer;
gtk_window_close((GtkWindow *)app->mainWindow);
g_application_quit((GApplication *)app->application);
DestroyApplication(appPointer);
DestroyApplication(app);
}
// SetTitle sets the main window title to the given string
void SetTitle(void *appPointer, const char *title)
void SetTitle(struct Application *app, const char *title)
{
struct Application *app = (struct Application *)appPointer;
gtk_window_set_title(app->mainWindow, title);
}
// Fullscreen sets the main window to be fullscreen
void Fullscreen(void *appPointer)
void Fullscreen(struct Application *app)
{
struct Application *app = (struct Application *)appPointer;
gtk_window_fullscreen(app->mainWindow);
}
// UnFullscreen resets the main window after a fullscreen
void UnFullscreen(void *appPointer)
void UnFullscreen(struct Application *app)
{
struct Application *app = (struct Application *)appPointer;
gtk_window_unfullscreen(app->mainWindow);
}
void Center(void *appPointer)
void setMinMaxSize(struct Application *app)
{
struct Application *app = (struct Application *)appPointer;
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
}
void setMinMaxSize(void *appPointer)
{
struct Application *app = (struct Application *)appPointer;
GdkGeometry size;
size.min_width = size.min_height = size.max_width = size.max_height = 0;
int flags = 0;
@@ -245,32 +223,43 @@ void setMinMaxSize(void *appPointer)
size.min_width = app->minWidth;
flags |= GDK_HINT_MIN_SIZE;
}
Debug("size: %dx%d", app->width, app->height);
Debug("min: %dx%d", size.min_width, size.min_height);
Debug("max: %dx%d", size.max_width, size.max_height);
gtk_window_set_geometry_hints(app->mainWindow, NULL, &size, flags);
}
// OpenFileDialog opens a dialog to select a file
// NOTE: The result is a string that will need to be freed!
char *OpenFileDialog(void *appPointer, char *title)
{
struct Application *app = (struct Application *)appPointer;
char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAction, char **args) {
GtkFileChooserNative *native;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
GtkFileChooserAction action = chooserAction;
gint res;
char *filename;
char *title = args[0];
char *filter = args[1];
native = gtk_file_chooser_native_new(title,
app->mainWindow,
action,
"_Open",
"_Cancel");
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
// If we have filters, process them
if (filter[0] != '\0') {
GtkFileFilter *file_filter = gtk_file_filter_new();
gchar **filters = g_strsplit(filter, ",", -1);
gint i;
for(i = 0; filters && filters[i]; i++) {
gtk_file_filter_add_pattern(file_filter, filters[i]);
// Debug("Adding filter pattern: %s\n", filters[i]);
}
gtk_file_filter_set_name(file_filter, filter);
gtk_file_chooser_add_filter(chooser, file_filter);
g_strfreev(filters);
}
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT)
{
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
filename = gtk_file_chooser_get_filename(chooser);
}
@@ -279,104 +268,65 @@ char *OpenFileDialog(void *appPointer, char *title)
return filename;
}
// SaveFileDialog opens a dialog to select a file
// openFileDialogInternal opens a dialog to select a file
// NOTE: The result is a string that will need to be freed!
char *SaveFileDialog(void *appPointer, char *title)
char *openFileDialogInternal(struct Application *app, char **args)
{
struct Application *app = (struct Application *)appPointer;
GtkFileChooserNative *native;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
gint res;
char *filename;
native = gtk_file_chooser_native_new(title,
app->mainWindow,
action,
"_Save",
"_Cancel");
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT)
{
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
filename = gtk_file_chooser_get_filename(chooser);
}
g_object_unref(native);
return filename;
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_OPEN, args);
}
// OpenDirectoryDialog opens a dialog to select a directory
// saveFileDialogInternal opens a dialog to select a file
// NOTE: The result is a string that will need to be freed!
char *OpenDirectoryDialog(void *appPointer, char *title)
char *saveFileDialogInternal(struct Application *app, char **args)
{
struct Application *app = (struct Application *)appPointer;
GtkFileChooserNative *native;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
gint res;
char *foldername;
native = gtk_file_chooser_native_new(title,
app->mainWindow,
action,
"_Open",
"_Cancel");
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT)
{
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
foldername = gtk_file_chooser_get_filename(chooser);
}
g_object_unref(native);
return foldername;
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SAVE, args);
}
void SetMinWindowSize(void *appPointer, int minWidth, int minHeight)
// openDirectoryDialogInternal opens a dialog to select a directory
// NOTE: The result is a string that will need to be freed!
char *openDirectoryDialogInternal(struct Application *app, char **args)
{
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, args);
}
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
{
struct Application *app = (struct Application *)appPointer;
app->minWidth = minWidth;
app->minHeight = minHeight;
}
void SetMaxWindowSize(void *appPointer, int maxWidth, int maxHeight)
void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
{
struct Application *app = (struct Application *)appPointer;
app->maxWidth = maxWidth;
app->maxHeight = maxHeight;
}
// SetColour sets the colour of the webview to the given colour string
int SetColour(void *appPointer, const char *colourString)
void SetColour(struct Application *app, int red, int green, int blue, int alpha)
{
struct Application *app = (struct Application *)appPointer;
GdkRGBA rgba;
gboolean result = gdk_rgba_parse(&rgba, colourString);
if (result == FALSE)
{
return 0;
}
Debug("Setting webview colour to: %s", colourString);
webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
return 1;
// GdkRGBA rgba;
// rgba.
// gboolean result = gdk_rgba_parse(&rgba, colourString);
// if (result == FALSE)
// {
// return 0;
// }
// // Debug("Setting webview colour to: %s", colourString);
// webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
}
// DisableFrame disables the window frame
void DisableFrame(void *appPointer)
void DisableFrame(struct Application *app)
{
struct Application *app = (struct Application *)appPointer;
app->frame = 0;
}
void syncCallback(GObject *source_object,
GAsyncResult *res,
gpointer user_data)
void *data)
{
struct Application *app = (struct Application *)user_data;
struct Application *app = (struct Application *)data;
app->lock = 0;
}
@@ -384,8 +334,6 @@ void syncEval(struct Application *app, const gchar *script)
{
WebKitWebView *webView = (WebKitWebView *)(app->webView);
// Debug("[%p] webview\n", webView);
// Debug("[%p] Running sync\n", script);
// wait for lock to free
while (app->lock == 1)
@@ -395,18 +343,15 @@ void syncEval(struct Application *app, const gchar *script)
// Set lock
app->lock = 1;
//
webkit_web_view_run_javascript(
webView,
script,
NULL, syncCallback, app);
NULL, syncCallback, (void*)app);
while (app->lock == 1)
{
// Debug("[%p] Waiting for callback\n", script);
g_main_context_iteration(0, true);
}
// Debug("[%p] Finished\n", script);
}
void asyncEval(WebKitWebView *webView, const gchar *script)
@@ -417,7 +362,7 @@ void asyncEval(WebKitWebView *webView, const gchar *script)
NULL, NULL, NULL);
}
typedef void (*dispatchMethod)(void *app, void *);
typedef void (*dispatchMethod)(struct Application *app, void *);
struct dispatchData
{
@@ -429,20 +374,14 @@ struct dispatchData
gboolean executeMethod(gpointer data)
{
struct dispatchData *d = (struct dispatchData *)data;
struct Application *app = (struct Application *)(d->app);
// Debug("Webview %p\n", app->webView);
// Debug("Args %s\n", d->args);
// Debug("Method %p\n", (d->method));
(d->method)(app, d->args);
// Debug("Method Execute Complete. Freeing memory");
(d->method)(d->app, d->args);
g_free(d);
return FALSE;
}
void ExecJS(void *app, char *js)
void ExecJS(struct Application *app, char *js)
{
struct dispatchData *data =
(struct dispatchData *)g_new(struct dispatchData, 1);
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)syncEval;
data->args = js;
data->app = app;
@@ -450,13 +389,14 @@ void ExecJS(void *app, char *js)
gdk_threads_add_idle(executeMethod, data);
}
typedef char *(*dialogMethod)(void *app, void *);
typedef char *(*dialogMethod)(struct Application *app, void *);
struct dialogCall
{
struct Application *app;
dialogMethod method;
void *args;
void *filter;
char *result;
int done;
};
@@ -464,79 +404,70 @@ struct dialogCall
gboolean executeMethodWithReturn(gpointer data)
{
struct dialogCall *d = (struct dialogCall *)data;
struct Application *app = (struct Application *)(d->app);
Debug("Webview %p\n", app->webView);
Debug("Args %s\n", d->args);
Debug("Method %p\n", (d->method));
d->result = (d->method)(app, d->args);
d->result = (d->method)(d->app, d->args);
d->done = 1;
// Debug("Method Execute Complete. Freeing memory");
return FALSE;
}
char *OpenFileDialogOnMainThread(void *app, char *title)
char *OpenFileDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data =
(struct dialogCall *)g_new(struct dialogCall, 1);
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)OpenFileDialog;
data->args = title;
data->method = (dialogMethod)openFileDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
usleep(100000);
}
g_free(data);
return data->result;
}
char *SaveFileDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)saveFileDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
// Debug("Waiting for dialog");
usleep(100000);
}
Debug("Dialog done");
Debug("Result = %s\n", data->result);
g_free(data);
// Fingers crossed this wasn't freed by g_free above
return data->result;
}
char *SaveFileDialogOnMainThread(void *app, char *title)
char *OpenDirectoryDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data =
(struct dialogCall *)g_new(struct dialogCall, 1);
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)SaveFileDialog;
data->args = title;
data->method = (dialogMethod)openDirectoryDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
// Debug("Waiting for dialog");
usleep(100000);
}
Debug("Dialog done");
Debug("Result = %s\n", data->result);
g_free(data);
// Fingers crossed this wasn't freed by g_free above
return data->result;
}
char *OpenDirectoryDialogOnMainThread(void *app, char *title)
{
struct dialogCall *data =
(struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)OpenDirectoryDialog;
data->args = title;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
// Debug("Waiting for dialog");
usleep(100000);
}
Debug("Directory Dialog done");
@@ -555,10 +486,8 @@ void setIcon(struct Application *app)
static void load_finished_cb(WebKitWebView *webView,
WebKitLoadEvent load_event,
gpointer userData)
struct Application *app)
{
struct Application *app;
switch (load_event)
{
// case WEBKIT_LOAD_STARTED:
@@ -580,12 +509,16 @@ static void load_finished_cb(WebKitWebView *webView,
case WEBKIT_LOAD_FINISHED:
/* Load finished, we can now stop the spinner */
// printf("Finished loading: %s\n", webkit_web_view_get_uri(web_view));
app = (struct Application *)userData;
// Bindings
Debug("Binding Methods");
syncEval(app, app->bindings);
// Setup IPC commands
Debug("Setting up IPC methods");
const char *invoke = "window.wailsInvoke=function(message){window.webkit.messageHandlers.external.postMessage(message);};window.wailsDrag=function(message){window.webkit.messageHandlers.windowDrag.postMessage(message);};window.wailsContextMenuMessage=function(message){window.webkit.messageHandlers.contextMenu.postMessage(message);};";
syncEval(app, invoke);
// Runtime
Debug("Setting up Wails runtime");
syncEval(app, &runtime);
@@ -637,21 +570,23 @@ static void load_finished_cb(WebKitWebView *webView,
setMinMaxSize(app);
// Centre by default
Center(app);
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
// Show window and focus
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_grab_focus(app->webView);
if( app->startHidden == 0) {
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_grab_focus(app->webView);
}
break;
}
}
static gboolean
disable_context_menu_cb(WebKitWebView *web_view,
WebKitContextMenu *context_menu,
GdkEvent *event,
WebKitHitTestResult *hit_test_result,
gpointer user_data)
static gboolean disable_context_menu_cb(
WebKitWebView *web_view,
WebKitContextMenu *context_menu,
GdkEvent *event,
WebKitHitTestResult *hit_test_result,
gpointer user_data)
{
return TRUE;
}
@@ -666,12 +601,11 @@ static void printEvent(const char *message, GdkEventButton *event)
event->time);
}
static void dragWindow(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result,
gpointer arg)
struct Application *app)
{
struct Application *app = (struct Application *)arg;
// If we get this message erroneously, ignore
if (app->dragButtonEvent == NULL)
{
@@ -696,31 +630,22 @@ static void dragWindow(WebKitUserContentManager *contentManager,
// Clear the event
app->dragButtonEvent = NULL;
return FALSE;
return;
}
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, gpointer arg)
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, struct Application *app)
{
struct Application *app = (struct Application *)arg;
Debug("I am pressing a button");
if (event->type == GDK_BUTTON_PRESS && event->button == app->dragButton)
{
printEvent("Drag button event was saved", event);
app->dragButtonEvent = event;
}
return FALSE;
}
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, gpointer arg)
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Application *app)
{
struct Application *app = (struct Application *)arg;
Debug("I am releasing a button");
if (event->type == GDK_BUTTON_RELEASE && event->button == app->dragButton)
{
printEvent("Drag button event was reset", event);
app->dragButtonEvent = NULL;
}
return FALSE;
@@ -728,9 +653,8 @@ gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, gpointer arg)
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result,
gpointer arg)
struct Application *app)
{
struct Application *app = (struct Application *)arg;
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
JSCValue *value = webkit_javascript_result_get_js_value(result);
char *message = jsc_value_to_string(value);
@@ -747,16 +671,240 @@ static void sendMessageToBackend(WebKitUserContentManager *contentManager,
g_free(message);
}
void SetDebug(void *applicationPointer, int flag)
void SetDebug(struct Application *app, int flag)
{
struct Application *app = (struct Application *)applicationPointer;
debug = flag;
}
void SetBindings(void *applicationPointer, const char *bindings)
{
struct Application *app = (struct Application *)applicationPointer;
// getCurrentMonitorGeometry gets the geometry of the monitor
// that the window is mostly on.
GdkRectangle getCurrentMonitorGeometry(GtkWindow *window) {
// Get the monitor that the window is currently on
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
GdkMonitor *monitor = gdk_display_get_monitor_at_window (display, gdk_window);
// Get the geometry of the monitor
GdkRectangle result;
gdk_monitor_get_geometry (monitor,&result);
return result;
}
/*******************
* Window Position *
*******************/
// Position holds an x/y corrdinate
struct Position {
int x;
int y;
};
// Internal call for setting the position of the window.
void setPositionInternal(struct Application *app, struct Position *pos) {
// Get the monitor geometry
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
// Move the window relative to the monitor
gtk_window_move(app->mainWindow, m.x + pos->x, m.y + pos->y);
// Free memory
free(pos);
}
// SetPosition sets the position of the window to the given x/y
// coordinates. The x/y values are relative to the monitor
// the window is mostly on.
void SetPosition(struct Application *app, int x, int y) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)setPositionInternal;
struct Position *pos = malloc(sizeof(struct Position));
pos->x = x;
pos->y = y;
data->args = pos;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
/***************
* Window Size *
***************/
// Size holds a width/height
struct Size {
int width;
int height;
};
// Internal call for setting the size of the window.
void setSizeInternal(struct Application *app, struct Size *size) {
gtk_window_resize(app->mainWindow, size->width, size->height);
free(size);
}
// SetSize sets the size of the window to the given width/height
void SetSize(struct Application *app, int width, int height) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)setSizeInternal;
struct Size *size = malloc(sizeof(struct Size));
size->width = width;
size->height = height;
data->args = size;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
// centerInternal will center the main window on the monitor it is mostly in
void centerInternal(struct Application *app)
{
// Get the geometry of the monitor
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
// Get the window width/height
int windowWidth, windowHeight;
gtk_window_get_size(app->mainWindow, &windowWidth, &windowHeight);
// Place the window at the center of the monitor
gtk_window_move(app->mainWindow, ((m.width - windowWidth) / 2) + m.x, ((m.height - windowHeight) / 2) + m.y);
}
// Center the window
void Center(struct Application *app) {
// Setup a call to centerInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)centerInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// hideInternal hides the main window
void hideInternal(struct Application *app) {
gtk_widget_hide (GTK_WIDGET(app->mainWindow));
}
// Hide places the hideInternal method onto the main thread for execution
void Hide(struct Application *app) {
// Setup a call to hideInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)hideInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// showInternal shows the main window
void showInternal(struct Application *app) {
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_grab_focus(app->webView);
}
// Show places the showInternal method onto the main thread for execution
void Show(struct Application *app) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)showInternal;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
// maximiseInternal maximises the main window
void maximiseInternal(struct Application *app) {
gtk_window_maximize(GTK_WINDOW(app->mainWindow));
}
// Maximise places the maximiseInternal method onto the main thread for execution
void Maximise(struct Application *app) {
// Setup a call to maximiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)maximiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// unmaximiseInternal unmaximises the main window
void unmaximiseInternal(struct Application *app) {
gtk_window_unmaximize(GTK_WINDOW(app->mainWindow));
}
// Unmaximise places the unmaximiseInternal method onto the main thread for execution
void Unmaximise(struct Application *app) {
// Setup a call to unmaximiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)unmaximiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
void DarkModeEnabled(struct Application* app, char *callbackID) {}
void SetApplicationMenu(struct Application* app, const char *menuJSON) {}
void AddTrayMenu(struct Application* app, const char *menuTrayJSON) {}
void SetTrayMenu(struct Application* app, const char *menuTrayJSON) {}
void DeleteTrayMenuByID(struct Application* app, const char *id) {}
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 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) {}
// minimiseInternal minimises the main window
void minimiseInternal(struct Application *app) {
gtk_window_iconify(app->mainWindow);
}
// Minimise places the minimiseInternal method onto the main thread for execution
void Minimise(struct Application *app) {
// Setup a call to minimiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)minimiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// unminimiseInternal unminimises the main window
void unminimiseInternal(struct Application *app) {
gtk_window_present(app->mainWindow);
}
// Unminimise places the unminimiseInternal method onto the main thread for execution
void Unminimise(struct Application *app) {
// Setup a call to unminimiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)unminimiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
void SetBindings(struct Application *app, const char *bindings)
{
const char *temp = concat("window.wailsbindings = \"", bindings);
const char *jscall = concat(temp, "\";");
free((void *)temp);
@@ -766,18 +914,15 @@ void SetBindings(void *applicationPointer, const char *bindings)
// This is called when the close button on the window is pressed
gboolean close_button_pressed(GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
struct Application *app)
{
struct Application *app = (struct Application *)user_data;
app->sendMessageToBackend("WC"); // Window Close
return TRUE;
}
static void setupWindow(void *applicationPointer)
static void setupWindow(struct Application *app)
{
struct Application *app = (struct Application *)applicationPointer;
// Create the window
GtkWidget *mainWindow = gtk_application_window_new(app->application);
// Save reference
@@ -796,10 +941,14 @@ static void setupWindow(void *applicationPointer)
webkit_user_content_manager_register_script_message_handler(contentManager, "external");
app->signalInvoke = g_signal_connect(contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), app);
// Setup the window drag handler
webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
// Setup the window drag handler if this is a frameless app
if ( app->frame == 0 ) {
webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
// Setup the mouse handlers
app->signalButtonPressed = g_signal_connect(app->webView, "button-press-event", G_CALLBACK(buttonPress), app);
app->signalButtonReleased = g_signal_connect(app->webView, "button-release-event", G_CALLBACK(buttonRelease), app);
}
GtkWidget *webView = webkit_web_view_new_with_user_content_manager(contentManager);
// Save reference
@@ -808,9 +957,6 @@ static void setupWindow(void *applicationPointer)
// Add the webview to the window
gtk_container_add(GTK_CONTAINER(mainWindow), webView);
// Setup the mouse handlers
app->signalButtonPressed = g_signal_connect(app->webView, "button-press-event", G_CALLBACK(buttonPress), app);
app->signalButtonReleased = g_signal_connect(app->webView, "button-release-event", G_CALLBACK(buttonRelease), app);
// Load default HTML
app->signalLoadChanged = g_signal_connect(G_OBJECT(webView), "load-changed", G_CALLBACK(load_finished_cb), app);
@@ -835,20 +981,13 @@ static void setupWindow(void *applicationPointer)
g_signal_connect(GTK_WIDGET(mainWindow), "delete-event", G_CALLBACK(close_button_pressed), app);
}
static void
activate(GtkApplication *app,
gpointer user_data)
static void activate(GtkApplication* _, struct Application *app)
{
struct Application *mainApp = (struct Application *)user_data;
// Main Window
setupWindow(mainApp);
setupWindow(app);
}
void Run(void *applicationPointer, int argc, char **argv)
void Run(struct Application *app, int argc, char **argv)
{
struct Application *app = (struct Application *)applicationPointer;
g_signal_connect(app->application, "activate", G_CALLBACK(activate), app);
g_application_run(G_APPLICATION(app->application), argc, argv);
}

View File

@@ -0,0 +1,17 @@
package ffenestri
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include "ffenestri.h"
#include "ffenestri_linux.h"
*/
import "C"
func (a *Application) processPlatformSettings() error {
return nil
}

View File

@@ -0,0 +1,6 @@
#ifndef FFENESTRI_LINUX_H
#define FFENESTRI_LINUX_H
#endif

View File

@@ -0,0 +1,622 @@
// Some code may be inspired by or directly used from Webview (c) zserge.
// License included in README.md
#include "ffenestri_windows.h"
#include "wv2ComHandler_windows.h"
#include <functional>
#include <atomic>
#include <Shlwapi.h>
#include <locale>
#include <codecvt>
#include "windows/WebView2.h"
#include "effectstructs_windows.h"
#include <Shlobj.h>
int debug = 0;
DWORD mainThread;
// --- Assets
extern const unsigned char runtime;
extern const unsigned char *defaultDialogIcons[];
// dispatch will execute the given `func` pointer
void dispatch(dispatchFunction func) {
PostThreadMessage(mainThread, WM_APP, 0, (LPARAM) new dispatchFunction(func));
}
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];
MultiByteToWideChar( CP_UTF8 , 0 , cstr , -1, wstr , wchars_num );
return wstr;
}
// Credit: https://stackoverflow.com/a/9842450
char* LPWSTRToCstr(LPWSTR input) {
int length = WideCharToMultiByte(CP_UTF8, 0, input, -1, 0, 0, NULL, NULL);
char* output = new char[length];
WideCharToMultiByte(CP_UTF8, 0, input, -1, output , length, NULL, NULL);
return output;
}
struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
// Create application
struct Application *result = (struct Application*)malloc(sizeof(struct Application));
result->window = nullptr;
result->webview = nullptr;
result->webviewController = nullptr;
result->title = title;
result->width = width;
result->height = height;
result->resizable = resizable;
result->devtools = devtools;
result->fullscreen = fullscreen;
result->startHidden = startHidden;
result->logLevel = logLevel;
result->hideWindowOnClose = hideWindowOnClose;
result->webviewIsTranparent = false;
result->windowBackgroundIsTranslucent = false;
// Min/Max Width/Height
result->minWidth = 0;
result->minHeight = 0;
result->maxWidth = 0;
result->maxHeight = 0;
// Default colour
result->backgroundColour.R = 255;
result->backgroundColour.G = 255;
result->backgroundColour.B = 255;
result->backgroundColour.A = 255;
// Have a frame by default
result->frame = 1;
// Capture Main Thread
mainThread = GetCurrentThreadId();
// Startup url
result->startupURL = nullptr;
return result;
}
void SetMinWindowSize(struct Application* app, int minWidth, int minHeight) {
app->minWidth = (LONG)minWidth;
app->minHeight = (LONG)minHeight;
}
void SetMaxWindowSize(struct Application* app, int maxWidth, int maxHeight) {
app->maxWidth = (LONG)maxWidth;
app->maxHeight = (LONG)maxHeight;
}
void SetBindings(struct Application *app, const char *bindings) {
std::string temp = std::string("window.wailsbindings = \"") + std::string(bindings) + std::string("\";");
app->bindings = new char[temp.length()+1];
memcpy(app->bindings, temp.c_str(), temp.length()+1);
}
void performShutdown(struct Application *app) {
if( app->startupURL != nullptr ) {
delete[] app->startupURL;
}
messageFromWindowCallback("WC");
}
void enableTranslucentBackground(struct Application *app) {
HMODULE hUser = GetModuleHandleA("user32.dll");
if (hUser)
{
pfnSetWindowCompositionAttribute setWindowCompositionAttribute = (pfnSetWindowCompositionAttribute)GetProcAddress(hUser, "SetWindowCompositionAttribute");
if (setWindowCompositionAttribute)
{
ACCENT_POLICY accent = { ACCENT_ENABLE_BLURBEHIND, 0, 0, 0 };
WINDOWCOMPOSITIONATTRIBDATA data;
data.Attrib = WCA_ACCENT_POLICY;
data.pvData = &accent;
data.cbData = sizeof(accent);
setWindowCompositionAttribute(app->window, &data);
}
}
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
struct Application *app = (struct Application *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch(msg) {
case WM_DESTROY: {
DestroyApplication(app);
break;
}
case WM_SIZE: {
if( app->webviewController != nullptr) {
RECT bounds;
GetClientRect(app->window, &bounds);
app->webviewController->put_Bounds(bounds);
}
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);
RECT rcClient, 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;
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;
}
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;
}
return 0;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
void init(struct Application *app, const char* js) {
LPCWSTR wjs = cstrToLPWSTR(js);
app->webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr);
delete[] wjs;
}
void execJS(struct Application* app, const char *script) {
LPWSTR s = cstrToLPWSTR(script);
app->webview->ExecuteScript(s, nullptr);
delete[] s;
}
void loadAssets(struct Application* app) {
// setup window.wailsInvoke
std::string initialCode = std::string("window.wailsInvoke=function(m){window.chrome.webview.postMessage(m)};");
// Load bindings
initialCode += std::string(app->bindings);
delete[] app->bindings;
// Load runtime
initialCode += std::string((const char*)&runtime);
int index = 1;
while(1) {
// Get next asset pointer
const unsigned char *asset = assets[index];
// If we have no more assets, break
if (asset == 0x00) {
break;
}
initialCode += std::string((const char*)asset);
index++;
};
// Disable context menu if not in debug mode
if( debug != 1 ) {
initialCode += std::string("wails._.DisableDefaultContextMenu();");
}
initialCode += std::string("window.wailsInvoke('completed');");
// Keep a copy of the code
app->initialCode = new char[initialCode.length()+1];
memcpy(app->initialCode, initialCode.c_str(), initialCode.length()+1);
execJS(app, app->initialCode);
// Show app if we need to
if( app->startHidden == false ) {
Show(app);
}
}
// This is called when all our assets are loaded into the DOM
void completed(struct Application* app) {
delete[] app->initialCode;
app->initialCode = nullptr;
if( app->startupURL == nullptr ) {
messageFromWindowCallback("SS");
return;
}
std::string readyMessage = std::string("SS") + std::string(app->startupURL);
messageFromWindowCallback(readyMessage.c_str());
}
//
bool initWebView2(struct Application *app, int debugEnabled, messageCallback cb) {
debug = debugEnabled;
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
std::atomic_flag flag = ATOMIC_FLAG_INIT;
flag.test_and_set();
// char currentExePath[MAX_PATH];
// GetModuleFileNameA(NULL, currentExePath, MAX_PATH);
// char *currentExeName = PathFindFileNameA(currentExePath);
// std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
// auto exeName = wideCharConverter.from_bytes(currentExeName);
//
// PWSTR path;
// HRESULT appDataResult = SHGetFolderPathAndSubDir(app->window, CSIDL_LOCAL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, exeName.c_str(), path);
// if ( appDataResult == false ) {
// path = nullptr;
// }
//
ICoreWebView2Controller *controller;
ICoreWebView2* webview;
HRESULT res = CreateCoreWebView2EnvironmentWithOptions(
nullptr, nullptr, nullptr,
new wv2ComHandler(app, app->window, cb,
[&](ICoreWebView2Controller *webviewController) {
controller = webviewController;
controller->get_CoreWebView2(&webview);
webview->AddRef();
flag.clear();
}));
if (res != S_OK) {
CoUninitialize();
return false;
}
MSG msg = {};
while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
app->webviewController = controller;
app->webview = webview;
// Resize WebView to fit the bounds of the parent window
RECT bounds;
GetClientRect(app->window, &bounds);
app->webviewController->put_Bounds(bounds);
// Let the backend know we have initialised
app->webview->AddScriptToExecuteOnDocumentCreated(L"window.chrome.webview.postMessage('initialised');", nullptr);
// Load the HTML
LPCWSTR html = (LPCWSTR) cstrToLPWSTR((char*)assets[0]);
app->webview->Navigate(html);
messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}");
return true;
}
void initialCallback(std::string message) {
printf("MESSAGE=%s\n", message);
}
void Run(struct Application* app, int argc, char **argv) {
WNDCLASSEX wc;
HINSTANCE hInstance = GetModuleHandle(NULL);
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.hInstance = hInstance;
wc.lpszClassName = (LPCWSTR)"ffenestri";
wc.lpfnWndProc = WndProc;
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(100));
wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(100));
// TODO: Menu
// wc.lpszMenuName = nullptr;
// Process window resizable
DWORD windowStyle = WS_OVERLAPPEDWINDOW;
if (app->resizable == 0) {
windowStyle &= ~WS_MAXIMIZEBOX;
windowStyle &= ~WS_THICKFRAME;
}
if ( app->frame == 0 ) {
windowStyle = WS_POPUP;
}
RegisterClassEx(&wc);
app->window = CreateWindow((LPCWSTR)"ffenestri", (LPCWSTR)"", windowStyle, CW_USEDEFAULT,
CW_USEDEFAULT, app->width, app->height, NULL, NULL,
hInstance, NULL);
// Private setTitle as we're on the main thread
setTitle(app, app->title);
// Store application pointer in window handle
SetWindowLongPtr(app->window, GWLP_USERDATA, (LONG_PTR)app);
// Process whether window should show by default
int startVisibility = SW_SHOWNORMAL;
if ( app->startHidden == 1 ) {
startVisibility = SW_HIDE;
}
// TODO: Make configurable
// COREWEBVIEW2_COLOR wvColor;
// wvColor.A = 255;
// std::weak_ptr<ICoreWebView2Controller2> controller2 = app->webviewController->query<ICoreWebView2Controller2>();
// controller2->put_DefaultBackgroundColor(wvColor);
if( app->windowBackgroundIsTranslucent ) {
enableTranslucentBackground(app);
}
// private center() as we are on main thread
center(app);
ShowWindow(app->window, startVisibility);
UpdateWindow(app->window);
SetFocus(app->window);
// Add webview2
initWebView2(app, 1, initialCallback);
// Main event loop
MSG msg;
BOOL res;
while ((res = GetMessage(&msg, NULL, 0, 0)) != -1) {
if (msg.hwnd) {
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
}
if (msg.message == WM_APP) {
dispatchFunction *f = (dispatchFunction*) msg.lParam;
(*f)();
delete(f);
} else if (msg.message == WM_QUIT) {
performShutdown(app);
return;
}
}
}
void DestroyApplication(struct Application* app) {
PostQuitMessage(0);
}
void SetDebug(struct Application* app, int flag) {
debug = flag;
}
void ExecJS(struct Application* app, const char *script) {
ON_MAIN_THREAD(
execJS(app, script);
);
}
void hide(struct Application* app) {
ShowWindow(app->window, SW_HIDE);
}
void Hide(struct Application* app) {
ON_MAIN_THREAD(
hide(app);
);
}
void show(struct Application* app) {
ShowWindow(app->window, SW_SHOW);
}
void Show(struct Application* app) {
ON_MAIN_THREAD(
show(app);
);
}
void center(struct Application* app) {
HMONITOR currentMonitor = MonitorFromWindow(app->window, MONITOR_DEFAULTTONEAREST);
MONITORINFO info = {0};
info.cbSize = sizeof(info);
GetMonitorInfoA(currentMonitor, &info);
RECT workRect = info.rcWork;
LONG screenMiddleW = (workRect.right - workRect.left) / 2;
LONG screenMiddleH = (workRect.bottom - workRect.top) / 2;
RECT winRect;
if (app->frame == 1) {
GetWindowRect(app->window, &winRect);
} else {
GetClientRect(app->window, &winRect);
}
LONG winWidth = winRect.right - winRect.left;
LONG winHeight = winRect.bottom - winRect.top;
LONG windowX = screenMiddleW - (winWidth / 2);
LONG windowY = screenMiddleH - (winHeight / 2);
SetWindowPos(app->window, HWND_TOP, windowX, windowY, winWidth, winHeight, SWP_NOSIZE);
}
void Center(struct Application* app) {
ON_MAIN_THREAD(
center(app);
);
}
UINT getWindowPlacement(struct Application* app) {
WINDOWPLACEMENT lpwndpl;
lpwndpl.length = sizeof(WINDOWPLACEMENT);
BOOL result = GetWindowPlacement(app->window, &lpwndpl);
if( result == 0 ) {
// TODO: Work out what this call failing means
return -1;
}
return lpwndpl.showCmd;
}
int isMaximised(struct Application* app) {
return getWindowPlacement(app) == SW_SHOWMAXIMIZED;
}
void maximise(struct Application* app) {
ShowWindow(app->window, SW_MAXIMIZE);
}
void Maximise(struct Application* app) {
ON_MAIN_THREAD(
maximise(app);
);
}
void unmaximise(struct Application* app) {
ShowWindow(app->window, SW_RESTORE);
}
void Unmaximise(struct Application* app) {
ON_MAIN_THREAD(
unmaximise(app);
);
}
void ToggleMaximise(struct Application* app) {
if(isMaximised(app)) {
return Unmaximise(app);
}
return Maximise(app);
}
int isMinimised(struct Application* app) {
return getWindowPlacement(app) == SW_SHOWMINIMIZED;
}
void minimise(struct Application* app) {
ShowWindow(app->window, SW_MINIMIZE);
}
void Minimise(struct Application* app) {
ON_MAIN_THREAD(
minimise(app);
);
}
void unminimise(struct Application* app) {
ShowWindow(app->window, SW_RESTORE);
}
void Unminimise(struct Application* app) {
ON_MAIN_THREAD(
unminimise(app);
);
}
void ToggleMinimise(struct Application* app) {
if(isMinimised(app)) {
return Unminimise(app);
}
return Minimise(app);
}
void SetColour(struct Application* app, int red, int green, int blue, int alpha) {
// TBD
}
void SetSize(struct Application* app, int width, int height) {
// TBD
}
void setPosition(struct Application* app, int x, int y) {
// TBD
}
void SetPosition(struct Application* app, int x, int y) {
// ON_MAIN_THREAD(
// setPosition(app, x, y);
// );
}
void Quit(struct Application* app) {
DestroyWindow(app->window);
}
// Credit: https://stackoverflow.com/a/6693107
void setTitle(struct Application* app, const char *title) {
LPCTSTR text = cstrToLPWSTR(title);
SetWindowText(app->window, text);
delete[] text;
}
void SetTitle(struct Application* app, const char *title) {
ON_MAIN_THREAD(
setTitle(app, title);
);
}
void Fullscreen(struct Application* app) {
}
void UnFullscreen(struct Application* app) {
}
void ToggleFullscreen(struct Application* app) {
}
void DisableFrame(struct Application* app) {
app->frame = 0;
}
// WebviewIsTransparent will make the webview transparent
// revealing the window underneath
void WebviewIsTransparent(struct Application *app) {
app->webviewIsTranparent = true;
}
void WindowBackgroundIsTranslucent(struct Application *app) {
app->windowBackgroundIsTranslucent = true;
}
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) {
}
void DarkModeEnabled(struct Application* app, char *callbackID) {
}
void SetApplicationMenu(struct Application* app, const char *applicationMenuJSON) {
}
void AddTrayMenu(struct Application* app, const char *menuTrayJSON) {
}
void SetTrayMenu(struct Application* app, const char *menuTrayJSON) {
}
void DeleteTrayMenuByID(struct Application* app, const char *id) {
}
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
}
void AddContextMenu(struct Application* app, char *contextMenuJSON) {
}
void UpdateContextMenu(struct Application* app, char *contextMenuJSON) {
}

View File

@@ -0,0 +1,66 @@
package ffenestri
import "C"
/*
#cgo windows CXXFLAGS: -std=c++11
#cgo windows,amd64 LDFLAGS: -L./windows/x64 -lwebview -lWebView2Loader -lgdi32 -lole32 -lShlwapi -luser32 -loleaut32
#include "ffenestri.h"
*/
import "C"
func (a *Application) processPlatformSettings() error {
config := a.config.Windows
if config == nil {
return nil
}
// Check if the webview should be transparent
if config.WebviewIsTransparent {
C.WebviewIsTransparent(a.app)
}
if config.WindowBackgroundIsTranslucent {
C.WindowBackgroundIsTranslucent(a.app)
}
//// Process menu
////applicationMenu := options.GetApplicationMenu(a.config)
//applicationMenu := a.menuManager.GetApplicationMenuJSON()
//if applicationMenu != "" {
// C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
//}
//
//// Process tray
//trays, err := a.menuManager.GetTrayMenus()
//if err != nil {
// return err
//}
//if trays != nil {
// for _, tray := range trays {
// C.AddTrayMenu(a.app, a.string2CString(tray))
// }
//}
//
//// Process context menus
//contextMenus, err := a.menuManager.GetContextMenus()
//if err != nil {
// return err
//}
//if contextMenus != nil {
// for _, contextMenu := range contextMenus {
// C.AddContextMenu(a.app, a.string2CString(contextMenu))
// }
//}
//
//// Process URL Handlers
//if a.config.Mac.URLHandlers != nil {
// C.HasURLHandlers(a.app)
//}
return nil
}

View File

@@ -0,0 +1,75 @@
#ifndef _FFENESTRI_WINDOWS_H
#define _FFENESTRI_WINDOWS_H
#define WIN32_LEAN_AND_MEAN
#define UNICODE 1
#include "ffenestri.h"
#include <windows.h>
#include <wingdi.h>
#include <functional>
#include <codecvt>
#include "windows/WebView2.h"
#include "assets.h"
// TODO:
//#include "userdialogicons.h"
struct Application{
// Window specific
HWND window;
ICoreWebView2 *webview;
ICoreWebView2Controller* webviewController;
// Application
const char *title;
int width;
int height;
int resizable;
int devtools;
int fullscreen;
int startHidden;
int logLevel;
int hideWindowOnClose;
int minSizeSet;
LONG minWidth;
LONG minHeight;
int maxSizeSet;
LONG maxWidth;
LONG maxHeight;
int frame;
char *startupURL;
bool webviewIsTranparent;
bool windowBackgroundIsTranslucent;
COREWEBVIEW2_COLOR backgroundColour;
// placeholders
char* bindings;
char* initialCode;
};
#define ON_MAIN_THREAD(code) dispatch( [=]{ code; } )
typedef std::function<void()> dispatchFunction;
typedef std::function<void(const std::string)> messageCallback;
typedef std::function<void(ICoreWebView2Controller *)> comHandlerCallback;
void center(struct Application*);
void setTitle(struct Application* app, const char *title);
char* LPWSTRToCstr(LPWSTR input);
// called when the DOM is ready
void loadAssets(struct Application* app);
// called when the application assets have been loaded into the DOM
void completed(struct Application* app);
// Callback
extern "C" {
void messageFromWindowCallback(const char *);
}
#endif

View File

@@ -1,3 +1,5 @@
// +build !windows
/*
Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
All rights reserved.

View File

@@ -90,7 +90,7 @@ void DeleteMenu(Menu *menu) {
// Free nsmenu if we have it
if ( menu->menu != NULL ) {
msg(menu->menu, s("release"));
msg_reg(menu->menu, s("release"));
}
free(menu);
@@ -120,17 +120,17 @@ const char* createMenuClickedMessage(const char *menuItemID, const char *data, e
// Callback for text menu items
void menuItemCallback(id self, SEL cmd, id sender) {
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg_reg(msg_reg(sender, s("representedObject")), s("pointerValue"));
const char *message;
// Update checkbox / radio item
if( callbackData->menuItemType == Checkbox) {
// Toggle state
bool state = msg(callbackData->menuItem, s("state"));
msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
bool state = msg_reg(callbackData->menuItem, s("state"));
msg_int(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
} else if( callbackData->menuItemType == Radio ) {
// Check the menu items' current state
bool selected = msg(callbackData->menuItem, s("state"));
bool selected = (bool)msg_reg(callbackData->menuItem, s("state"));
// If it's already selected, exit early
if (selected) return;
@@ -142,13 +142,13 @@ void menuItemCallback(id self, SEL cmd, id sender) {
id thisMember = members[0];
int count = 0;
while(thisMember != NULL) {
msg(thisMember, s("setState:"), NSControlStateValueOff);
msg_int(thisMember, s("setState:"), NSControlStateValueOff);
count = count + 1;
thisMember = members[count];
}
// check the selected menu item
msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
msg_int(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
}
const char *menuID = callbackData->menuID;
@@ -189,6 +189,9 @@ id processAcceleratorKey(const char *key) {
if( STREQ(key, "return") ) {
return strunicode(0x000d);
}
if( STREQ(key, "enter") ) {
return strunicode(0x000d);
}
if( STREQ(key, "escape") ) {
return strunicode(0x001b);
}
@@ -345,61 +348,61 @@ id processAcceleratorKey(const char *key) {
void addSeparator(id menu) {
id item = msg(c("NSMenuItem"), s("separatorItem"));
msg(menu, s("addItem:"), item);
id item = msg_reg(c("NSMenuItem"), s("separatorItem"));
msg_id(menu, s("addItem:"), item);
}
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
id item = ALLOC("NSMenuItem");
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
return item;
}
id createMenuItem(id title, const char *action, const char *key) {
id item = ALLOC("NSMenuItem");
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
msg(item, s("autorelease"));
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
msg_reg(item, s("autorelease"));
return item;
}
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
id item = createMenuItem(str(title), action, key);
msg(item, s("setEnabled:"), !disabled);
msg(menu, s("addItem:"), item);
msg_bool(item, s("setEnabled:"), !disabled);
msg_id(menu, s("addItem:"), item);
return item;
}
id createMenu(id title) {
id menu = ALLOC("NSMenu");
msg(menu, s("initWithTitle:"), title);
msg(menu, s("setAutoenablesItems:"), NO);
msg_id(menu, s("initWithTitle:"), title);
msg_bool(menu, s("setAutoenablesItems:"), NO);
// msg(menu, s("autorelease"));
return menu;
}
void createDefaultAppMenu(id parentMenu) {
// App Menu
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
id appName = msg_reg(msg_reg(c("NSProcessInfo"), s("processInfo")), s("processName"));
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
id appMenu = createMenu(appName);
msg(appMenuItem, s("setSubmenu:"), appMenu);
msg(parentMenu, s("addItem:"), appMenuItem);
msg_id(appMenuItem, s("setSubmenu:"), appMenu);
msg_id(parentMenu, s("addItem:"), appMenuItem);
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
id title = msg_id(str("Hide "), s("stringByAppendingString:"), appName);
id item = createMenuItem(title, "hide:", "h");
msg(appMenu, s("addItem:"), item);
msg_id(appMenu, s("addItem:"), item);
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
addSeparator(appMenu);
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
title = msg_id(str("Quit "), s("stringByAppendingString:"), appName);
item = createMenuItem(title, "terminate:", "q");
msg(appMenu, s("addItem:"), item);
msg_id(appMenu, s("addItem:"), item);
}
void createDefaultEditMenu(id parentMenu) {
@@ -407,8 +410,8 @@ void createDefaultEditMenu(id parentMenu) {
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
id editMenu = createMenu(str("Edit"));
msg(editMenuItem, s("setSubmenu:"), editMenu);
msg(parentMenu, s("addItem:"), editMenuItem);
msg_id(editMenuItem, s("setSubmenu:"), editMenu);
msg_id(parentMenu, s("addItem:"), editMenuItem);
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
@@ -436,7 +439,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
}
if ( STREQ(roleName, "hideothers")) {
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
return;
}
if ( STREQ(roleName, "unhide")) {
@@ -473,7 +476,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
}
if( STREQ(roleName, "pasteandmatchstyle")) {
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
msg_int(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
}
if ( STREQ(roleName, "selectall")) {
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
@@ -540,18 +543,18 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
msg_id(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg_bool(item, s("setEnabled:"), !disabled);
msg_reg(item, s("autorelease"));
msg_int(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg(parentmenu, s("addItem:"), item);
msg_id(parentmenu, s("addItem:"), item);
return item;
}
@@ -566,46 +569,136 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg(parentmenu, s("addItem:"), item);
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
msg_id(item, s("setRepresentedObject:"), wrappedId);
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
msg_bool(item, s("setEnabled:"), !disabled);
msg_reg(item, s("autorelease"));
msg_int(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg_id(parentmenu, s("addItem:"), item);
return item;
}
// getColour returns the colour from a styledLabel based on the key
const char* getColour(JsonNode *styledLabelEntry, const char* key) {
JsonNode* colEntry = getJSONObject(styledLabelEntry, key);
if( colEntry == NULL ) {
return NULL;
}
return getJSONString(colEntry, "hex");
}
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize) {
// Create result
id attributedString = ALLOC_INIT("NSMutableAttributedString");
msg_reg(attributedString, s("autorelease"));
// Create new Dictionary
id dictionary = ALLOC_INIT("NSMutableDictionary");
msg_reg(dictionary, s("autorelease"));
// Use default font
CGFloat fontSizeFloat = (CGFloat)fontSize;
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
// Check user supplied font
if( STR_HAS_CHARS(fontName) ) {
id fontNameAsNSString = str(fontName);
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
if( userFont != NULL ) {
font = userFont;
}
}
id fan = lookupStringConstant(str("NSFontAttributeName"));
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
id NSBackgroundColorAttributeName = lookupStringConstant(str("NSBackgroundColorAttributeName"));
// Loop over styled text creating NSAttributedText and appending to result
JsonNode *styledLabelEntry;
json_foreach(styledLabelEntry, styledLabel) {
// Clear dictionary
msg_reg(dictionary, s("removeAllObjects"));
// Add font to dictionary
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
// Get Text
const char* thisLabel = mustJSONString(styledLabelEntry, "Label");
// Get foreground colour
const char *hexColour = getColour(styledLabelEntry, "FgCol");
if( hexColour != NULL) {
unsigned short r, g, b, a;
// white by default
r = g = b = a = 255;
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
if (count > 0) {
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(CGFloat)r / (CGFloat)255.0,
(CGFloat)g / (CGFloat)255.0,
(CGFloat)b / (CGFloat)255.0,
(CGFloat)a / (CGFloat)255.0);
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
}
}
// Get background colour
hexColour = getColour(styledLabelEntry, "BgCol");
if( hexColour != NULL) {
unsigned short r, g, b, a;
// white by default
r = g = b = a = 255;
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
if (count > 0) {
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(CGFloat)r / (CGFloat)255.0,
(CGFloat)g / (CGFloat)255.0,
(CGFloat)b / (CGFloat)255.0,
(CGFloat)a / (CGFloat)255.0);
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
}
}
// Create AttributedText
id thisString = ALLOC("NSMutableAttributedString");
msg_reg(thisString, s("autorelease"));
msg_id_id(thisString, s("initWithString:attributes:"), str(thisLabel), dictionary);
// Append text to result
msg_id(attributedString, s("appendAttributedString:"), thisString);
}
return attributedString;
}
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
// Process Menu Item attributes
// Create new Dictionary
id dictionary = ALLOC_INIT("NSMutableDictionary");
// Process font
id font;
CGFloat fontSizeFloat = (CGFloat)fontSize;
// Check if valid
id fontNameAsNSString = str(fontName);
id fontsOnSystem = msg(msg(c("NSFontManager"), s("sharedFontManager")), s("availableFonts"));
bool valid = msg(fontsOnSystem, s("containsObject:"), fontNameAsNSString);
if( valid ) {
font = msg(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
} else {
bool supportsMonospacedDigitSystemFont = (bool) msg(c("NSFont"), s("respondsToSelector:"), s("monospacedDigitSystemFontOfSize:weight:"));
if( supportsMonospacedDigitSystemFont ) {
font = msg(c("NSFont"), s("monospacedDigitSystemFontOfSize:weight:"), fontSizeFloat, NSFontWeightRegular);
} else {
font = msg(c("NSFont"), s("menuFontOfSize:"), fontSizeFloat);
// Use default font
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
// Check user supplied font
if( STR_HAS_CHARS(fontName) ) {
id fontNameAsNSString = str(fontName);
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
if( userFont != NULL ) {
font = userFont;
}
}
// Add font to dictionary
msg(dictionary, s("setObject:forKey:"), font, lookupStringConstant(str("NSFontAttributeName")));
// Add offset to dictionary
id offset = msg(c("NSNumber"), s("numberWithFloat:"), 0.0);
msg(dictionary, s("setObject:forKey:"), offset, lookupStringConstant(str("NSBaselineOffsetAttributeName")));
id fan = lookupStringConstant(str("NSFontAttributeName"));
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
// RGBA
if( RGBA != NULL && strlen(RGBA) > 0) {
@@ -615,74 +708,75 @@ id createAttributedString(const char* title, const char* fontName, int fontSize,
r = g = b = a = 255;
int count = sscanf(RGBA, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
if (count > 0) {
id colour = msg(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(float)r / 255.0,
(float)g / 255.0,
(float)b / 255.0,
(float)a / 255.0);
msg(dictionary, s("setObject:forKey:"), colour, lookupStringConstant(str("NSForegroundColorAttributeName")));
msg(colour, s("release"));
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(CGFloat)r / (CGFloat)255.0,
(CGFloat)g / (CGFloat)255.0,
(CGFloat)b / (CGFloat)255.0,
(CGFloat)a / (CGFloat)255.0);
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
}
}
id attributedString = ALLOC("NSMutableAttributedString");
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
msg(attributedString, s("autorelease"));
msg(dictionary, s("release"));
msg_id_id(attributedString, s("initWithString:attributes:"), str(title), dictionary);
msg_reg(attributedString, s("autorelease"));
msg_reg(dictionary, s("autorelease"));
return attributedString;
}
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate) {
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate, JsonNode* styledLabel) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
msg_id(item, s("setRepresentedObject:"), wrappedId);
if( !alternate ) {
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
} else {
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
}
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
msg_id(item, s("setToolTip:"), str(tooltip));
}
// Process image
if( image != NULL && strlen(image) > 0) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
if( templateImage ) {
msg(nsimage, s("setTemplate:"), YES);
}
msg(item, s("setImage:"), nsimage);
id nsimage = createImageFromBase64Data(image, templateImage);
msg_id(item, s("setImage:"), nsimage);
}
id attributedString = createAttributedString(title, fontName, fontSize, RGBA);
msg(item, s("setAttributedTitle:"), attributedString);
id attributedString = NULL;
if( styledLabel != NULL) {
attributedString = createAttributedStringFromStyledLabel(styledLabel, fontName, fontSize);
} else {
attributedString = createAttributedString(title, fontName, fontSize, RGBA);
}
msg_id(item, s("setAttributedTitle:"), attributedString);
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
//msg_id(item, s("setTitle:"), str(title));
msg_bool(item, s("setEnabled:"), !disabled);
msg_reg(item, s("autorelease"));
// Process modifiers
if( modifiers != NULL && !alternate) {
unsigned long modifierFlags = parseModifiers(modifiers);
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
((id(*)(id, SEL, unsigned long))objc_msgSend)(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
}
// alternate
if( alternate ) {
msg(item, s("setAlternate:"), true);
msg(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
msg_bool(item, s("setAlternate:"), true);
msg_int(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
}
msg(parentMenu, s("addItem:"), item);
msg_id(parentMenu, s("addItem:"), item);
return item;
}
@@ -703,38 +797,6 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
return;
}
// Check if this is a submenu
JsonNode *submenu = json_find_member(item, "SubMenu");
if( submenu != NULL ) {
// Get the label
JsonNode *menuNameNode = json_find_member(item, "Label");
const char *name = "";
if ( menuNameNode != NULL) {
name = menuNameNode->string_;
}
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
id thisMenu = createMenu(str(name));
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
msg(parentMenu, s("addItem:"), thisMenuItem);
JsonNode *submenuItems = json_find_member(submenu, "Items");
// If we have no items, just return
if ( submenuItems == NULL ) {
return;
}
// Loop over submenu items
JsonNode *item;
json_foreach(item, submenuItems) {
// Get item label
processMenuItem(menu, thisMenu, item);
}
return;
}
// This is a user menu. Get the common data
// Get the label
const char *label = getJSONString(item, "Label");
@@ -742,6 +804,8 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
label = "(empty)";
}
// Check for a styled label
JsonNode *styledLabel = getJSONObject(item, "StyledLabel");
// Is this an alternate menu item?
bool alternate = false;
@@ -800,9 +864,36 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
// Get the Type
JsonNode *type = json_find_member(item, "Type");
if( type != NULL ) {
if( STREQ(type->string_, "Text") || STREQ(type->string_, "Submenu")) {
id thisMenuItem = processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate, styledLabel);
if( STREQ(type->string_, "Text")) {
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate);
// Check if this node has a submenu
JsonNode *submenu = json_find_member(item, "SubMenu");
if( submenu != NULL ) {
// Get the label
JsonNode *menuNameNode = json_find_member(item, "Label");
const char *name = "";
if ( menuNameNode != NULL) {
name = menuNameNode->string_;
}
id thisMenu = createMenu(str(name));
msg_id(thisMenuItem, s("setSubmenu:"), thisMenu);
JsonNode *submenuItems = json_find_member(submenu, "Items");
// If we have no items, just return
if ( submenuItems == NULL ) {
return;
}
// Loop over submenu items
JsonNode *item;
json_foreach(item, submenuItems) {
// Get item label
processMenuItem(menu, thisMenu, item);
}
}
}
else if ( STREQ(type->string_, "Separator")) {
addSeparator(parentMenu);
@@ -821,7 +912,6 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
}
}
if ( modifiers != NULL ) {

View File

@@ -105,12 +105,13 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate, JsonNode* styledLabel);
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
void processMenuData(Menu *menu, JsonNode *menuData);
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup);
id GetMenu(Menu *menu);
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA);
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize);
#endif //ASSETS_C_MENU_DARWIN_H

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -42,6 +42,8 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
result->disabled = false;
getJSONBool(processedJSON, "Disabled", &result->disabled);
result->styledLabel = getJSONObject(processedJSON, "StyledLabel");
// Create the menu
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
result->menu = NewMenu(processedMenu);
@@ -63,23 +65,28 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
}
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled) {
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled, JsonNode *styledLabel) {
// Exit early if NULL
if( trayMenu->label == NULL ) {
return;
}
// Update button label
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
id attributedString = createAttributedString(label, fontName, fontSize, RGBA);
if( tooltip != NULL ) {
msg(statusBarButton, s("setToolTip:"), str(tooltip));
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
id attributedString = NULL;
if( styledLabel != NULL) {
attributedString = createAttributedStringFromStyledLabel(styledLabel, fontName, fontSize);
} else {
attributedString = createAttributedString(label, fontName, fontSize, RGBA);
}
msg(statusBarButton, s("setEnabled:"), !disabled);
if( tooltip != NULL ) {
msg_id(statusBarButton, s("setToolTip:"), str(tooltip));
}
msg(statusBarButton, s("setAttributedTitle:"), attributedString);
msg_bool(statusBarButton, s("setEnabled:"), !disabled);
msg_id(statusBarButton, s("setAttributedTitle:"), attributedString);
}
void UpdateTrayIcon(TrayMenu *trayMenu) {
@@ -89,12 +96,12 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
return;
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
// Empty icon means remove it
if( STREMPTY(trayMenu->icon) ) {
// Remove image
msg(statusBarButton, s("setImage:"), NULL);
msg_id(statusBarButton, s("setImage:"), NULL);
return;
}
@@ -102,18 +109,11 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
// If we don't have the image in the icon cache then assume it's base64 encoded image data
if (trayImage == NULL) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(trayMenu->icon), 0);
trayImage = ALLOC("NSImage");
msg(trayImage, s("initWithData:"), imageData);
if( trayMenu->templateImage ) {
msg(trayImage, s("setTemplate:"), YES);
}
trayImage = createImageFromBase64Data(trayMenu->icon, trayMenu->templateImage);
}
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
msg(statusBarButton, s("setImage:"), trayImage);
msg_int(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
msg_id(statusBarButton, s("setImage:"), trayImage);
}
@@ -121,33 +121,32 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
// Create a status bar item if we don't have one
if( trayMenu->statusbaritem == NULL ) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg(trayMenu->statusbaritem, s("retain"));
id statusBar = msg_reg( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = ((id(*)(id, SEL, CGFloat))objc_msgSend)(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg_reg(trayMenu->statusbaritem, s("retain"));
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
msg_uint(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
// Update the icon if needed
UpdateTrayIcon(trayMenu);
// Update the label if needed
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled);
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled, trayMenu->styledLabel);
// Update the menu
id menu = GetMenu(trayMenu->menu);
objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN);
// Create delegate
id trayMenuDelegate = msg((id)trayMenuDelegateClass, s("new"));
msg(menu, s("setDelegate:"), trayMenuDelegate);
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
id trayMenuDelegate = msg_reg((id)trayMenuDelegateClass, s("new"));
msg_id(menu, s("setDelegate:"), trayMenuDelegate);
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
// Create menu delegate
trayMenu->delegate = trayMenuDelegate;
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
msg_id(trayMenu->statusbaritem, s("setMenu:"), menu);
}
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
@@ -169,6 +168,7 @@ void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
// Copy the other data
currentMenu->ID = newMenu->ID;
currentMenu->label = newMenu->label;
currentMenu->styledLabel = newMenu->styledLabel;
currentMenu->trayIconPosition = newMenu->trayIconPosition;
currentMenu->icon = newMenu->icon;
@@ -189,14 +189,14 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
// Free the status item
if ( trayMenu->statusbaritem != NULL ) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
msg(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
msg(trayMenu->statusbaritem, s("release"));
id statusBar = msg_reg( c("NSStatusBar"), s("systemStatusBar") );
msg_id(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
msg_reg(trayMenu->statusbaritem, s("release"));
trayMenu->statusbaritem = NULL;
}
if ( trayMenu->delegate != NULL ) {
msg(trayMenu->delegate, s("release"));
msg_reg(trayMenu->delegate, s("release"));
}
// Free the tray menu memory
@@ -228,9 +228,9 @@ void LoadTrayIcons() {
int length = atoi((const char *)lengthAsString);
// Create the icon and add to the hashmap
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
id imageData = ((id(*)(id, SEL, id, int))objc_msgSend)(c("NSData"), s("dataWithBytes:length:"), (id)data, length);
id trayImage = ALLOC("NSImage");
msg(trayImage, s("initWithData:"), imageData);
msg_id(trayImage, s("initWithData:"), imageData);
hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage);
}
}

View File

@@ -25,10 +25,12 @@ typedef struct {
Menu* menu;
id statusbaritem;
int trayIconPosition;
unsigned int trayIconPosition;
JsonNode* processedJSON;
JsonNode* styledLabel;
id delegate;
} TrayMenu;
@@ -38,7 +40,7 @@ void DumpTrayMenu(TrayMenu* trayMenu);
void ShowTrayMenu(TrayMenu* trayMenu);
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
void UpdateTrayIcon(TrayMenu *trayMenu);
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled);
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled, JsonNode *styledLabel);
void LoadTrayIcons();
void UnloadTrayIcons();

View File

@@ -127,7 +127,9 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
bool disabled = false;
getJSONBool(parsedUpdate, "Disabled", &disabled);
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled);
JsonNode *styledLabel = getJSONObject(parsedUpdate, "StyledLabel");
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled, styledLabel);
}

View File

@@ -1,4 +1,6 @@
/**
// +build !windows
/**
* Copyright (c) 2014 rxi
*
* This library is free software; you can redistribute it and/or modify it

View File

@@ -1,4 +1,4 @@
/**
/**
* Copyright (c) 2014 rxi
*
* This library is free software; you can redistribute it and/or modify it

View File

@@ -0,0 +1,68 @@
/* this ALWAYS GENERATED file contains the definitions for the interfaces */
/* File created by MIDL compiler version 8.01.0622 */
/* @@MIDL_FILE_HEADING( ) */
/* verify that the <rpcndr.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCNDR_H_VERSION__
#define __REQUIRED_RPCNDR_H_VERSION__ 500
#endif
/* verify that the <rpcsal.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCSAL_H_VERSION__
#define __REQUIRED_RPCSAL_H_VERSION__ 100
#endif
#include "rpc.h"
#include "rpcndr.h"
#ifndef __RPCNDR_H_VERSION__
#error this stub requires an updated version of <rpcndr.h>
#endif /* __RPCNDR_H_VERSION__ */
#ifndef __eventtoken_h__
#define __eventtoken_h__
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
/* Forward Declarations */
#ifdef __cplusplus
extern "C"{
#endif
/* interface __MIDL_itf_eventtoken_0000_0000 */
/* [local] */
// Microsoft Windows
// Copyright (c) Microsoft Corporation. All rights reserved.
#pragma once
typedef struct EventRegistrationToken
{
__int64 value;
} EventRegistrationToken;
extern RPC_IF_HANDLE __MIDL_itf_eventtoken_0000_0000_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_eventtoken_0000_0000_v0_0_s_ifspec;
/* Additional Prototypes for ALL interfaces */
/* end of Additional Prototypes */
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1 @@
These files were generated using the scripts in the [webview](https://github.com/webview/webview) project and compressed using UPX.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.19)
project(test)
set(CMAKE_CXX_STANDARD 14)
add_executable(test main.cpp)

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,11 @@
// +build windows
package x64
import _ "embed"
//go:embed webview.dll
var WebView2 []byte
//go:embed WebView2Loader.dll
var WebView2Loader []byte

View File

@@ -0,0 +1,9 @@
// +build !windows
// This is a stub to define the following even though they don't exist
// on non-Windows systems. Note: This is not the right way to handle this.
package x64
var WebView2 []byte
var WebView2Loader []byte

View File

@@ -0,0 +1,94 @@
#ifndef WV2COMHANDLER_H
#define WV2COMHANDLER_H
#include "ffenestri_windows.h"
#include "windows/WebView2.h"
#include <locale>
#include <codecvt>
class wv2ComHandler
: public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
public ICoreWebView2WebMessageReceivedEventHandler,
public ICoreWebView2PermissionRequestedEventHandler
{
struct Application *app;
HWND window;
messageCallback mcb;
comHandlerCallback cb;
public:
wv2ComHandler(struct Application *app, HWND window, messageCallback mcb, comHandlerCallback cb) {
this->app = app;
this->window = window;
this->mcb = mcb;
this->cb = cb;
}
ULONG STDMETHODCALLTYPE AddRef() { return 1; }
ULONG STDMETHODCALLTYPE Release() { return 1; }
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
ICoreWebView2Environment *env) {
env->CreateCoreWebView2Controller(window, this);
return S_OK;
}
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
ICoreWebView2Controller *controller) {
controller->AddRef();
ICoreWebView2 *webview;
::EventRegistrationToken token;
controller->get_CoreWebView2(&webview);
webview->add_WebMessageReceived(this, &token);
webview->add_PermissionRequested(this, &token);
cb(controller);
return S_OK;
}
// This is called when JS posts a message back to webkit
HRESULT STDMETHODCALLTYPE Invoke(
ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) {
LPWSTR message;
args->TryGetWebMessageAsString(&message);
if ( message == nullptr ) {
return S_OK;
}
const char *m = LPWSTRToCstr(message);
// check for internal messages
if (strcmp(m, "completed") == 0) {
completed(app);
return S_OK;
}
else if (strcmp(m, "initialised") == 0) {
loadAssets(app);
return S_OK;
}
else {
messageFromWindowCallback(m);
}
delete[] m;
return S_OK;
}
HRESULT STDMETHODCALLTYPE
Invoke(ICoreWebView2 *sender,
ICoreWebView2PermissionRequestedEventArgs *args) {
printf("DDDDDDDDDDDD\n");
COREWEBVIEW2_PERMISSION_KIND kind;
args->get_PermissionKind(&kind);
if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
}
return S_OK;
}
};
#endif

View File

@@ -9,6 +9,8 @@ import (
"path/filepath"
"runtime"
"unsafe"
"github.com/leaanthony/slicer"
)
// LocalDirectory gets the caller's file directory
@@ -18,11 +20,45 @@ func LocalDirectory() string {
return filepath.Dir(thisFile)
}
// RelativeToCwd returns an absolute path based on the cwd
// and the given relative path
func RelativeToCwd(relativePath string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(cwd, relativePath), nil
}
// Mkdir will create the given directory
func Mkdir(dirname string) error {
return os.Mkdir(dirname, 0755)
}
// MkDirs creates the given nested directories.
// Returns error on failure
func MkDirs(fullPath string, mode ...os.FileMode) error {
var perms os.FileMode
perms = 0755
if len(mode) == 1 {
perms = mode[0]
}
return os.MkdirAll(fullPath, perms)
}
// MoveFile attempts to move the source file to the target
// Target is a fully qualified path to a file *name*, not a
// directory
func MoveFile(source string, target string) error {
return os.Rename(source, target)
}
// DeleteFile will delete the given file
func DeleteFile(filename string) error {
return os.Remove(filename)
}
// CopyFile from source to target
func CopyFile(source string, target string) error {
s, err := os.Open(source)
@@ -144,3 +180,96 @@ func fatal(message ...string) {
}
os.Exit(1)
}
// GetSubdirectories returns a list of subdirectories for the given root directory
func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
var result slicer.StringSlicer
// Iterate root dir
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// If we have a directory, save it
if info.IsDir() {
result.Add(path)
}
return nil
})
return &result, err
}
func DirIsEmpty(dir string) (bool, error) {
// CREDIT: https://stackoverflow.com/a/30708914/8325411
f, err := os.Open(dir)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1) // Or f.Readdir(1)
if err == io.EOF {
return true, nil
}
return false, err // Either not empty or error, suits both cases
}
// Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist, destination directory must *not* exist.
// Symlinks are ignored and skipped.
func CopyDir(src string, dst string) (err error) {
src = filepath.Clean(src)
dst = filepath.Clean(dst)
si, err := os.Stat(src)
if err != nil {
return err
}
if !si.IsDir() {
return fmt.Errorf("source is not a directory")
}
_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
return
}
if err == nil {
return fmt.Errorf("destination already exists")
}
err = MkDirs(dst)
if err != nil {
return
}
entries, err := ioutil.ReadDir(src)
if err != nil {
return
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
err = CopyDir(srcPath, dstPath)
if err != nil {
return
}
} else {
// Skip symlinks.
if entry.Mode()&os.ModeSymlink != 0 {
continue
}
err = CopyFile(srcPath, dstPath)
if err != nil {
return
}
}
}
return
}

View File

@@ -3,11 +3,15 @@ package html
import (
"fmt"
"io/ioutil"
"log"
"net/url"
"path/filepath"
"regexp"
"strings"
"unsafe"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/js"
)
type assetTypes struct {
@@ -48,26 +52,22 @@ func (a *Asset) AsString() string {
return a.Data
}
// AsCHexData processes the asset data so it may be used by C
func (a *Asset) AsCHexData() string {
// This will be our final string to hexify
dataString := a.Data
func (a *Asset) minifiedData() (string, error) {
switch a.Type {
case AssetTypes.HTML:
// Escape HTML
var re = regexp.MustCompile(`\s{2,}`)
result := re.ReplaceAllString(a.Data, ``)
result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\r\n", "")
result = strings.ReplaceAll(result, "\n", "")
var re = regexp.MustCompile(`[\s]+`)
result := re.ReplaceAllString(a.Data, ` `)
// Inject wailsloader code
result = strings.Replace(result, `</body>`, `<script id='wailsloader'>window.wailsloader = { html: true, runtime: false, userjs: false, usercss: false };var self=document.querySelector('#wailsloader');self.parentNode.removeChild(self);</script></body>`, 1)
url := url.URL{Path: result}
urlString := strings.ReplaceAll(url.String(), "/", "%2f")
// Save Data uRI string
dataString = "data:text/html;charset=utf-8," + urlString
return "data:text/html;charset=utf-8," + urlString, nil
case AssetTypes.CSS:
@@ -83,9 +83,28 @@ func (a *Asset) AsCHexData() string {
result = strings.ReplaceAll(result, `'`, `\'`)
result = strings.ReplaceAll(result, ` {`, `{`)
result = strings.ReplaceAll(result, `: `, `:`)
dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result)
}
return fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result), nil
case AssetTypes.JS:
m := minify.New()
m.AddFunc("application/javascript", js.Minify)
var err error
result, err := m.String("application/javascript", a.Data+";")
if err != nil {
return "", err
}
return result, nil
default:
return "", fmt.Errorf("minification for asset type %s not implemented", a.Type)
}
}
// AsCHexData processes the asset data so it may be used by C
func (a *Asset) AsCHexData() string {
dataString, err := a.minifiedData()
if err != nil {
log.Fatal(err)
}
// Get byte data of the string
bytes := *(*[]byte)(unsafe.Pointer(&dataString))
@@ -103,6 +122,7 @@ func (a *Asset) AsCHexData() string {
return cdata.String()
}
// Dump will output the asset to the terminal
func (a *Asset) Dump() {
fmt.Printf("{ Type: %s, Path: %s, Data: %+v }\n", a.Type, a.Path, a.Data[:10])
}

View File

@@ -0,0 +1,53 @@
package html
import "testing"
func TestAsset_minifiedData(t *testing.T) {
type fields struct {
Type string
Path string
Data string
}
tests := []struct {
name string
fields fields
want string
wantErr bool
}{
{
name: "multi-line tag",
fields: fields{
Type: AssetTypes.HTML,
Path: "foo.html",
Data: "<link\n rel=\"stylesheet\"\n href=\"src/foo.css\"\n>\n",
},
want: "data:text/html;charset=utf-8,%3Clink%20rel=%22stylesheet%22%20href=%22src%2ffoo.css%22%20%3E%20",
},
{
name: "multi-line tag no spaces",
fields: fields{
Type: AssetTypes.HTML,
Path: "foo.html",
Data: "<link\nrel=\"stylesheet\"\nhref=\"src/foo.css\"\n>\n",
},
want: "data:text/html;charset=utf-8,%3Clink%20rel=%22stylesheet%22%20href=%22src%2ffoo.css%22%20%3E%20",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Asset{
Type: tt.fields.Type,
Path: tt.fields.Path,
Data: tt.fields.Data,
}
got, err := a.minifiedData()
if (err != nil) != tt.wantErr {
t.Errorf("Asset.minifiedData() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Asset.minifiedData() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -61,6 +61,8 @@ func (a *AssetBundle) processHTML(htmldata string) error {
buf := bytes.NewBufferString(htmldata)
tokenizer := html.NewTokenizer(buf)
paths := slicer.String()
for {
//get the next token type
tokenType := tokenizer.Next()
@@ -82,7 +84,7 @@ func (a *AssetBundle) processHTML(htmldata string) error {
}
//process the token according to the token type...
if tokenType == html.StartTagToken {
if tokenType == html.StartTagToken || tokenType == html.SelfClosingTagToken {
//get the token
token := tokenizer.Token()
@@ -100,19 +102,32 @@ func (a *AssetBundle) processHTML(htmldata string) error {
if attr.Key == "href" {
asset.Path = attr.Val
}
// stylesheet
// standard stylesheet
if attr.Key == "rel" && attr.Val == "stylesheet" {
asset.Type = AssetTypes.CSS
}
if attr.Key == "as" && attr.Val == "style" {
asset.Type = AssetTypes.CSS
}
if attr.Key == "as" && attr.Val == "script" {
asset.Type = AssetTypes.JS
}
if attr.Key == "rel" && attr.Val == "modulepreload" {
asset.Type = AssetTypes.JS
}
}
err := asset.Load(a.basedirectory)
if err != nil {
return err
// Ensure we don't include duplicates
if !paths.Contains(asset.Path) {
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
paths.Add(asset.Path)
}
a.assets = append(a.assets, asset)
}
if "script" == token.Data {
tokenType = tokenizer.Next()
//just make sure it's actually a text token
asset := &Asset{Type: AssetTypes.JS}
@@ -122,11 +137,14 @@ func (a *AssetBundle) processHTML(htmldata string) error {
break
}
}
err := asset.Load(a.basedirectory)
if err != nil {
return err
if !paths.Contains(asset.Path) {
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
paths.Add(asset.Path)
}
a.assets = append(a.assets, asset)
}
}
}
@@ -141,7 +159,7 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
var cdata strings.Builder
// Write header
header := `// assets.c
header := `// assets.h
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL.
// This file was auto-generated. DO NOT MODIFY.
@@ -153,6 +171,10 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
assetVariables := slicer.String()
var variableName string
for index, asset := range a.assets {
// For desktop we ignore the favicon
if asset.Type == AssetTypes.FAVICON {
continue
}
variableName = fmt.Sprintf("%s%d", asset.Type, index)
assetCdata := fmt.Sprintf("const unsigned char %s[]={ %s0x00 };\n", variableName, asset.AsCHexData())
cdata.WriteString(assetCdata)
@@ -160,13 +182,13 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
}
if assetVariables.Length() > 0 {
cdata.WriteString(fmt.Sprintf("\nconst char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
cdata.WriteString(fmt.Sprintf("\nconst unsigned char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
} else {
cdata.WriteString("\nconst char *assets[] = { 0x00 };")
cdata.WriteString("\nconst unsigned char *assets[] = { 0x00 };")
}
// Save file
assetsFile := filepath.Join(targetDir, "assets.c")
assetsFile := filepath.Join(targetDir, "assets.h")
err = ioutil.WriteFile(assetsFile, []byte(cdata.String()), 0600)
if err != nil {
return "", err
@@ -177,16 +199,17 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
// ConvertToAssetDB returns an assetdb.AssetDB initialized with
// the items in the AssetBundle
func (a *AssetBundle) ConvertToAssetDB() (*assetdb.AssetDB, error) {
assetdb := assetdb.NewAssetDB()
theassetdb := assetdb.NewAssetDB()
// Loop over the Assets
for _, asset := range a.assets {
assetdb.AddAsset(asset.Path, []byte(asset.Data))
theassetdb.AddAsset(asset.Path, []byte(asset.Data))
}
return assetdb, nil
return theassetdb, nil
}
// Dump will output the assets to the terminal
func (a *AssetBundle) Dump() {
println("Assets:")
for _, asset := range a.assets {

View File

@@ -0,0 +1,73 @@
package html
import (
"testing"
)
func TestNewAssetBundle(t *testing.T) {
tests := []struct {
name string
pathToHTML string
wantAssets []string
wantErr bool
}{
{
name: "basic html",
pathToHTML: "testdata/basic.html",
wantAssets: []string{
AssetTypes.HTML,
AssetTypes.FAVICON,
AssetTypes.JS,
AssetTypes.CSS,
},
wantErr: false,
},
{
name: "self closing tags",
pathToHTML: "testdata/self_closing.html",
wantAssets: []string{
AssetTypes.HTML,
AssetTypes.FAVICON,
AssetTypes.JS,
AssetTypes.CSS,
},
wantErr: false,
},
{
name: "multi-line tags",
pathToHTML: "testdata/self_closing.html",
wantAssets: []string{
AssetTypes.HTML,
AssetTypes.FAVICON,
AssetTypes.JS,
AssetTypes.CSS,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewAssetBundle(tt.pathToHTML)
if (err != nil) != tt.wantErr {
t.Errorf("NewAssetBundle() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got.assets) != len(tt.wantAssets) {
t.Errorf("NewAssetBundle() len(assets) = %d, want %d",
len(got.assets), len(tt.wantAssets))
}
for i := range tt.wantAssets {
if i >= len(got.assets) {
t.Errorf("NewAssetBundle() missing assets[%d].Type = %s",
i, tt.wantAssets[i])
} else {
if got.assets[i].Type != tt.wantAssets[i] {
t.Errorf("NewAssetBundle() assets[%d].Type = %s, want %s",
i, got.assets[i].Type, tt.wantAssets[i])
}
}
}
})
}
}

14
v2/internal/html/testdata/basic.html vendored Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg"></link>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script type="text/javascript" src="src/bundle.js"></script>
<link rel="stylesheet" href="src/style.css"></link>
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script type="text/javascript" src="src/bundle.js"></script>
<link
rel="stylesheet"
href="src/style.css"
/>
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script type="text/javascript" src="src/bundle.js"></script>
<link rel="stylesheet" href="src/style.css" />
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1 @@
window.alert("I am JS!");

View File

@@ -0,0 +1,8 @@
<svg version="1.1"
baseProfile="full"
width="300" height="200"
xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="blue" />
</svg>

After

Width:  |  Height:  |  Size: 172 B

View File

@@ -0,0 +1,3 @@
.body {
background: blue;
}

View File

@@ -2,31 +2,30 @@ package logger
import (
"fmt"
"os"
)
// CustomLogger defines what a user can do with a logger
type CustomLogger interface {
// Writeln writes directly to the output with no log level plus line ending
Writeln(message string) error
Writeln(message string)
// Write writes directly to the output with no log level
Write(message string) error
Write(message string)
// Trace level logging. Works like Sprintf.
Trace(format string, args ...interface{}) error
Trace(format string, args ...interface{})
// Debug level logging. Works like Sprintf.
Debug(format string, args ...interface{}) error
Debug(format string, args ...interface{})
// Info level logging. Works like Sprintf.
Info(format string, args ...interface{}) error
Info(format string, args ...interface{})
// Warning level logging. Works like Sprintf.
Warning(format string, args ...interface{}) error
Warning(format string, args ...interface{})
// Error level logging. Works like Sprintf.
Error(format string, args ...interface{}) error
Error(format string, args ...interface{})
// Fatal level logging. Works like Sprintf.
Fatal(format string, args ...interface{})
@@ -50,49 +49,48 @@ func newcustomLogger(logger *Logger, name string) *customLogger {
// Writeln writes directly to the output with no log level
// Appends a carriage return to the message
func (l *customLogger) Writeln(message string) error {
return l.logger.Writeln(message)
func (l *customLogger) Writeln(message string) {
l.logger.Writeln(message)
}
// Write writes directly to the output with no log level
func (l *customLogger) Write(message string) error {
return l.logger.Write(message)
func (l *customLogger) Write(message string) {
l.logger.Write(message)
}
// Trace level logging. Works like Sprintf.
func (l *customLogger) Trace(format string, args ...interface{}) error {
func (l *customLogger) Trace(format string, args ...interface{}) {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(TRACE, format, args...)
l.logger.Trace(format, args...)
}
// Debug level logging. Works like Sprintf.
func (l *customLogger) Debug(format string, args ...interface{}) error {
func (l *customLogger) Debug(format string, args ...interface{}) {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(DEBUG, format, args...)
l.logger.Debug(format, args...)
}
// Info level logging. Works like Sprintf.
func (l *customLogger) Info(format string, args ...interface{}) error {
func (l *customLogger) Info(format string, args ...interface{}) {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(INFO, format, args...)
l.logger.Info(format, args...)
}
// Warning level logging. Works like Sprintf.
func (l *customLogger) Warning(format string, args ...interface{}) error {
func (l *customLogger) Warning(format string, args ...interface{}) {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(WARNING, format, args...)
l.logger.Warning(format, args...)
}
// Error level logging. Works like Sprintf.
func (l *customLogger) Error(format string, args ...interface{}) error {
func (l *customLogger) Error(format string, args ...interface{}) {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(ERROR, format, args...)
l.logger.Error(format, args...)
}
// Fatal level logging. Works like Sprintf.
func (l *customLogger) Fatal(format string, args ...interface{}) {
format = fmt.Sprintf("%s | %s", l.name, format)
l.logger.processLogMessage(FATAL, format, args...)
os.Exit(1)
l.logger.Fatal(format, args...)
}

View File

@@ -2,37 +2,32 @@ package logger
import (
"fmt"
"io"
"os"
"sync"
"github.com/wailsapp/wails/v2/pkg/logger"
)
// LogLevel is an alias for the public LogLevel
type LogLevel = logger.LogLevel
// Logger is a utlility to log messages to a number of destinations
type Logger struct {
writers []io.Writer
logLevel uint8
output logger.Logger
logLevel LogLevel
showLevelInLog bool
lock sync.RWMutex
}
// New creates a new Logger. You may pass in a number of `io.Writer`s that
// are the targets for the logs
func New(writers ...io.Writer) *Logger {
func New(output logger.Logger) *Logger {
result := &Logger{
logLevel: INFO,
logLevel: logger.INFO,
showLevelInLog: true,
}
for _, writer := range writers {
result.AddOutput(writer)
output: output,
}
return result
}
// Writers gets the log writers
func (l *Logger) Writers() []io.Writer {
return l.writers
}
// CustomLogger creates a new custom logger that prints out a name/id
// before the messages
func (l *Logger) CustomLogger(name string) CustomLogger {
@@ -45,99 +40,66 @@ func (l *Logger) HideLogLevel() {
}
// SetLogLevel sets the minimum level of logs that will be output
func (l *Logger) SetLogLevel(level uint8) {
func (l *Logger) SetLogLevel(level LogLevel) {
l.logLevel = level
}
// AddOutput adds the given `io.Writer` to the list of destinations
// that get logged to
func (l *Logger) AddOutput(writer io.Writer) {
l.writers = append(l.writers, writer)
}
func (l *Logger) write(loglevel uint8, message string) error {
// Don't print logs lower than the current log level
if loglevel < l.logLevel {
return nil
}
// Show log level text if enabled
if l.showLevelInLog {
message = mapLogLevel[loglevel] + message
}
// write out the logs
l.lock.Lock()
for _, writer := range l.writers {
_, err := writer.Write([]byte(message))
if err != nil {
l.lock.Unlock() // Because defer is slow
return err
}
}
l.lock.Unlock()
return nil
}
// writeln appends a newline character to the message before writing
func (l *Logger) writeln(loglevel uint8, message string) error {
return l.write(loglevel, message+"\n")
}
// Writeln writes directly to the output with no log level
// Appends a carriage return to the message
func (l *Logger) Writeln(message string) error {
return l.write(BYPASS, message+"\n")
func (l *Logger) Writeln(message string) {
l.output.Print(message)
}
// Write writes directly to the output with no log level
func (l *Logger) Write(message string) error {
return l.write(BYPASS, message)
func (l *Logger) Write(message string) {
l.output.Print(message)
}
// processLogMessage formats the given message before writing it out
func (l *Logger) processLogMessage(loglevel uint8, format string, args ...interface{}) error {
message := fmt.Sprintf(format, args...)
return l.writeln(loglevel, message)
// Print writes directly to the output with no log level
// Appends a carriage return to the message
func (l *Logger) Print(message string) {
l.Write(message)
}
// Trace level logging. Works like Sprintf.
func (l *Logger) Trace(format string, args ...interface{}) error {
return l.processLogMessage(TRACE, format, args...)
}
// CustomTrace returns a custom Logging function that will insert the given name before the message
func (l *Logger) CustomTrace(name string) func(format string, args ...interface{}) {
return func(format string, args ...interface{}) {
format = name + " | " + format
l.processLogMessage(TRACE, format, args...)
func (l *Logger) Trace(format string, args ...interface{}) {
if l.logLevel <= logger.TRACE {
l.output.Trace(fmt.Sprintf(format, args...))
}
}
// Debug level logging. Works like Sprintf.
func (l *Logger) Debug(format string, args ...interface{}) error {
return l.processLogMessage(DEBUG, format, args...)
func (l *Logger) Debug(format string, args ...interface{}) {
if l.logLevel <= logger.DEBUG {
l.output.Debug(fmt.Sprintf(format, args...))
}
}
// Info level logging. Works like Sprintf.
func (l *Logger) Info(format string, args ...interface{}) error {
return l.processLogMessage(INFO, format, args...)
func (l *Logger) Info(format string, args ...interface{}) {
if l.logLevel <= logger.INFO {
l.output.Info(fmt.Sprintf(format, args...))
}
}
// Warning level logging. Works like Sprintf.
func (l *Logger) Warning(format string, args ...interface{}) error {
return l.processLogMessage(WARNING, format, args...)
func (l *Logger) Warning(format string, args ...interface{}) {
if l.logLevel <= logger.WARNING {
l.output.Warning(fmt.Sprintf(format, args...))
}
}
// Error level logging. Works like Sprintf.
func (l *Logger) Error(format string, args ...interface{}) error {
return l.processLogMessage(ERROR, format, args...)
func (l *Logger) Error(format string, args ...interface{}) {
if l.logLevel <= logger.ERROR {
l.output.Error(fmt.Sprintf(format, args...))
}
}
// Fatal level logging. Works like Sprintf.
func (l *Logger) Fatal(format string, args ...interface{}) {
l.processLogMessage(FATAL, format, args...)
l.output.Fatal(fmt.Sprintf(format, args...))
os.Exit(1)
}

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