Compare commits

...

107 Commits

Author SHA1 Message Date
Lea Anthony
a020b67f67 [website] Fix desktop icon 2021-11-08 19:10:31 +11:00
Lea Anthony
fa958e7a07 [website] assetdir clarification 2021-11-08 17:38:30 +11:00
Lea Anthony
1a3e81a3f8 [website] Misc updates 2021-11-08 17:36:08 +11:00
Lea Anthony
0eb7a8a771 [v2] Update svelte template 2021-11-08 07:00:01 +11:00
Lea Anthony
2fa004808f [website] Update options example 2021-11-08 06:31:34 +11:00
Lea Anthony
cc5fd30256 [v2] Update vanilla template 2021-11-08 06:31:13 +11:00
Lea Anthony
c90bfc310a [mac] Fix lifecycle hooks 2021-11-06 10:40:02 +11:00
Lea Anthony
62d1d621aa [mac] Tidy up 2021-11-04 20:58:08 +11:00
Lea Anthony
6e8cbb8e8f [mac] Ensure minimum osx version 2021-11-04 20:45:22 +11:00
Lea Anthony
32fa543164 [website] Update docs 2021-11-03 19:25:42 +11:00
Lea Anthony
04f93ac54e [v2] Update go.mod 2021-11-03 19:24:12 +11:00
Lea Anthony
3c87d13b21 [mac] Fix fullscreen 2021-11-03 19:23:23 +11:00
Lea Anthony
0949eab72e [linux] add flag 2021-11-03 19:22:59 +11:00
Lea Anthony
aab67b416f [mac] add default menu 2021-11-03 19:22:38 +11:00
Lea Anthony
83a575e43f [v2] warn if wails version out of sync during build 2021-11-02 22:33:34 +11:00
Lea Anthony
333949ee53 [mac] better output text on build 2021-11-02 22:20:44 +11:00
Lea Anthony
1d1238cea3 [website] add cross compile 2021-11-02 22:19:59 +11:00
Lea Anthony
bd7b436631 [mac] Add fallback for app name 2021-11-02 22:05:42 +11:00
Lea Anthony
c136df48b9 [mac] Fix App Name in app menu 2021-11-02 22:04:06 +11:00
Lea Anthony
a090a689cf [mac] Fix plist generation 2021-11-02 21:56:35 +11:00
Lea Anthony
5ef2448a0c [website] updates 2021-11-02 21:00:56 +11:00
Lea Anthony
06ab4c88ad [website] updates 2021-11-02 20:54:42 +11:00
Lea Anthony
48efdea11a [website] updates 2021-11-02 20:54:13 +11:00
Lea Anthony
43cc55cb0a [mac] Small tweaks 2021-11-02 20:06:59 +11:00
Lea Anthony
71f2436562 [website] Add Mac options 2021-11-02 08:31:21 +11:00
Lea Anthony
4653c77a81 Merge branch 'master' into v2-mac-docs 2021-11-02 08:20:20 +11:00
Lea Anthony
72b05c6b44 Merge pull request #907 from phoenix147/appargs
Appargs
2021-11-02 07:23:00 +11:00
Lukas Crepaz
b5f68e24d6 added appargs for application arguments in dev mode 2021-11-01 08:57:10 +01:00
Lukas Crepaz
3948c8ca61 use environment variables to supply the development binary with flags to support CLI arguments in the app 2021-10-31 09:12:22 +01:00
Lea Anthony
cf3a868e3a [mac] Support MenuUpdateApplicationMenu 2021-10-31 15:09:50 +11:00
Lea Anthony
43c29abb23 Merge pull request #901 from misitebao/synchronize-chinese-documents
docs: synchronize chinese documents
2021-10-31 08:55:22 +11:00
Lea Anthony
7ef445f526 [mac] Improve string/memory handling, dialog icon -> []byte 2021-10-31 08:50:14 +11:00
misitebao
f6c2d4ae6b docs: synchronize all chinese documents 2021-10-31 04:32:38 +08:00
misitebao
8f9fae6ad9 docs: synchronize chinese readme logo size 2021-10-31 03:02:00 +08:00
misitebao
b45f264e2a docs: synchronize chinese readme 2021-10-31 02:55:01 +08:00
misitebao
986f8f48c7 docs: organize the readme 2021-10-31 02:54:22 +08:00
misitebao
bbc2e86286 docs: update chinese readme 2021-10-31 02:27:47 +08:00
Lea Anthony
2dc126bf19 [mac] Fix ExecJS 2021-10-30 19:28:25 +11:00
Lea Anthony
86cbcdc089 [mac] Move ops to main thread 2021-10-30 17:19:58 +11:00
Lea Anthony
1dd957f461 [mac] Fix SetPosition 2021-10-30 16:31:06 +11:00
Lea Anthony
4be4946756 [mac] Fix SetMaxSize 2021-10-30 11:07:07 +11:00
Lea Anthony
65979cbc75 Merge pull request #900 from misitebao/synchronize-chinese-documents
docs: synchronize chinese documents
2021-10-30 10:57:05 +11:00
Lea Anthony
6a7118ff6d [mac] Support cross compiling to windows 2021-10-30 10:44:22 +11:00
Lea Anthony
a88b3553ba [mac] Support min/max 2021-10-30 10:34:55 +11:00
Lea Anthony
fd5348d26d [v2] Fix build output 2021-10-30 10:33:30 +11:00
Lea Anthony
569569f1fc [mac] support amd/arm/universal 2021-10-30 10:19:49 +11:00
Lea Anthony
489b9b358b [mac] menu support 2021-10-30 09:51:46 +11:00
misitebao
71cfdfc7c8 feat: increase synchronized content 2021-10-29 03:15:42 +08:00
misitebao
6ebf4ed428 feat: synchronize some chinese documents 2021-10-29 03:15:04 +08:00
misitebao
5be0739c5d feat(website): synchronize chinese credits 2021-10-28 18:45:45 +08:00
Lea Anthony
6721e59277 [v2] Fix build command for dev mode 2021-10-28 19:24:05 +11:00
Lea Anthony
77775d85ab v1.16.8 2021-10-26 19:38:53 +11:00
Lea Anthony
6de0865c3e Merge pull request #896 from wailsapp/develop
Develop
2021-10-26 19:36:48 +11:00
Lea Anthony
f9e559f069 Merge branch 'master' into develop 2021-10-26 19:35:18 +11:00
Lea Anthony
9a4c603001 [v1] Update logrus to v1.8.1 2021-10-26 19:32:53 +11:00
Lea Anthony
98a95e99a5 [mac] Fixes #879 2021-10-26 19:28:15 +11:00
Lea Anthony
d19c982eed v2.0.0-beta.15 2021-10-26 19:21:41 +11:00
Lea Anthony
a963836e75 [v2] fix: check process exists before killing 2021-10-26 19:20:39 +11:00
Lea Anthony
00e9eb4b0b [v2] fix: run frontend:dev when using wails dev 2021-10-26 19:20:09 +11:00
Lea Anthony
262b6281e1 Update sponsors. Cheers DonTomato! 2021-10-26 19:05:07 +11:00
Lea Anthony
f66c70f0be Merge pull request #893 from Wakeful-Cloud/master
Fixed WindowGetSize
2021-10-25 19:20:47 +11:00
Wakeful-Cloud
717d373668 Fixed WindowGetSize 2021-10-24 18:00:47 -06:00
Lea Anthony
d29fa94aa4 Merge pull request #891 from Wakeful-Cloud/master
Fix TypeScript runtime declaration
2021-10-24 19:11:11 +11:00
Wakeful-Cloud
01dd0cd0b2 Fix TypeScript runtime declaration 2021-10-24 04:05:47 +00:00
Lea Anthony
126cc78d1a [v2] add overscroll-behavior 2021-10-24 09:03:05 +11:00
Lea Anthony
5703d465fc [v2] v2.0.0-beta.14 2021-10-23 08:52:02 +11:00
Lea Anthony
0c2963cf53 [windows] Add webview2 permissions 2021-10-23 07:17:58 +11:00
Lea Anthony
b61fd16936 [windows] Disable swipe navigation 2021-10-23 05:50:39 +11:00
Lea Anthony
3a8ba96cb3 [windows] Update webview2 to 91.0.992.28 2021-10-23 05:50:39 +11:00
Lea Anthony
3b1d74cf84 Merge pull request #886 from TAINCER/patch-1
Added Angular template to Community templates list
2021-10-22 17:11:50 +11:00
Lea Anthony
2e0a6f95a0 [website] Update dialog docs 2021-10-22 16:59:44 +11:00
Timm Ortloff
8470bfb26b Added Angular template to Community templates list 2021-10-22 06:47:03 +02:00
Lea Anthony
bea0c1446a [mac] dialog support 2021-10-22 08:42:36 +11:00
Lea Anthony
35ebbdfa12 [v2] Fix typo in templates 2021-10-22 08:42:35 +11:00
Lea Anthony
bb25b3f42f Update events.mdx 2021-10-20 20:31:58 +11:00
Lea Anthony
4a11f9bb20 Update sponsors 2021-10-20 17:40:57 +11:00
Lea Anthony
c1a20d0509 [mac] Fix SetRGBA and disabling context menus in prod build 2021-10-19 20:08:43 +11:00
Lea Anthony
32c3721b1b [mac] Fix webviewistransparent and debug flag 2021-10-19 20:07:36 +11:00
Lea Anthony
913cc56adf [v2] Add flag to remove default context menu 2021-10-19 20:06:18 +11:00
Lea Anthony
38f37e817b [mac] Get asset server hooked up, window drag, Window runtime. 2021-10-18 22:02:23 +11:00
Lea Anthony
4e68f92083 [v2] Add WindowGetPos & WindowGetSize 2021-10-18 21:42:02 +11:00
Lea Anthony
3edbda313e [mac] add SetRGBA and basic hooks for asset serving 2021-10-17 21:50:15 +11:00
Lea Anthony
04cde94c96 [windows] add build tags to browser runtime 2021-10-17 21:50:15 +11:00
Lea Anthony
1faa962cf5 Update README.md 2021-10-16 15:47:02 +11:00
Lea Anthony
94a74520be Update logo 2021-10-16 15:45:59 +11:00
Lea Anthony
27dd40fd29 Update logos 2021-10-16 08:57:28 +11:00
Lea Anthony
616ecabb41 [mac] migrated colour code 2021-10-14 20:38:11 +11:00
Lea Anthony
15cd325034 [mac] experimental 2021-10-14 20:35:45 +11:00
Lea Anthony
450eb2e7ae [mac] message passing, quit 2021-10-14 20:34:47 +11:00
Lea Anthony
84622b829c [website] Fix build 2021-10-14 17:55:01 +11:00
Lea Anthony
a1323ce5e9 [mac] experimental 2021-10-13 22:01:35 +11:00
Lea Anthony
49629f6dc6 [mac] Fix build tags 2021-10-13 21:16:07 +11:00
Lea Anthony
231848cb9e [mac] Don't create .app in dev 2021-10-13 21:16:06 +11:00
Lea Anthony
a51d8bb47d [v2] Move "AlwaysOnTop" option 2021-10-13 08:05:31 +11:00
Lea Anthony
e0e4c0ae11 [v2] Add "AlwaysOnTop" option 2021-10-13 08:02:35 +11:00
Lea Anthony
d47b3734af [v2] v2.0.0-beta.13 2021-10-12 20:47:03 +11:00
Lea Anthony
26d248a4b6 [v2] Add flag to disable scrollbar drag 2021-10-12 20:45:53 +11:00
Lea Anthony
6413a6fb4d Update Sponsors 2021-10-12 20:35:38 +11:00
Lea Anthony
5e36f4fc7f [v2] Remove chromium message on shutdown 2021-10-12 08:58:33 +11:00
Lea Anthony
b47c278c95 Merge pull request #868 from stankovic98/add-artix-linux
add artix linux distro
2021-10-12 08:54:16 +11:00
Antonio
a94a720a68 add artix linux distro 2021-10-11 17:16:24 +02:00
Lea Anthony
3caa0f1438 v1.16.7 2021-09-03 19:15:37 +10:00
Florian Didron
b8ef90cb41 fix: prevent hidden files to show on gtk host when opening a file dialog 2021-09-03 19:12:47 +10:00
Lea Anthony
9efc648e3d Merge pull request #789 from diogox/develop
Add NixOS support
2021-09-03 18:55:53 +10:00
Diogo Xavier
baa96f47d8 Add NixOS support 2021-08-30 19:15:10 +01:00
Lea Anthony
184ce763c1 v1.16.6 2021-08-14 19:02:16 +10:00
Lea Anthony
229ee95f91 Don't build project by default. Added -build flag to wails init to mimic old behaviour 2021-08-14 19:00:35 +10:00
139 changed files with 6697 additions and 16786 deletions

173
README.md
View File

@@ -1,5 +1,5 @@
<p align="center" style="text-align: center">
<img src="logo_cropped.png" width="40%"><br/>
<img src="logo.png" width="55%"><br/>
</p>
<p align="center">
Build desktop applications using Go & Web Technologies.<br/><br/>
@@ -18,42 +18,57 @@
## Internationalization
English | [简体中文](README.zh-Hans.md)
[English](README.md) | [简体中文](README.zh-Hans.md)
<span id="nav-2"></span>
## Table of Contents
<details>
<summary>Click me to Open/Close the directory listing</summary>
- [1. Internationalization](#nav-1)
- [2. Table of Contents](#nav-2)
- [3. Introduction](#nav-3)
- [3.1 Official Website](#nav-3-1)
- [4. Features](#nav-4)
- [5. Sponsors](#nav-5)
- [6. Installation](#nav-6)
- [6.1 MacOS](#nav-6-1)
- [6.2 Linux](#nav-6-2)
- [6.2.1 Debian/Ubuntu](#nav-6-2-1)
- [6.2.2 Arch Linux / ArchLabs / Ctlos Linux](#nav-6-2-2)
- [6.2.3 Centos](#nav-6-2-3)
- [6.2.4 Fedora](#nav-6-2-4)
- [6.2.5 VoidLinux & VoidLinux-musl](#nav-6-2-5)
- [6.2.6 Gentoo](#nav-6-2-6)
- [6.3 Windows](#nav-6-3)
- [7. Usage](#nav-7)
- [7.1 Next Steps](#nav-7-1)
- [8. FAQ](#nav-8)
- [9. Contributors](#nav-9)
- [10. Special Mentions](#nav-10)
- [12. Special Thanks](#nav-11)
</details>
<span id="nav-3"></span>
## Introductions
The traditional method of providing web interfaces to Go programs is via a built-in web server. Wails offers a different
approach: it provides the ability to wrap both Go code and a web frontend into a single binary. Tools are provided to
make this easy for you by handling project creation, compilation and bundling. All you have to do is get creative!
<span id="nav-3-1"></span>
### Official Website
The official docs can be found at [https://wails.app](https://wails.app).
Click [here](https://wails.io) if you are interested in trying out v2 Beta for Windows.
<span id="nav-2"></span>
## Contents
- [1. Internationalization](#nav-1)
- [2. Contents](#nav-2)
- [3. Features](#nav-3)
- [4. Sponsors](#nav-4)
- [5. Installation](#nav-5)
- [5.1 MacOS](#nav-5-1)
- [5.2 Linux](#nav-5-2)
- [5.2.1 Debian/Ubuntu](#nav-5-2-1)
- [5.2.2 Arch Linux / ArchLabs / Ctlos Linux](#nav-5-2-2)
- [5.2.3 Centos](#nav-5-2-3)
- [5.2.4 Fedora](#nav-5-2-4)
- [5.2.5 VoidLinux & VoidLinux-musl](#nav-5-2-5)
- [5.2.6 Gentoo](#nav-5-2-6)
- [5.3 Windows](#nav-5-3)
- [6. Installation](#nav-6)
- [7. Next Steps](#nav-7)
- [8. FAQ](#nav-8)
- [9. Contributors](#nav-9)
- [10. Special Mentions](#nav-10)
- [11. Special Thanks](#nav-11)
<span id="nav-3"></span>
<span id="nav-4"></span>
## Features
@@ -67,7 +82,7 @@ Click [here](https://wails.io) if you are interested in trying out v2 Beta for W
- Powerful cli tool
- Multiplatform
<span id="nav-4"></span>
<span id="nav-5"></span>
## Sponsors
@@ -79,48 +94,49 @@ This project is supported by these kind people / companies:
<a href="https://github.com/snider" style="width:100px;">
<img src="https://github.com/snider.png?size=100" width="100"/>
</a>
<a href="https://github.com/codydbentley" style="width:100px">
<img src="https://github.com/codydbentley.png?size=100" width="100"/>
</a>
<br/>
<br/>
<a href="https://github.com/matryer" style="width:100px">
<img src="https://github.com/matryer.png" width="100"/>
</a>
<a href="https://www.jetbrains.com?from=Wails" style="width:100px">
<img src="jetbrains-grayscale.png" width="100"/>
<img src="/img/jetbrains-grayscale.png" width="100"/>
</a>
<a href="https://github.com/tc-hib" style="width:55px;border-radius: 50%">
<img src="https://github.com/tc-hib.png?size=55" width="55" style="border-radius: 50%"/>
<a href="https://github.com/tc-hib" style="width:55px">
<img src="https://github.com/tc-hib.png?size=55" width="55"/>
</a>
<a href="https://github.com/picatz" style="width:50px;border-radius: 50%">
<img src="https://github.com/picatz.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/picatz" style="width:50px">
<img src="https://github.com/picatz.png?size=50" width="50"/>
</a>
<a href="https://github.com/tylertravisty" style="width:50px;border-radius: 50%">
<img src="https://github.com/tylertravisty.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/tylertravisty" style="width:50px">
<img src="https://github.com/tylertravisty.png?size=50" width="50"/>
</a>
<a href="https://github.com/akhudek" style="width:50px;border-radius: 50%">
<img src="https://github.com/akhudek.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/akhudek" style="width:50px">
<img src="https://github.com/akhudek.png?size=50" width="50"/>
</a>
<a href="https://github.com/trea" style="width:50px;border-radius: 50%">
<img src="https://github.com/trea.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/trea" style="width:50px">
<img src="https://github.com/trea.png?size=50" width="50"/>
</a>
<a href="https://github.com/LanguageAgnostic" style="width:55px;border-radius: 50%">
<img src="https://github.com/LanguageAgnostic.png?size=55" width="55" style="border-radius: 50%"/>
<a href="https://github.com/LanguageAgnostic" style="width:55px">
<img src="https://github.com/LanguageAgnostic.png?size=55" width="55"/>
</a>
<a href="https://github.com/fcjr" style="width:55px;border-radius: 50%">
<img src="https://github.com/fcjr.png?size=55" width="55" style="border-radius: 50%"/>
<a href="https://github.com/fcjr" style="width:55px">
<img src="https://github.com/fcjr.png?size=55" width="55"/>
</a>
<a href="https://github.com/nickarellano" style="width:60px;border-radius: 50%">
<img src="https://github.com/nickarellano.png?size=60" width="60" style="border-radius: 50%"/>
<a href="https://github.com/nickarellano" style="width:60px">
<img src="https://github.com/nickarellano.png?size=60" width="60"/>
</a>
<a href="https://github.com/bglw" style="width:65px;border-radius: 50%">
<img src="https://github.com/bglw.png?size=65" width="65" style="border-radius: 50%"/>
<a href="https://github.com/bglw" style="width:65px">
<img src="https://github.com/bglw.png?size=65" width="65"/>
</a>
<a href="https://github.com/jugglingjsons" style="width:50px;border-radius: 50%">
<img src="https://github.com/jugglingjsons.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/jugglingjsons" style="width:50px">
<img src="https://github.com/jugglingjsons.png?size=50" width="50"/>
</a>
<a href="https://github.com/marcus-crane" style="width:50px;border-radius: 50%">
<img src="https://github.com/marcus-crane.png?size=50" width="50" style="border-radius: 50%"/>
</a>
<a href="https://github.com/codydbentley" style="width:65px">
<img src="https://github.com/codydbentley.png?size=65" width="65"/>
<a href="https://github.com/marcus-crane" style="width:65px">
<img src="https://github.com/marcus-crane.png?size=65" width="65"/>
</a>
<a href="https://github.com/bbergshaven" style="width:45px">
<img src="https://github.com/bbergshaven.png?size=45" width="45"/>
@@ -128,9 +144,20 @@ This project is supported by these kind people / companies:
<a href="https://github.com/Gilgames000" style="width:45px">
<img src="https://github.com/Gilgames000.png?size=45" width="45"/>
</a>
<a href="https://github.com/ilgityildirim" style="width:50px">
<img src="https://github.com/ilgityildirim.png?size=50" width="50"/>
</a>
<a href="https://github.com/ondoki" style="width:65px">
<img src="https://github.com/ondoki.png?size=65" width="65"/>
</a>
<a href="https://github.com/questrail" style="width:50px">
<img src="https://github.com/questrail.png?size=50" width="50"/>
</a>
<a href="https://github.com/DonTomato" style="width:45px">
<img src="https://github.com/DonTomato.png?size=45" width="45"/>
</a>
<span id="nav-5"></span>
<span id="nav-6"></span>
## Installation
@@ -140,7 +167,7 @@ an installation of Go. The basic requirements are:
- Go 1.16
- npm
<span id="nav-5-1"></span>
<span id="nav-6-1"></span>
### MacOS
@@ -148,11 +175,11 @@ Make sure you have the xcode command line tools installed. This can be done by r
`xcode-select --install`
<span id="nav-5-2"></span>
<span id="nav-6-2"></span>
### Linux
<span id="nav-5-2-1"></span>
<span id="nav-6-2-1"></span>
#### Debian/Ubuntu
@@ -164,7 +191,7 @@ _Ubuntu: 16.04, 18.04, 19.04_
_Also succesfully tested on: Zorin 15, Parrot 4.7, Linuxmint 19, Elementary 5, Kali, Neon_, Pop!\_OS
<span id="nav-5-2-2"></span>
<span id="nav-6-2-2"></span>
#### Arch Linux / ArchLabs / Ctlos Linux
@@ -172,7 +199,7 @@ _Also succesfully tested on: Zorin 15, Parrot 4.7, Linuxmint 19, Elementary 5, K
_Also succesfully test on: Manjaro & ArcoLinux_
<span id="nav-5-2-3"></span>
<span id="nav-6-2-3"></span>
#### Centos
@@ -180,7 +207,7 @@ _Also succesfully test on: Manjaro & ArcoLinux_
_CentOS 6, 7_
<span id="nav-5-2-4"></span>
<span id="nav-6-2-4"></span>
#### Fedora
@@ -188,19 +215,19 @@ _CentOS 6, 7_
_Fedora 29, 30_
<span id="nav-5-2-5"></span>
<span id="nav-6-2-5"></span>
#### VoidLinux & VoidLinux-musl
`xbps-install gtk+3-devel webkit2gtk-devel`
<span id="nav-5-2-6"></span>
<span id="nav-6-2-6"></span>
#### Gentoo
`sudo emerge gtk+:3 webkit-gtk`
<span id="nav-5-3"></span>
<span id="nav-6-3"></span>
### Windows
@@ -208,21 +235,21 @@ Windows requires gcc and related tooling. The recommended download is
from [http://tdm-gcc.tdragon.net/download](http://tdm-gcc.tdragon.net/download). Once this is installed, you are good to
go.
<span id="nav-6"></span>
<span id="nav-7"></span>
## Installation
## Usage
**Ensure Go modules are enabled: GO111MODULE=on and go/bin is in your PATH variable.**
Installation is as simple as running the following command:
<pre style='color:white'>
```
go get -u github.com/wailsapp/wails/cmd/wails
</pre>
```
<span id="nav-7"></span>
<span id="nav-7-1"></span>
## Next Steps
### Next Steps
It is recommended at this stage to read the comprehensive documentation at [https://wails.app](https://wails.app).
@@ -333,7 +360,7 @@ This project was mainly coded to the following albums:
<p align="center" style="text-align: center">
<a href="https://pace.dev"><img src="pace.jpeg"/></a><br/>
A <i>huge<i/> thanks to <a href="https://pace.dev">Pace</a> for sponsoring the project and helping the efforts to get Wails ported to Apple Silicon!<br/><br/>
A <i>huge</i> thanks to <a href="https://pace.dev">Pace</a> for sponsoring the project and helping the efforts to get Wails ported to Apple Silicon!<br/><br/>
If you are looking for a Project Management tool that's powerful but quick and easy to use, check them out!<br/><br/>
</p>

View File

@@ -1,5 +1,5 @@
<p align="center" style="text-align: center">
<img src="logo_cropped.png" width="40%"><br/>
<img src="logo.png" width="55%"><br/>
</p>
<p align="center">
使用 Go 和 Web 技术构建桌面应用程序。<br/><br/>
@@ -18,44 +18,62 @@
## 国际化
[English](README.md) | 简体中文
向 Go 程序提供 Web 接口的传统方法是通过内置 Web 服务器。Wails 提供了一种不同的方法:它提供了将 Go 代码和 Web
前端都包装成单个二进制文件的能力。通过提供工具,可以很轻松的完成项目的创建、编译和打包。你所要做的就是发挥创意!
官方文档可以在 [https://wails.app](https://wails.app) 中找到。
国内镜像站点 [https://wails.top](https://wails.top)。
[English](README.md) | [简体中文](README.zh-Hans.md)
<span id="nav-2"></span>
## 内容目录
<details>
<summary>点我 打开/关闭 目录列表</summary>
- [1. 国际化](#nav-1)
- [2. 内容目录](#nav-2)
- [3. 特征](#nav-3)
- [4. 赞助商](#nav-4)
- [5. 安装](#nav-5)
- [5.1 MacOS](#nav-5-1)
- [5.2 Linux](#nav-5-2)
- [5.2.1 Debian/Ubuntu](#nav-5-2-1)
- [5.2.2 Arch Linux / ArchLabs / Ctlos Linux](#nav-5-2-2)
- [5.2.3 Centos](#nav-5-2-3)
- [5.2.4 Fedora](#nav-5-2-4)
- [5.2.5 VoidLinux & VoidLinux-musl](#nav-5-2-5)
- [5.2.6 Gentoo](#nav-5-2-6)
- [5.3 Windows](#nav-5-3)
- [3. 项目介绍](#nav-3)
- [3.1 官方网站](#nav-3-1)
- [4. 功能](#nav-4)
- [5. 赞助商](#nav-5)
- [6. 安装](#nav-6)
- [7. 下一步](#nav-7)
- [6.1 MacOS](#nav-6-1)
- [6.2 Linux](#nav-6-2)
- [6.2.1 Debian/Ubuntu](#nav-6-2-1)
- [6.2.2 Arch Linux / ArchLabs / Ctlos Linux](#nav-6-2-2)
- [6.2.3 Centos](#nav-6-2-3)
- [6.2.4 Fedora](#nav-6-2-4)
- [6.2.5 VoidLinux & VoidLinux-musl](#nav-6-2-5)
- [6.2.6 Gentoo](#nav-6-2-6)
- [6.3 Windows](#nav-6-3)
- [7. 使用方法](#nav-7)
- [7.1 下一步](#nav-7-1)
- [8. 常见问题](#nav-8)
- [9. 贡献者](#nav-9)
- [10. 特别提及](#nav-10)
- [11. 许可协议](#nav-11)
- [12. 特别感谢](#nav-12)
- [12. 特别感谢](#nav-11)
</details>
<span id="nav-3"></span>
## 特征
## 项目介绍
为 Go 程序提供 Web 界面的传统方法是通过内置 Web 服务器。Wails 提供了一种不同的方法:它提供了将 Go 代码和 Web
前端一起打包成单个二进制文件的能力。通过提供的工具,可以很轻松的完成项目的创建、编译和打包。你所要做的就是发挥想象力!
<span id="nav-3-1"></span>
### 官方网站
官方文档可以在 [https://wails.app](https://wails.app) 中找到。
如果您对适用于 Windows 的 v2 测试版感兴趣,可以点击[此处](https://wails.io)查看。
镜像网站:
- [中国大陆镜像站点 - https://wails.top](https://wails.top)
<span id="nav-4"></span>
## 功能
- 后端使用标准 Go
- 使用任意前端技术构建 UI 界面
@@ -67,56 +85,82 @@
- 强大的命令行工具
- 跨多个平台
<span id="nav-4"></span>
<span id="nav-5"></span>
## 赞助商
这个项目由以下这些人或者公司支持:
<a href="https://github.com/sponsors/leaanthony" style="width:100px;">
<img src="sponsors/bronze%20sponsor.png" width="100"/>
</a>
<a href="https://github.com/snider" style="width:100px;">
<img src="https://github.com/snider.png?size=100" width="100"/>
</a>
<a href="https://github.com/codydbentley" style="width:100px">
<img src="https://github.com/codydbentley.png?size=100" width="100"/>
</a>
<br/>
<br/>
<a href="https://github.com/matryer" style="width:100px">
<img src="https://github.com/matryer.png" width="100"/>
</a>
<a href="https://www.jetbrains.com?from=Wails" style="width:100px">
<img src="jetbrains-grayscale.png" width="100"/>
<img src="/img/jetbrains-grayscale.png" width="100"/>
</a>
<a href="https://github.com/tc-hib" style="width:55px;border-radius: 50%">
<img src="https://github.com/tc-hib.png?size=55" width="55" style="border-radius: 50%"/>
<a href="https://github.com/tc-hib" style="width:55px">
<img src="https://github.com/tc-hib.png?size=55" width="55"/>
</a>
<a href="https://github.com/picatz" style="width:50px;border-radius: 50%">
<img src="https://github.com/picatz.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/picatz" style="width:50px">
<img src="https://github.com/picatz.png?size=50" width="50"/>
</a>
<a href="https://github.com/tylertravisty" style="width:50px;border-radius: 50%">
<img src="https://github.com/tylertravisty.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/tylertravisty" style="width:50px">
<img src="https://github.com/tylertravisty.png?size=50" width="50"/>
</a>
<a href="https://github.com/akhudek" style="width:50px;border-radius: 50%">
<img src="https://github.com/akhudek.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/akhudek" style="width:50px">
<img src="https://github.com/akhudek.png?size=50" width="50"/>
</a>
<a href="https://github.com/trea" style="width:50px;border-radius: 50%">
<img src="https://github.com/trea.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/trea" style="width:50px">
<img src="https://github.com/trea.png?size=50" width="50"/>
</a>
<a href="https://github.com/LanguageAgnostic" style="width:55px;border-radius: 50%">
<img src="https://github.com/LanguageAgnostic.png?size=55" width="55" style="border-radius: 50%"/>
<a href="https://github.com/LanguageAgnostic" style="width:55px">
<img src="https://github.com/LanguageAgnostic.png?size=55" width="55"/>
</a>
<a href="https://github.com/snider" style="width:60px;border-radius: 50%">
<img src="https://github.com/snider.png?size=60" width="60" style="border-radius: 50%"/>
<a href="https://github.com/fcjr" style="width:55px">
<img src="https://github.com/fcjr.png?size=55" width="55"/>
</a>
<a href="https://github.com/fcjr" style="width:55px;border-radius: 50%">
<img src="https://github.com/fcjr.png?size=55" width="55" style="border-radius: 50%"/>
<a href="https://github.com/nickarellano" style="width:60px">
<img src="https://github.com/nickarellano.png?size=60" width="60"/>
</a>
<a href="https://github.com/nickarellano" style="width:60px;border-radius: 50%">
<img src="https://github.com/nickarellano.png?size=60" width="60" style="border-radius: 50%"/>
<a href="https://github.com/bglw" style="width:65px">
<img src="https://github.com/bglw.png?size=65" width="65"/>
</a>
<a href="https://github.com/bglw" style="width:65px;border-radius: 50%">
<img src="https://github.com/bglw.png?size=65" width="65" style="border-radius: 50%"/>
<a href="https://github.com/jugglingjsons" style="width:50px">
<img src="https://github.com/jugglingjsons.png?size=50" width="50"/>
</a>
<a href="https://github.com/jugglingjsons" style="width:50px;border-radius: 50%">
<img src="https://github.com/jugglingjsons.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/marcus-crane" style="width:65px">
<img src="https://github.com/marcus-crane.png?size=65" width="65"/>
</a>
<a href="https://github.com/marcus-crane" style="width:50px;border-radius: 50%">
<img src="https://github.com/marcus-crane.png?size=50" width="50" style="border-radius: 50%"/>
<a href="https://github.com/bbergshaven" style="width:45px">
<img src="https://github.com/bbergshaven.png?size=45" width="45"/>
</a>
<a href="https://github.com/Gilgames000" style="width:45px">
<img src="https://github.com/Gilgames000.png?size=45" width="45"/>
</a>
<a href="https://github.com/ilgityildirim" style="width:50px">
<img src="https://github.com/ilgityildirim.png?size=50" width="50"/>
</a>
<a href="https://github.com/ondoki" style="width:65px">
<img src="https://github.com/ondoki.png?size=65" width="65"/>
</a>
<a href="https://github.com/questrail" style="width:50px">
<img src="https://github.com/questrail.png?size=50" width="50"/>
</a>
<a href="https://github.com/DonTomato" style="width:45px">
<img src="https://github.com/DonTomato.png?size=45" width="45"/>
</a>
<span id="nav-5"></span>
<span id="nav-6"></span>
## 安装
@@ -125,7 +169,7 @@ Wails 使用 cgo 与原生渲染引擎结合,因此需要依赖一些平台的
- Go 1.16
- npm
<span id="nav-5-1"></span>
<span id="nav-6-1"></span>
### MacOS
@@ -133,11 +177,11 @@ Wails 使用 cgo 与原生渲染引擎结合,因此需要依赖一些平台的
`xcode-select --install`
<span id="nav-5-2"></span>
<span id="nav-6-2"></span>
### Linux
<span id="nav-5-2-1"></span>
<span id="nav-6-2-1"></span>
#### Debian/Ubuntu
@@ -149,7 +193,7 @@ _Ubuntu: 16.04, 18.04, 19.04_
_也成功测试了: Zorin 15, Parrot 4.7, Linuxmint 19, Elementary 5, Kali, Neon_, Pop!\_OS
<span id="nav-5-2-2"></span>
<span id="nav-6-2-2"></span>
#### Arch Linux / ArchLabs / Ctlos Linux
@@ -157,7 +201,7 @@ _也成功测试了: Zorin 15, Parrot 4.7, Linuxmint 19, Elementary 5, Kali, Neo
_也成功测试了: Manjaro & ArcoLinux_
<span id="nav-5-2-3"></span>
<span id="nav-6-2-3"></span>
#### Centos
@@ -165,7 +209,7 @@ _也成功测试了: Manjaro & ArcoLinux_
_CentOS 6, 7_
<span id="nav-5-2-4"></span>
<span id="nav-6-2-4"></span>
#### Fedora
@@ -173,39 +217,39 @@ _CentOS 6, 7_
_Fedora 29, 30_
<span id="nav-5-2-5"></span>
<span id="nav-6-2-5"></span>
#### VoidLinux & VoidLinux-musl
`xbps-install gtk+3-devel webkit2gtk-devel`
<span id="nav-5-2-6"></span>
<span id="nav-6-2-6"></span>
#### Gentoo
`sudo emerge gtk+:3 webkit-gtk`
<span id="nav-5-3"></span>
<span id="nav-6-3"></span>
### Windows
Windows 需要 GCC 和相关工具。 建议从 [http://tdm-gcc.tdragon.net/download](http://tdm-gcc.tdragon.net/download) 下载, 安装完成,您就可以开始了。
<span id="nav-6"></span>
<span id="nav-7"></span>
## 安装
## 使用方法
**确保 Go modules 是开启的GO111MODULE=on 并且 go/bin 在您的 PATH 变量中。**
安装很简单,运行以下命令:
<pre style='color:white'>
```
go get -u github.com/wailsapp/wails/cmd/wails
</pre>
```
<span id="nav-7"></span>
<span id="nav-7-1"></span>
## 下一步
### 下一步
建议在此时阅读 [https://wails.app](https://wails.app) 上面的文档.
@@ -217,14 +261,14 @@ go get -u github.com/wailsapp/wails/cmd/wails
取决于您的要求。它旨在使 Go 程序员可以轻松制作轻量级桌面应用程序或在其现有应用程序中添加前端。尽管 Wails 当前不提供对诸如菜单之类的原生元素的钩子,但将来可能会改变。
- 这个项目针对的是?
- 这个项目针对的是哪些人?
希望将 HTML / JS / CSS 前端与其应用程序捆绑在一起的程序员,而不是借助创建服务并打开浏览器进行查看的方式。
- 名字怎么来的?
当我看到 WebView 时,我想"我真正想要的是围绕构建 WebView 应用程序工作,有点像 Rails 对于 Ruby"。因此最初它是一个文字游戏Webview on
Rails。碰巧也是我来自的 [国家](https://en.wikipedia.org/wiki/Wales) 的英文名字的同音。所以就是了。
Rails。碰巧也是我来自的 [国家](https://en.wikipedia.org/wiki/Wales) 的英文名字的同音。所以就是了。
<span id="nav-9"></span>
@@ -275,6 +319,7 @@ go get -u github.com/wailsapp/wails/cmd/wails
<a href="https://github.com/Igogrek"><img src="https://github.com/Igogrek.png?size=40" width="40"/></a></a>
<a href="https://github.com/aschey"><img src="https://github.com/aschey.png?size=40" width="40"/></a></a>
<a href="https://github.com/akhudek"><img src="https://github.com/akhudek.png?size=40" width="40"/></a></a>
<a href="https://github.com/s12chung"><img src="https://github.com/s12chung.png?size=40" width="40"/></a></a>
<span id="nav-10"></span>
@@ -284,9 +329,9 @@ go get -u github.com/wailsapp/wails/cmd/wails
- [Dustin Krysak](https://wiki.ubuntu.com/bashfulrobot) - 他的支持和反馈是巨大的。
- [Serge Zaitsev](https://github.com/zserge) - Wails 窗口所使用的 [Webview](https://github.com/zserge/webview) 的作者。
- [Byron](https://github.com/bh90210) - 有时Byron 单枪匹马地保持这个项目活着。没有他令人难以置信的投入,我们永远不会得到 v1 。
- [Byron](https://github.com/bh90210) - 有时Byron 一个人保持这个项目活着。没有他令人难以置信的投入,我们永远不会得到 v1 。
This project was mainly coded to the following albums:
编写项目代码时伴随着以下专辑:
- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA)
- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN)
@@ -304,17 +349,11 @@ This project was mainly coded to the following albums:
<span id="nav-11"></span>
## 许可协议
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large)
<span id="nav-12"></span>
## 特别感谢
<p align="center" style="text-align: center">
<a href="https://pace.dev"><img src="pace.jpeg"/></a><br/>
<i>非常<i/>感谢<a href="https://pace.dev">Pace</a>对项目的赞助,并帮助将 Wails 移植到 Apple Silicon !<br/><br/>
<i>非常</i> 感谢<a href="https://pace.dev">Pace</a>对项目的赞助,并帮助将 Wails 移植到 Apple Silicon !<br/><br/>
如果您正在寻找一个强大并且快速和易于使用的项目管理工具,可以看看他们!<br/><br/>
</p>

View File

@@ -71,6 +71,11 @@ const (
Crux
// RHEL distribution
RHEL
// NixOS distribution
NixOS
// Artix linux distribution
ArtixLinux
)
// DistroInfo contains all the information relating to a linux distribution
@@ -183,6 +188,10 @@ func parseOsRelease(osRelease string) *DistroInfo {
result.Distribution = EndeavourOS
case "crux":
result.Distribution = Crux
case "nixos":
result.Distribution = NixOS
case "artix":
result.Distribution = ArtixLinux
default:
result.Distribution = Unknown
}
@@ -274,6 +283,18 @@ func PrtGetInstalled(packageName string) (bool, error) {
return exitCode == 0, nil
}
// NixEnvInstalled uses nix-env to see if a package is installed
func NixEnvInstalled(packageName string) (bool, error) {
program := NewProgramHelper()
nixEnv := program.FindProgram("nix-env")
if nixEnv == nil {
return false, fmt.Errorf("cannot check dependencies: nix-env not found")
}
packageName = strings.ReplaceAll(packageName, "+", `\+`)
_, _, exitCode, _ := nixEnv.Run("-q", packageName)
return exitCode == 0, nil
}
// RequestSupportForDistribution promts the user to submit a request to support their
// currently unsupported distribution
func RequestSupportForDistribution(distroInfo *DistroInfo) error {

View File

@@ -213,6 +213,15 @@ distributions:
gccversioncommand: *gccdumpversion
programs: *archdefaultprograms
libraries: *archdefaultlibraries
artix:
id: artix
releases:
default:
version: default
name: Artix Linux
gccversioncommand: *gccdumpversion
programs: *archdefaultprograms
libraries: *archdefaultlibraries
ctlos:
id: ctlos
releases:
@@ -345,3 +354,22 @@ distributions:
help: Please install with `sudo prt-get depinst gtk3` and try again
- name: webkitgtk
help: Please install with `sudo prt-get depinst webkitgtk` and try again
nixos:
id: nixos
releases:
default:
version: default
name: NixOS
gccversioncommand: *gccdumpversion
programs:
- name: gcc
help: Please install with `nix-env -iA nixos.gcc`
- name: pkg-config
help: Please install with `nix-env -iA nixos.pkg-config`
- name: npm
help: Please install with `nix-env -iA nixos.nodejs`
libraries:
- name: gtk+3
help: Please install with `nix-env -iA nixos.gtk3`
- name: webkitgtk
help: Please install with `nix-env -iA nixos.nodePackages.webkitgtk`

View File

@@ -281,7 +281,7 @@ func CheckDependencies(logger *Logger) (bool, error) {
switch distroInfo.Distribution {
case Ubuntu, Debian, Zorin, Parrot, Linuxmint, Elementary, Kali, Neon, Deepin, Raspbian, PopOS:
libraryChecker = DpkgInstalled
case Arch, ArcoLinux, ArchLabs, Ctlos, Manjaro, ManjaroARM, EndeavourOS:
case Arch, ArcoLinux, ArchLabs, Ctlos, Manjaro, ManjaroARM, EndeavourOS, ArtixLinux:
libraryChecker = PacmanInstalled
case CentOS, Fedora, Tumbleweed, Leap, RHEL:
libraryChecker = RpmInstalled
@@ -293,6 +293,8 @@ func CheckDependencies(logger *Logger) (bool, error) {
libraryChecker = EOpkgInstalled
case Crux:
libraryChecker = PrtGetInstalled
case NixOS:
libraryChecker = NixEnvInstalled
default:
return false, RequestSupportForDistribution(distroInfo)
}

View File

@@ -1,4 +1,4 @@
package cmd
// Version - Wails version
const Version = "v1.16.5"
const Version = "v1.16.8"

View File

@@ -15,6 +15,7 @@ func init() {
projectOptions := projectHelper.NewProjectOptions()
commandDescription := `Generates a new Wails project using the given flags.
Any flags that are required and not given will be prompted for.`
build := false
initCommand := app.Command("init", "Initialises a new Wails project").
LongDescription(commandDescription).
@@ -23,7 +24,8 @@ Any flags that are required and not given will be prompted for.`
StringFlag("template", "Template name", &projectOptions.Template).
StringFlag("name", "Project name", &projectOptions.Name).
StringFlag("description", "Project description", &projectOptions.Description).
StringFlag("output", "Output binary name", &projectOptions.BinaryName)
StringFlag("output", "Output binary name", &projectOptions.BinaryName).
BoolFlag("build", "Build project after generating", &build)
initCommand.Action(func() error {
@@ -64,6 +66,10 @@ Any flags that are required and not given will be prompted for.`
return err
}
genSpinner.Success()
if !build {
logger.Yellow("Project '%s' initialised. Run `wails build` to build it.", projectOptions.Name)
return nil
}
// Build the project
cwd, _ := os.Getwd()

5
go.mod
View File

@@ -16,12 +16,13 @@ require (
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
github.com/pkg/errors v0.8.1 // indirect
github.com/sirupsen/logrus v1.4.1
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/testify v1.3.0 // indirect
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba
golang.org/x/image v0.0.0-20200430140353-33d19683fad8
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359
golang.org/x/text v0.3.0
gopkg.in/AlecAivazis/survey.v1 v1.8.4
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22

5
go.sum
View File

@@ -54,6 +54,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -76,9 +78,12 @@ golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/AlecAivazis/survey.v1 v1.8.4 h1:10xXXN3wgIhPheb5NI58zFgZv32Ana7P3Tl4shW+0Qc=

View File

@@ -54,7 +54,7 @@ extern "C"
int ready;
int js_busy;
int should_exit;
int min_width;
int min_height;
int max_width;
@@ -179,7 +179,7 @@ struct webview_priv
WEBVIEW_API int webview_inject_css(struct webview *w, const char *css);
WEBVIEW_API void webview_set_title(struct webview *w, const char *title);
WEBVIEW_API void webview_focus(struct webview *w);
WEBVIEW_API void webview_minsize(struct webview *w, int width, int height);
WEBVIEW_API void webview_minsize(struct webview *w, int width, int height);
WEBVIEW_API void webview_maxsize(struct webview *w, int width, int height);
WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen);
WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g,
@@ -342,12 +342,12 @@ struct webview_priv
w->priv.should_exit = 0;
w->priv.queue = g_async_queue_new();
w->priv.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
w->priv.min_width = -1;
w->priv.min_height = -1;
w->priv.max_width = -1;
w->priv.max_height = -1;
gtk_window_set_title(GTK_WINDOW(w->priv.window), w->title);
if (w->resizable)
@@ -421,13 +421,13 @@ struct webview_priv
}
WEBVIEW_API void webview_minsize(struct webview *w, int width, int height) {
w->priv.min_width = width;
w->priv.min_height = height;
GdkGeometry hints;
GdkWindowHints usedHints = (GdkWindowHints) GDK_HINT_MIN_SIZE;
hints.min_width = w->priv.min_width;
hints.min_height = w->priv.min_height;
if (w->priv.max_width != -1) {
@@ -435,18 +435,18 @@ struct webview_priv
hints.max_height = w->priv.max_height;
usedHints = (GdkWindowHints)(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
}
gtk_window_set_geometry_hints(GTK_WINDOW(w->priv.window), w->priv.window, &hints, usedHints);
}
WEBVIEW_API void webview_maxsize(struct webview *w, int width, int height) {
w->priv.max_width = width;
w->priv.max_height = height;
GdkGeometry hints;
GdkWindowHints usedHints = (GdkWindowHints) GDK_HINT_MAX_SIZE;
if (w->priv.min_width != -1) {
hints.min_width = w->priv.min_width;
hints.min_height = w->priv.min_height;
@@ -454,7 +454,7 @@ struct webview_priv
}
hints.max_width = w->priv.max_width;
hints.max_height = w->priv.max_height;
gtk_window_set_geometry_hints(GTK_WINDOW(w->priv.window), w->priv.window, &hints, usedHints);
}
@@ -514,7 +514,6 @@ struct webview_priv
}
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), FALSE);
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dlg), FALSE);
gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dlg), TRUE);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE);
gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dlg), TRUE);
gint response = gtk_dialog_run(GTK_DIALOG(dlg));
@@ -1398,12 +1397,12 @@ struct webview_priv
case WM_GETMINMAXINFO:
{
if (w != NULL) {
// get pixel density
// 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);
@@ -1413,7 +1412,7 @@ struct webview_priv
int heightExtra = (rcWind.bottom - rcWind.top) - rcClient.bottom;
LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam;
if (w->priv.min_width != -1) {
lpMMI->ptMinTrackSize.x = w->priv.min_width * DPIScaleX + widthExtra;
lpMMI->ptMinTrackSize.y = w->priv.min_height * DPIScaleY + heightExtra;
@@ -1423,7 +1422,7 @@ struct webview_priv
lpMMI->ptMaxTrackSize.y = w->priv.max_height * DPIScaleY + heightExtra;
}
}
return 0;
}
case WM_DESTROY:
@@ -2328,14 +2327,14 @@ struct webview_priv
{
[w->priv.window makeKeyWindow];
}
WEBVIEW_API void webview_minsize(struct webview *w, int width, int height) {
NSSize size;
size.width = width;
size.height = height;
[w->priv.window setMinSize:size];
}
WEBVIEW_API void webview_maxsize(struct webview *w, int width, int height) {
NSSize size;
size.width = width;
@@ -2346,7 +2345,7 @@ struct webview_priv
[button performSelectorOnMainThread:@selector(setEnabled:) withObject:NO
waitUntilDone:NO];
}
WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen)
{
int b = ((([w->priv.window styleMask] & NSWindowStyleMaskFullScreen) ==
@@ -2503,4 +2502,4 @@ struct webview_priv
}
#endif
#endif /* WEBVIEW_H */
#endif /* WEBVIEW_H */

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

View File

@@ -5,11 +5,15 @@ import (
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"text/tabwriter"
"time"
"github.com/wailsapp/wails/v2/cmd/wails/internal"
"github.com/wailsapp/wails/v2/internal/gomod"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/leaanthony/clir"
@@ -42,7 +46,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// Setup Platform flag
platform := runtime.GOOS
//command.StringFlag("platform", "Platform to target", &platform)
command.StringFlag("platform", "Platform to target", &platform)
// Verbosity
verbosity := 1
@@ -95,14 +99,14 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
"darwin/amd64",
"darwin/arm64",
"darwin/universal",
"linux",
//"linux",
//"linux/amd64",
//"linux/arm-7",
"windows",
"windows/amd64",
})
if !validPlatformArch.Contains(platform) {
return fmt.Errorf("platform %s is not supported", platform)
return fmt.Errorf("platform %s is not supported. Platforms supported: %s", platform, validPlatformArch.Join(","))
}
if compress && platform == "darwin/universal" {
@@ -117,12 +121,20 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
}
// Tags
experimental := false
userTags := []string{}
for _, tag := range strings.Split(tags, " ") {
thisTag := strings.TrimSpace(tag)
if thisTag != "" {
userTags = append(userTags, thisTag)
}
if thisTag == "exp" {
experimental = true
}
}
if runtime.GOOS == "linux" && !experimental {
return fmt.Errorf("Linux version coming soon!")
}
// Webview2 installer strategy (download by default)
@@ -197,6 +209,11 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
fmt.Fprintf(w, "\n")
w.Flush()
err = checkGoModVersion(logger)
if err != nil {
return err
}
return doBuild(buildOptions)
})
}
@@ -220,3 +237,29 @@ func doBuild(buildOptions *build.Options) error {
return nil
}
func checkGoModVersion(logger *clilogger.CLILogger) error {
cwd, err := os.Getwd()
if err != nil {
return err
}
gomodFilename := filepath.Join(cwd, "go.mod")
gomodData, err := os.ReadFile(gomodFilename)
if err != nil {
return err
}
outOfSync, err := gomod.GoModOutOfSync(gomodData, internal.Version)
if err != nil {
return err
}
if !outOfSync {
return nil
}
gomodversion, err := gomod.GetWailsVersionFromModFile(gomodData)
if err != nil {
return err
}
logger.Println("Warning: go.mod is using Wails '%s' but the CLI is '%s'. Consider updating it.\n", gomodversion.String(), internal.Version)
return nil
}

View File

@@ -3,8 +3,6 @@ package dev
import (
"context"
"fmt"
"github.com/wailsapp/wails/v2/cmd/wails/internal"
"github.com/wailsapp/wails/v2/internal/gomod"
"io"
"net/http"
"os"
@@ -18,7 +16,10 @@ import (
"syscall"
"time"
"github.com/leaanthony/slicer"
"github.com/google/shlex"
"github.com/wailsapp/wails/v2/cmd/wails/internal"
"github.com/wailsapp/wails/v2/internal/gomod"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/pkg/browser"
@@ -71,6 +72,7 @@ type devFlags struct {
forceBuild bool
debounceMS int
devServerURL string
appargs string
}
// AddSubcommand adds the `dev` command for the Wails application
@@ -92,12 +94,29 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
command.BoolFlag("f", "Force build application", &flags.forceBuild)
command.IntFlag("debounce", "The amount of time to wait to trigger a reload on change", &flags.debounceMS)
command.StringFlag("devserverurl", "The url of the dev server to use", &flags.devServerURL)
command.StringFlag("appargs", "arguments to pass to the underlying app (quoted and space searated)", &flags.appargs)
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
app.PrintBanner()
experimental := false
userTags := []string{}
for _, tag := range strings.Split(flags.tags, " ") {
thisTag := strings.TrimSpace(tag)
if thisTag != "" {
userTags = append(userTags, thisTag)
}
if thisTag == "exp" {
experimental = true
}
}
if runtime.GOOS == "linux" && !experimental {
return fmt.Errorf("Linux version coming soon!")
}
cwd, err := os.Getwd()
if err != nil {
return err
@@ -255,11 +274,11 @@ func defaultDevFlags() devFlags {
// generateBuildOptions creates a build.Options using the flags
func generateBuildOptions(flags devFlags) *build.Options {
return &build.Options{
result := &build.Options{
OutputType: "dev",
Mode: build.Dev,
Arch: runtime.GOARCH,
Pack: true,
Pack: false,
Platform: runtime.GOOS,
LDFlags: flags.ldflags,
Compiler: flags.compilerCommand,
@@ -268,6 +287,11 @@ func generateBuildOptions(flags devFlags) *build.Options {
Verbosity: flags.verbosity,
WailsJSDir: flags.wailsjsdir,
}
switch runtime.GOOS {
case "darwin":
result.Pack = false
}
return result
}
// loadAndMergeProjectConfig reconciles flags passed to the CLI with project config settings and updates
@@ -331,6 +355,10 @@ func loadAndMergeProjectConfig(cwd string, flags *devFlags) (*project.Project, e
shouldSaveConfig = true
}
if flags.appargs == "" && projectConfig.AppArgs != "" {
flags.appargs = projectConfig.AppArgs
}
if shouldSaveConfig {
err = projectConfig.Save()
if err != nil {
@@ -367,13 +395,15 @@ func runFrontendDevCommand(cwd string, devCommand string, wg *sync.WaitGroup) fu
if runtime.GOOS == "windows" {
// Credit: https://stackoverflow.com/a/44551450
// For whatever reason, killing an npm script on windows just doesn't exit properly with cancel
kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(cmd.Process.Pid))
kill.Stderr = os.Stderr
kill.Stdout = os.Stdout
err := kill.Run()
if err != nil {
if err.Error() != "exit status 1" {
LogRed("Error from '%s': %s", devCommand, err.Error())
if cmd != nil && cmd.Process != nil {
kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(cmd.Process.Pid))
kill.Stderr = os.Stderr
kill.Stdout = os.Stdout
err := kill.Run()
if err != nil {
if err.Error() != "exit status 1" {
LogRed("Error from '%s': %s", devCommand, err.Error())
}
}
}
} else {
@@ -439,16 +469,20 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
debugBinaryProcess = nil
}
// parse appargs if any
args, err := shlex.Split(flags.appargs)
if err != nil {
buildOptions.Logger.Fatal("Unable to parse appargs: %s", err.Error())
}
// Set environment variables accordingly
os.Setenv("loglevel", flags.loglevel)
os.Setenv("assetdir", flags.assetDir)
os.Setenv("devserverurl", flags.devServerURL)
// Start up new binary with correct args
args := slicer.StringSlicer{}
args.Add("-loglevel", flags.loglevel)
if flags.assetDir != "" {
args.Add("-assetdir", flags.assetDir)
}
if flags.devServerURL != "" {
args.Add("-devserverurl", flags.devServerURL)
}
newProcess := process.NewProcess(appBinary, args.AsSlice()...)
newProcess := process.NewProcess(appBinary, args...)
err = newProcess.Start(exitCodeChannel)
if err != nil {
// Remove binary

View File

@@ -22,7 +22,7 @@ func (b *App) startup(ctx context.Context) {
}
// domReady is called after the front-end dom has been loaded
func (b App) domReady(ctx context.Context) {
func (b *App) domReady(ctx context.Context) {
// Add your action here
}

View File

@@ -2,7 +2,7 @@
html {
text-align: center;
color: white;
background-color: rgba(0, 0, 0, 255);
background-color: rgba(0, 0, 0, 0);
width: 100%;
height: 100%;
}
@@ -13,6 +13,7 @@ body {
margin: 0;
width: 100%;
height: 100%;
overscroll-behavior: none;
}
@font-face {

View File

@@ -4,6 +4,8 @@ import (
"embed"
"log"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
@@ -13,6 +15,9 @@ import (
//go:embed frontend/dist
var assets embed.FS
//go:embed build/appicon.png
var icon []byte
func main() {
// Create an instance of the app structure
app := NewApp()
@@ -31,7 +36,7 @@ func main() {
Frameless: false,
StartHidden: false,
HideWindowOnClose: false,
RGBA: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
RGBA: &options.RGBA{R: 0, G: 0, B: 0, A: 0},
Assets: assets,
LogLevel: logger.DEBUG,
OnStartup: app.startup,
@@ -46,6 +51,17 @@ func main() {
WindowIsTranslucent: false,
DisableWindowIcon: false,
},
Mac: &mac.Options{
TitleBar: mac.TitleBarHiddenInset(),
Appearance: mac.NSAppearanceNameDarkAqua,
WebviewIsTransparent: true,
WindowIsTranslucent: true,
About: &mac.AboutInfo{
Title: "My Application",
Message: "© 2021 Me",
Icon: icon,
},
},
})
if err != nil {

View File

@@ -22,7 +22,7 @@ func (b *App) startup(ctx context.Context) {
}
// domReady is called after the front-end dom has been loaded
func (b App) domReady(ctx context.Context) {
func (b *App) domReady(ctx context.Context) {
// Add your action here
}

View File

@@ -1,5 +1,5 @@
html {
background-color: rgba(33, 37, 43, 1);
background-color: rgba(33, 37, 43, 0);
text-align: center;
color: white;
}
@@ -8,8 +8,9 @@ body {
margin: 0;
color: white;
font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
overscroll-behavior: none;
}
@font-face {

View File

@@ -4,6 +4,8 @@ import (
"embed"
"log"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
@@ -13,6 +15,9 @@ import (
//go:embed frontend/src
var assets embed.FS
//go:embed build/appicon.png
var icon []byte
func main() {
// Create an instance of the app structure
app := NewApp()
@@ -31,7 +36,7 @@ func main() {
Frameless: false,
StartHidden: false,
HideWindowOnClose: false,
RGBA: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
RGBA: &options.RGBA{R: 0, G: 0, B: 0, A: 0},
Assets: assets,
LogLevel: logger.DEBUG,
OnStartup: app.startup,
@@ -46,6 +51,16 @@ func main() {
WindowIsTranslucent: false,
DisableWindowIcon: false,
},
Mac: &mac.Options{
TitleBar: mac.TitleBarHiddenInset(),
WebviewIsTransparent: true,
WindowIsTranslucent: true,
About: &mac.AboutInfo{
Title: "Vanilla Template",
Message: "Part of the Wails projects",
Icon: icon,
},
},
})
if err != nil {

View File

@@ -1,3 +1,3 @@
package internal
var Version = "v2.0.0-beta.12"
var Version = "v2.0.0-beta.15"

View File

@@ -13,6 +13,7 @@ require (
github.com/gofiber/fiber/v2 v2.17.0
github.com/gofiber/websocket/v2 v2.0.8
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.1.2 // indirect
github.com/gorilla/websocket v1.4.1
github.com/imdario/mergo v0.3.12
@@ -21,7 +22,7 @@ require (
github.com/leaanthony/debme v1.2.1
github.com/leaanthony/go-ansi-parser v1.0.1
github.com/leaanthony/go-common-file-dialog v1.0.3
github.com/leaanthony/go-webview2 v0.0.0-20211007095229-b1759d2e4ec7
github.com/leaanthony/go-webview2 v0.0.0-20211022194343-1e4c8d4226f3
github.com/leaanthony/gosod v1.0.3
github.com/leaanthony/idgen v1.0.0
github.com/leaanthony/slicer v1.5.0
@@ -43,7 +44,7 @@ require (
github.com/ztrue/tracerr v0.3.0
golang.org/x/mod v0.4.1
golang.org/x/net v0.0.0-20210510120150-4163338589ed
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6
golang.org/x/sys v0.0.0-20211020174200-9d6173849985
golang.org/x/tools v0.1.0
nhooyr.io/websocket v1.8.6
)
@@ -60,6 +61,7 @@ require (
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/klauspost/compress v1.12.2 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e // indirect
github.com/mattn/go-runewidth v0.0.7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect

View File

@@ -75,6 +75,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -101,8 +103,9 @@ github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -115,10 +118,8 @@ github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQ
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/go-webview2 v0.0.0-20210928094513-a94a08b538bd h1:6m4zZ/esiByaDbzgdvDxjsOaIDgtuG1q2cyhjAi6uAg=
github.com/leaanthony/go-webview2 v0.0.0-20210928094513-a94a08b538bd/go.mod h1:lS5ds4bruPk9d7lzdF/OH31Z0YCerI6MmHNFGsWoUnM=
github.com/leaanthony/go-webview2 v0.0.0-20211007095229-b1759d2e4ec7 h1:qw9f/UqPp2GQ318n8G0Ikawe8GRkdPpUNJMuYeeafGA=
github.com/leaanthony/go-webview2 v0.0.0-20211007095229-b1759d2e4ec7/go.mod h1:lS5ds4bruPk9d7lzdF/OH31Z0YCerI6MmHNFGsWoUnM=
github.com/leaanthony/go-webview2 v0.0.0-20211022194343-1e4c8d4226f3 h1:qhgrg3MhFRAIvtaqoqI+SrT+0wDYpxDMp9e3cvcxMpI=
github.com/leaanthony/go-webview2 v0.0.0-20211022194343-1e4c8d4226f3/go.mod h1:lS5ds4bruPk9d7lzdF/OH31Z0YCerI6MmHNFGsWoUnM=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/idgen v1.0.0 h1:IZreR+JGEzFV4yeVuBZA25gM0keUoFy+RDUldncQ+Jw=
@@ -161,6 +162,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f h1:PgA+Olipyj258EIEYnpFFONrrCcAIWNUNoFhUfMqAGY=
github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
@@ -259,8 +262,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985 h1:LOlKVhfDyahgmqa97awczplwkjzNaELFg3zRIJ13RYo=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -282,10 +285,12 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -5,7 +5,9 @@ package appng
import (
"context"
"flag"
"os"
"path/filepath"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
@@ -16,18 +18,14 @@ import (
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/internal/signal"
pkglogger "github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"os"
"path/filepath"
)
// App defines a Wails application structure
type App struct {
frontend frontend.Frontend
logger *logger.Logger
signal *signal.Manager
options *options.App
menuManager *menumanager.Manager
@@ -60,19 +58,20 @@ func CreateApp(appoptions *options.App) (*App, error) {
myLogger.SetLogLevel(appoptions.LogLevel)
// Check for CLI Flags
assetdir := flag.String("assetdir", "", "Directory to serve assets")
devServerURL := flag.String("devserverurl", "", "URL of development server")
loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
flag.Parse()
if devServerURL != nil && *devServerURL != "" {
ctx = context.WithValue(ctx, "devserverurl", *devServerURL)
assetdir := os.Getenv("assetdir")
devServerURL := os.Getenv("devserverurl")
loglevel := os.Getenv("loglevel")
if devServerURL != "" {
ctx = context.WithValue(ctx, "devserverurl", devServerURL)
}
if assetdir != nil && *assetdir != "" {
ctx = context.WithValue(ctx, "assetdir", *assetdir)
if assetdir != "" {
ctx = context.WithValue(ctx, "assetdir", assetdir)
}
if loglevel != nil && *loglevel != "" {
level, err := pkglogger.StringToLogLevel(*loglevel)
if loglevel != "" {
level, err := pkglogger.StringToLogLevel(loglevel)
if err != nil {
return nil, err
}

View File

@@ -5,6 +5,7 @@ package appng
import (
"context"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
@@ -12,7 +13,6 @@ import (
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/signal"
"github.com/wailsapp/wails/v2/pkg/options"
)
@@ -20,7 +20,6 @@ import (
type App struct {
frontend frontend.Frontend
logger *logger.Logger
signal *signal.Manager
options *options.App
menuManager *menumanager.Manager
@@ -36,9 +35,6 @@ type App struct {
func (a *App) Run() error {
err := a.frontend.Run(a.ctx)
if a.shutdownCallback != nil {
a.shutdownCallback(a.ctx)
}
return err
}

View File

@@ -1252,7 +1252,7 @@ void createDelegate(struct Application *app) {
app->delegate = delegate;
msg_id(app->application, s("setDelegate:"), delegate);
msg_id(app->application, s("setDelegate:"), delegate);
}
bool windowShouldClose(id self, SEL cmd, id sender) {

View File

@@ -1 +1 @@
The version of WebView2 used: 1.0.864.35
The version of WebView2 SDK used: 1.0.992.28

View File

@@ -7,7 +7,7 @@ set sdk_version=%1
set native_dir="%~dp0\microsoft.web.webview2.%sdk_version%\build\native"
copy "%native_dir%\include\*.h" .. >NUL
copy "%native_dir%\x64\WebView2Loader.dll" "..\x64" >NUL
@rd /S /Q "microsoft.web.webview2.%sdk_version%"
@REM @rd /S /Q "microsoft.web.webview2.%sdk_version%"
del /s version.txt >nul 2>&1
echo The version of WebView2 SDK used: %sdk_version% > sdkversion.txt
echo SDK updated to %sdk_version%

View File

@@ -5,7 +5,7 @@ import (
"github.com/leaanthony/webview2runtime"
)
const MinimumRuntimeVersion string = "91.0.864.48"
const MinimumRuntimeVersion string = "91.0.992.28"
type installationStatus int

View File

@@ -0,0 +1,21 @@
//
// AppDelegate.h
// test
//
// Created by Lea Anthony on 10/10/21.
//
#ifndef AppDelegate_h
#define AppDelegate_h
#import <Cocoa/Cocoa.h>
#import "WailsContext.h"
@interface AppDelegate : NSResponder <NSTouchBarProvider>
@property bool alwaysOnTop;
@property (retain) WailsWindow* mainWindow;
@end
#endif /* AppDelegate_h */

View File

@@ -0,0 +1,31 @@
//
// AppDelegate.m
// test
//
// Created by Lea Anthony on 10/10/21.
//
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
@implementation AppDelegate
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return NO;
}
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[self.mainWindow makeKeyAndOrderFront:self];
if (self.alwaysOnTop) {
[self.mainWindow setLevel:NSStatusWindowLevel];
}
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp activateIgnoringOtherApps:YES];
}
@synthesize touchBar;
@end

View File

@@ -0,0 +1,61 @@
//
// Application.h
// test
//
// Created by Lea Anthony on 10/10/21.
//
#ifndef Application_h
#define Application_h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "WailsContext.h"
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int debug);
void Run(void*);
void SetTitle(void* ctx, const char *title);
void Center(void* ctx);
void SetSize(void* ctx, int width, int height);
void SetMinSize(void* ctx, int width, int height);
void SetMaxSize(void* ctx, int width, int height);
void SetPosition(void* ctx, int x, int y);
void Fullscreen(void* ctx);
void UnFullscreen(void* ctx);
void Minimise(void* ctx);
void UnMinimise(void* ctx);
void Maximise(void* ctx);
void UnMaximise(void* ctx);
void Hide(void* ctx);
void Show(void* ctx);
void SetRGBA(void* ctx, int r, int g, int b, int a);
void ExecJS(void* ctx, const char*);
void Quit(void*);
const char* GetSize(void *ctx);
const char* GetPos(void *ctx);
void ProcessURLResponse(void *inctx, const char *url, const char *contentType, void* data, int datalength);
/* Dialogs */
void MessageDialog(void *inctx, const char* dialogType, const char* title, const char* message, const char* button1, const char* button2, const char* button3, const char* button4, const char* defaultButton, const char* cancelButton, void* iconData, int iconDataLength);
void OpenFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int allowDirectories, int allowFiles, int canCreateDirectories, int treatPackagesAsDirectories, int resolveAliases, int showHiddenFiles, int allowMultipleSelection, const char* filters);
void SaveFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int canCreateDirectories, int treatPackagesAsDirectories, int showHiddenFiles, const char* filters);
/* Application Menu */
void* NewMenu(const char* name);
void AppendSubmenu(void* parent, void* child);
void AppendRole(void *inctx, void *inMenu, int role);
void SetAsApplicationMenu(void *inctx, void *inMenu);
void UpdateApplicationMenu(void *inctx);
void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen);
void* AppendMenuItem(void* inctx, void* nsmenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID);
void AppendSeparator(void* inMenu);
void UpdateMenuItem(void* nsmenuitem, int checked);
NSString* safeInit(const char* input);
#endif /* Application_h */

View File

@@ -0,0 +1,314 @@
//
// Application.m
//
// Created by Lea Anthony on 10/10/21.
//
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "WailsContext.h"
#import "Application.h"
#import "AppDelegate.h"
#import "WailsMenu.h"
#import "WailsMenuItem.h"
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int debug) {
[NSApplication sharedApplication];
WailsContext *result = [WailsContext new];
result.debug = debug;
[result CreateWindow:width :height :frameless :resizable :fullscreen :fullSizeContent :hideTitleBar :titlebarAppearsTransparent :hideTitle :useToolbar :hideToolbarSeparator :webviewIsTransparent :hideWindowOnClose :safeInit(appearance) :windowIsTranslucent];
[result SetTitle:safeInit(title)];
[result Center];
result.alwaysOnTop = alwaysOnTop;
result.hideOnClose = hideWindowOnClose;
return result;
}
void ProcessURLResponse(void *inctx, const char *url, const char *contentType, void* data, int datalength) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSString *nsurl = safeInit(url);
NSString *nsContentType = safeInit(contentType);
NSData *nsdata = [NSData dataWithBytes:data length:datalength];
[ctx processURLResponse:nsurl :nsContentType :nsdata];
}
void ExecJS(void* inctx, const char *script) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSString *nsscript = safeInit(script);
ON_MAIN_THREAD(
[ctx ExecJS:nsscript];
);
}
void SetTitle(void* inctx, const char *title) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSString *_title = safeInit(title);
ON_MAIN_THREAD(
[ctx SetTitle:_title];
);
}
void SetRGBA(void *inctx, int r, int g, int b, int a) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx SetRGBA:r :g :b :a];
);
}
void SetSize(void* inctx, int width, int height) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx SetSize:width :height];
);
}
void SetMinSize(void* inctx, int width, int height) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx SetMinSize:width :height];
);
}
void SetMaxSize(void* inctx, int width, int height) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx SetMaxSize:width :height];
);
}
void SetPosition(void* inctx, int x, int y) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx SetPosition:x :y];
);
}
void Center(void* inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx Center];
);
}
void Fullscreen(void* inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx Fullscreen];
);
}
void UnFullscreen(void* inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx UnFullscreen];
);
}
void Minimise(void* inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx Minimise];
);
}
void UnMinimise(void* inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx UnMinimise];
);
}
void Maximise(void* inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx Maximise];
);
}
const char* GetSize(void *inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSRect frame = [ctx.mainWindow frame];
NSString *result = [NSString stringWithFormat:@"%d,%d", (int)frame.size.width, (int)frame.size.height];
return [result UTF8String];
}
const char* GetPos(void *inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSScreen* screen = [ctx getCurrentScreen];
NSRect windowFrame = [ctx.mainWindow frame];
NSRect screenFrame = [screen visibleFrame];
int x = windowFrame.origin.x - screenFrame.origin.x;
int y = windowFrame.origin.y - screenFrame.origin.y;
y = screenFrame.size.height - y - windowFrame.size.height;
NSString *result = [NSString stringWithFormat:@"%d,%d",x,y];
return [result UTF8String];
}
void UnMaximise(void* inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx UnMaximise];
);
}
void Quit(void *inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
[NSApp stop:ctx];
}
void Hide(void *inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx Hide];
);
}
void Show(void *inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
[ctx Show];
);
}
NSString* safeInit(const char* input) {
NSString *result = nil;
if (input != nil) {
result = [NSString stringWithUTF8String:input];
}
return result;
}
void MessageDialog(void *inctx, const char* dialogType, const char* title, const char* message, const char* button1, const char* button2, const char* button3, const char* button4, const char* defaultButton, const char* cancelButton, void* iconData, int iconDataLength) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSString *_dialogType = safeInit(dialogType);
NSString *_title = safeInit(title);
NSString *_message = safeInit(message);
NSString *_button1 = safeInit(button1);
NSString *_button2 = safeInit(button2);
NSString *_button3 = safeInit(button3);
NSString *_button4 = safeInit(button4);
NSString *_defaultButton = safeInit(defaultButton);
NSString *_cancelButton = safeInit(cancelButton);
ON_MAIN_THREAD(
[ctx MessageDialog:_dialogType :_title :_message :_button1 :_button2 :_button3 :_button4 :_defaultButton :_cancelButton :iconData :iconDataLength];
)
}
void OpenFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int allowDirectories, int allowFiles, int canCreateDirectories, int treatPackagesAsDirectories, int resolveAliases, int showHiddenFiles, int allowMultipleSelection, const char* filters) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSString *_title = safeInit(title);
NSString *_defaultFilename = safeInit(defaultFilename);
NSString *_defaultDirectory = safeInit(defaultDirectory);
NSString *_filters = safeInit(filters);
ON_MAIN_THREAD(
[ctx OpenFileDialog:_title :_defaultFilename :_defaultDirectory :allowDirectories :allowFiles :canCreateDirectories :treatPackagesAsDirectories :resolveAliases :showHiddenFiles :allowMultipleSelection :_filters];
)
}
void SaveFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int canCreateDirectories, int treatPackagesAsDirectories, int showHiddenFiles, const char* filters) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSString *_title = safeInit(title);
NSString *_defaultFilename = safeInit(defaultFilename);
NSString *_defaultDirectory = safeInit(defaultDirectory);
NSString *_filters = safeInit(filters);
ON_MAIN_THREAD(
[ctx SaveFileDialog:_title :_defaultFilename :_defaultDirectory :canCreateDirectories :treatPackagesAsDirectories :showHiddenFiles :_filters];
)
}
void AppendRole(void *inctx, void *inMenu, int role) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
[menu appendRole :ctx :role];
}
void* NewMenu(const char *name) {
NSString *title = @"";
if (name != nil) {
title = [NSString stringWithUTF8String:name];
}
WailsMenu *result = [[WailsMenu new] initWithNSTitle:title];
return result;
}
void AppendSubmenu(void* inparent, void* inchild) {
WailsMenu *parent = (__bridge WailsMenu*) inparent;
WailsMenu *child = (__bridge WailsMenu*) inchild;
[parent appendSubmenu:child];
}
void SetAsApplicationMenu(void *inctx, void *inMenu) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
ctx.applicationMenu = menu;
}
void UpdateApplicationMenu(void *inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
ON_MAIN_THREAD(
NSApplication *app = [NSApplication sharedApplication];
[app setMainMenu:ctx.applicationMenu];
)
}
void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSString *_title = safeInit(title);
NSString *_description = safeInit(description);
[ctx SetAbout :_title :_description :imagedata :datalen];
}
void* AppendMenuItem(void* inctx, void* inMenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
NSString *_label = safeInit(label);
NSString *_shortcutKey = safeInit(shortcutKey);
return [menu AppendMenuItem:ctx :_label :_shortcutKey :modifiers :disabled :checked :menuItemID];
}
void UpdateMenuItem(void* nsmenuitem, int checked) {
ON_MAIN_THREAD(
WailsMenuItem *menuItem = (__bridge WailsMenuItem*) nsmenuitem;
[menuItem setState:(checked == 1?NSControlStateValueOn:NSControlStateValueOff)];
)
}
void AppendSeparator(void* inMenu) {
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
[menu AppendSeparator];
}
void Run(void *inctx) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
NSApplication *app = [NSApplication sharedApplication];
AppDelegate* delegate = [AppDelegate new];
[app setDelegate:(id)delegate];
ctx.appdelegate = delegate;
delegate.mainWindow = ctx.mainWindow;
delegate.alwaysOnTop = ctx.alwaysOnTop;
[ctx loadRequest:@"wails://wails/"];
[app setMainMenu:ctx.applicationMenu];
[app run];
[ctx release];
}

View File

@@ -0,0 +1,16 @@
//
// Role.h
// test
//
// Created by Lea Anthony on 24/10/21.
//
#ifndef Role_h
#define Role_h
typedef int Role;
static const Role AppMenu = 1;
static const Role EditMenu = 2;
#endif /* Role_h */

View File

@@ -0,0 +1,18 @@
//
// WailsAlert.h
// test
//
// Created by Lea Anthony on 20/10/21.
//
#ifndef WailsAlert_h
#define WailsAlert_h
#import <Cocoa/Cocoa.h>
@interface WailsAlert : NSAlert
- (void)addButton:(NSString*)text :(NSString*)defaultButton :(NSString*)cancelButton;
@end
#endif /* WailsAlert_h */

View File

@@ -0,0 +1,30 @@
//
// WailsAlert.m
// test
//
// Created by Lea Anthony on 20/10/21.
//
#import <Foundation/Foundation.h>
#import "WailsAlert.h"
@implementation WailsAlert
- (void)addButton:(NSString*)text :(NSString*)defaultButton :(NSString*)cancelButton {
if( text == nil ) {
return;
}
NSButton *button = [self addButtonWithTitle:text];
if( defaultButton != nil && [text isEqualToString:defaultButton]) {
[button setKeyEquivalent:@"\r"];
} else if( cancelButton != nil && [text isEqualToString:cancelButton]) {
[button setKeyEquivalent:@"\033"];
} else {
[button setKeyEquivalent:@""];
}
}
@end

View File

@@ -0,0 +1,82 @@
//
// WailsContext.h
// test
//
// Created by Lea Anthony on 10/10/21.
//
#ifndef WailsContext_h
#define WailsContext_h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#define ON_MAIN_THREAD(str) dispatch_async(dispatch_get_main_queue(), ^{ str; });
#define unicode(input) [NSString stringWithFormat:@"%C", input]
@interface WailsWindow : NSWindow
- (BOOL)canBecomeKeyWindow;
@end
@interface WailsContext : NSObject <WKURLSchemeHandler,WKScriptMessageHandler,WKNavigationDelegate>
@property (retain) WailsWindow* mainWindow;
@property (retain) WKWebView* webview;
@property (nonatomic, assign) id appdelegate;
@property bool hideOnClose;
@property bool shuttingDown;
@property NSSize maxSize;
@property NSSize minSize;
@property (retain) NSEvent* mouseEvent;
@property bool alwaysOnTop;
@property bool debug;
@property (retain) WKUserContentController* userContentController;
@property (retain) NSMutableDictionary *urlRequests;
@property (retain) NSMenu* applicationMenu;
@property (retain) NSImage* aboutImage;
@property (retain) NSString* aboutTitle;
@property (retain) NSString* aboutDescription;
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent;
- (void) SetSize:(int)width :(int)height;
- (void) SetPosition:(int)x :(int) y;
- (void) SetMinSize:(int)minWidth :(int)minHeight;
- (void) SetMaxSize:(int)maxWidth :(int)maxHeight;
- (void) SetTitle:(NSString*)title;
- (void) Center;
- (void) Fullscreen;
- (void) UnFullscreen;
- (void) Minimise;
- (void) UnMinimise;
- (void) Maximise;
- (void) UnMaximise;
- (void) SetRGBA:(int)r :(int)g :(int)b :(int)a;
- (void) HideMouse;
- (void) ShowMouse;
- (void) Hide;
- (void) Show;
- (void) Quit;
-(void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength;
- (void) OpenFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)allowDirectories :(bool)allowFiles :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)resolveAliases :(bool)showHiddenFiles :(bool)allowMultipleSelection :(NSString*)filters;
- (void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters;
- (void) loadRequest:(NSString*)url;
- (void) processURLResponse:(NSString *)url :(NSString *)contentType :(NSData*)data;
- (void) ExecJS:(NSString*)script;
- (NSScreen*) getCurrentScreen;
- (void) SetAbout :(NSString*)title :(NSString*)description :(void*)imagedata :(int)datalen;
@end
#endif /* WailsContext_h */

View File

@@ -0,0 +1,619 @@
//
// WailsContext.m
// test
//
// Created by Lea Anthony on 10/10/21.
//
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
#import "WailsContext.h"
#import "WailsAlert.h"
#import "WailsMenu.h"
#import "WindowDelegate.h"
#import "message.h"
#import "Role.h"
@implementation WailsWindow
- (BOOL)canBecomeKeyWindow
{
return YES;
}
@end
@implementation WailsContext
- (void) SetSize:(int)width :(int)height {
if (self.shuttingDown) return;
NSRect frame = [self.mainWindow frame];
frame.origin.y += frame.size.height - height;
frame.size.width = width;
frame.size.height = height;
ON_MAIN_THREAD([self.mainWindow setFrame:frame display:TRUE animate:FALSE];);
}
- (void) SetPosition:(int)x :(int)y {
if (self.shuttingDown) return;
NSScreen* screen = [self getCurrentScreen];
NSRect windowFrame = [self.mainWindow frame];
NSRect screenFrame = [screen frame];
windowFrame.origin.x = screenFrame.origin.x + (float)x;
windowFrame.origin.y = (screenFrame.origin.y + screenFrame.size.height) - windowFrame.size.height - (float)y;
ON_MAIN_THREAD([self.mainWindow setFrame:windowFrame display:TRUE animate:FALSE]; );
}
- (void) SetMinSize:(int)minWidth :(int)minHeight {
if (self.shuttingDown) return;
NSSize size = { minWidth, minHeight };
self.minSize = size;
ON_MAIN_THREAD(
[self.mainWindow setMinSize:size];
[self adjustWindowSize];
);
}
- (void) SetMaxSize:(int)maxWidth :(int)maxHeight {
if (self.shuttingDown) return;
NSSize size = { FLT_MAX, FLT_MAX };
size.width = maxWidth > 0 ? maxWidth : FLT_MAX;
size.height = maxHeight > 0 ? maxHeight : FLT_MAX;
self.maxSize = size;
ON_MAIN_THREAD(
[self.mainWindow setMaxSize:size];
[self adjustWindowSize];
);
}
- (void) adjustWindowSize {
if (self.shuttingDown) return;
NSRect currentFrame = [self.mainWindow frame];
if ( currentFrame.size.width > self.maxSize.width ) currentFrame.size.width = self.maxSize.width;
if ( currentFrame.size.width < self.minSize.width ) currentFrame.size.width = self.minSize.width;
if ( currentFrame.size.height > self.maxSize.height ) currentFrame.size.height = self.maxSize.height;
if ( currentFrame.size.height < self.minSize.height ) currentFrame.size.height = self.minSize.height;
[self.mainWindow setFrame:currentFrame display:YES animate:FALSE];
}
- (void) dealloc {
[super dealloc];
[self.appdelegate release];
[self.mainWindow release];
[self.mouseEvent release];
[self.userContentController release];
[self.urlRequests release];
[self.applicationMenu release];
}
- (NSScreen*) getCurrentScreen {
NSScreen* screen = [self.mainWindow screen];
if( screen == NULL ) {
screen = [NSScreen mainScreen];
}
return screen;
}
- (void) SetTitle:(NSString*)title {
ON_MAIN_THREAD([self.mainWindow setTitle:title];)
}
- (void) Center {
ON_MAIN_THREAD( [self.mainWindow center]; );
}
- (BOOL) isFullscreen {
NSWindowStyleMask masks = [self.mainWindow styleMask];
if ( masks & NSWindowStyleMaskFullScreen ) {
return YES;
}
return NO;
}
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent {
self.urlRequests = [NSMutableDictionary new];
NSWindowStyleMask styleMask = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
if (frameless) {
styleMask = NSWindowStyleMaskBorderless;
titlebarAppearsTransparent = true;
hideTitle = true;
} else {
if (!hideTitleBar) {
styleMask |= NSWindowStyleMaskTitled;
}
if (fullscreen) {
styleMask |= NSWindowStyleMaskFullScreen;
}
if( fullSizeContent || frameless || titlebarAppearsTransparent ) {
styleMask |= NSWindowStyleMaskFullSizeContentView;
}
}
if (resizable) {
styleMask |= NSWindowStyleMaskResizable;
}
self.mainWindow = [[[WailsWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height)
styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]
autorelease];
if (!frameless && useToolbar) {
id toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"];
[toolbar autorelease];
[toolbar setShowsBaselineSeparator:!hideToolbarSeparator];
[self.mainWindow setToolbar:toolbar];
}
[self.mainWindow setTitleVisibility:hideTitle];
[self.mainWindow setTitlebarAppearsTransparent:titlebarAppearsTransparent];
// [self.mainWindow canBecomeKeyWindow];
id contentView = [self.mainWindow contentView];
if (windowIsTranslucent) {
NSVisualEffectView *effectView = [NSVisualEffectView alloc];
NSRect bounds = [contentView bounds];
[effectView initWithFrame:bounds];
[effectView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
[effectView setState:NSVisualEffectStateActive];
[contentView addSubview:effectView positioned:NSWindowBelow relativeTo:nil];
}
if (appearance != nil) {
NSAppearance *nsAppearance = [NSAppearance appearanceNamed:appearance];
[self.mainWindow setAppearance:nsAppearance];
}
// Set up min/max
NSSize maxSize = { FLT_MAX, FLT_MAX };
self.maxSize = maxSize;
NSSize minSize = { 0, 0 };
self.minSize = minSize;
[self adjustWindowSize];
WindowDelegate *windowDelegate = [WindowDelegate new];
windowDelegate.hideOnClose = hideWindowOnClose;
[self.mainWindow setDelegate:windowDelegate];
// Webview stuff here!
WKWebViewConfiguration *config = [WKWebViewConfiguration new];
config.suppressesIncrementalRendering = true;
[config setURLSchemeHandler:self forURLScheme:@"wails"];
// [config.preferences setValue:[NSNumber numberWithBool:true] forKey:@"developerExtrasEnabled"];
WKUserContentController* userContentController = [WKUserContentController new];
[userContentController addScriptMessageHandler:self name:@"external"];
config.userContentController = userContentController;
self.userContentController = userContentController;
if (self.debug) {
[config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
} else {
// Disable default context menus
WKUserScript *initScript = [WKUserScript new];
[initScript initWithSource:@"window.wails.flags.disableWailsDefaultContextMenu = true;"
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:false];
[userContentController addUserScript:initScript];
}
self.webview = [WKWebView alloc];
CGRect init = { 0,0,0,0 };
[self.webview initWithFrame:init configuration:config];
[contentView addSubview:self.webview];
[self.webview setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];
CGRect contentViewBounds = [contentView bounds];
[self.webview setFrame:contentViewBounds];
if (webviewIsTransparent) {
[self.webview setValue:[NSNumber numberWithBool:!webviewIsTransparent] forKey:@"drawsBackground"];
}
[self.webview setNavigationDelegate:self];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:FALSE forKey:@"NSAutomaticQuoteSubstitutionEnabled"];
// Mouse monitors
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
id window = [event window];
if (window == self.mainWindow) {
self.mouseEvent = event;
}
return event;
}];
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
id window = [event window];
if (window == self.mainWindow) {
self.mouseEvent = nil;
[self ShowMouse];
}
return event;
}];
self.applicationMenu = [NSMenu new];
}
- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
NSMenuItem *result = [[[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:key] autorelease];
if( flags != 0 ) {
[result setKeyEquivalentModifierMask:flags];
}
return result;
}
- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key {
return [self newMenuItem :title :selector :key :0];
}
- (NSMenu*) newMenu :(NSString*)title {
WailsMenu *result = [[[WailsMenu new] initWithTitle:title] autorelease];
[result setAutoenablesItems:NO];
return result;
}
- (void) Quit {
processMessage("Q");
}
- (void) loadRequest :(NSString*)url {
NSURL *wkUrl = [NSURL URLWithString:url];
NSURLRequest *wkRequest = [NSURLRequest requestWithURL:wkUrl];
[self.webview loadRequest:wkRequest];
}
- (void) SetRGBA:(int)r :(int)g :(int)b :(int)a {
float red = r/255;
float green = g/255;
float blue = b/255;
float alpha = a/255;
id colour = [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:alpha ];
ON_MAIN_THREAD([self.mainWindow setBackgroundColor:colour];);
}
- (void) HideMouse {
[NSCursor hide];
}
- (void) ShowMouse {
[NSCursor unhide];
}
- (bool) isFullScreen {
long mask = [self.mainWindow styleMask];
return (mask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen;
}
- (bool) isMaximised {
long mask = [self.mainWindow styleMask];
return (mask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen;
}
// Fullscreen sets the main window to be fullscreen
- (void) Fullscreen {
if( ! [self isFullScreen] ) {
ON_MAIN_THREAD([self.mainWindow toggleFullScreen:nil];)
}
}
// UnFullscreen resets the main window after a fullscreen
- (void) UnFullscreen {
if( [self isFullScreen] ) {
ON_MAIN_THREAD([self.mainWindow toggleFullScreen:nil];)
}
}
- (void) Minimise {
ON_MAIN_THREAD([self.mainWindow miniaturize:nil];)
}
- (void) UnMinimise {
ON_MAIN_THREAD([self.mainWindow deminiaturize:nil];)
}
- (void) Hide {
ON_MAIN_THREAD([self.mainWindow orderOut:nil];)
}
- (void) Show {
ON_MAIN_THREAD(
[self.mainWindow makeKeyAndOrderFront:nil];
[NSApp activateIgnoringOtherApps:YES];
)
}
- (void) Maximise {
if (![self.mainWindow isZoomed]) {
ON_MAIN_THREAD([self.mainWindow zoom:nil];)
}
}
- (void) UnMaximise {
if ([self.mainWindow isZoomed]) {
ON_MAIN_THREAD([self.mainWindow zoom:nil];)
}
}
- (void) ExecJS:(NSString*)script {
ON_MAIN_THREAD(
[self.webview evaluateJavaScript:script completionHandler:nil];
)
}
- (void) processURLResponse:(NSString *)url :(NSString *)contentType :(NSData *)data {
id<WKURLSchemeTask> urlSchemeTask = self.urlRequests[url];
NSURL *nsurl = [NSURL URLWithString:url];
NSHTTPURLResponse *response = [NSHTTPURLResponse new];
NSMutableDictionary *headerFields = [NSMutableDictionary new];
headerFields[@"content-type"] = contentType;
[response initWithURL:nsurl statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:headerFields];
[urlSchemeTask didReceiveResponse:response];
[urlSchemeTask didReceiveData:data];
[urlSchemeTask didFinish];
[self.urlRequests removeObjectForKey:url];
}
- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
// Do something
self.urlRequests[urlSchemeTask.request.URL.absoluteString] = urlSchemeTask;
processURLRequest(self, [urlSchemeTask.request.URL.absoluteString UTF8String]);
}
- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
processMessage("DomReady");
}
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
NSString *m = message.body;
// Check for drag
if ( [m isEqualToString:@"drag"] ) {
if( ! [self isFullScreen] ) {
if( self.mouseEvent != nil ) {
[self HideMouse];
ON_MAIN_THREAD(
[self.mainWindow performWindowDragWithEvent:self.mouseEvent];
);
}
return;
}
}
const char *_m = [m UTF8String];
processMessage(_m);
}
/***** Dialogs ******/
-(void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength {
WailsAlert *alert = [WailsAlert new];
int style = NSAlertStyleInformational;
if (dialogType != nil ) {
if( [dialogType isEqualToString:@"warning"] ) {
style = NSAlertStyleWarning;
}
if( [dialogType isEqualToString:@"error"] ) {
style = NSAlertStyleCritical;
}
}
[alert setAlertStyle:style];
if( title != nil ) {
[alert setMessageText:title];
}
if( message != nil ) {
[alert setInformativeText:message];
}
[alert addButton:button1 :defaultButton :cancelButton];
[alert addButton:button2 :defaultButton :cancelButton];
[alert addButton:button3 :defaultButton :cancelButton];
[alert addButton:button4 :defaultButton :cancelButton];
NSImage *icon = nil;
if (iconData != nil) {
NSData *imageData = [NSData dataWithBytes:iconData length:iconDataLength];
icon = [[NSImage alloc] initWithData:imageData];
}
ON_MAIN_THREAD(
if( icon != nil) {
[alert setIcon:icon];
}
[alert.window setLevel:NSFloatingWindowLevel];
long response = [alert runModal];
int result;
if( response == NSAlertFirstButtonReturn ) {
result = 0;
}
else if( response == NSAlertSecondButtonReturn ) {
result = 1;
}
else if( response == NSAlertThirdButtonReturn ) {
result = 2;
} else {
result = 3;
}
processMessageDialogResponse(result);
)
}
-(void) OpenFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)allowDirectories :(bool)allowFiles :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)resolveAliases :(bool)showHiddenFiles :(bool)allowMultipleSelection :(NSString*)filters {
// Create the dialog
NSOpenPanel *dialog = [NSOpenPanel openPanel];
// Valid but appears to do nothing.... :/
if( title != nil ) {
[dialog setTitle:title];
}
// Filters - semicolon delimited list of file extensions
if( allowFiles ) {
if( filters != nil ) {
filters = [filters stringByReplacingOccurrencesOfString:@"*." withString:@""];
filters = [filters stringByReplacingOccurrencesOfString:@" " withString:@""];
NSArray *filterList = [filters componentsSeparatedByString:@";"];
[dialog setAllowedFileTypes:filterList];
} else {
[dialog setAllowsOtherFileTypes:true];
}
// Default Filename
if( defaultFilename != nil ) {
[dialog setNameFieldStringValue:defaultFilename];
}
[dialog setAllowsMultipleSelection: allowMultipleSelection];
[dialog setShowsHiddenFiles: showHiddenFiles];
}
// Default Directory
if( defaultDirectory != nil ) {
NSURL *url = [NSURL fileURLWithPath:defaultDirectory];
[dialog setDirectoryURL:url];
}
// Setup Options
[dialog setCanChooseFiles: allowFiles];
[dialog setCanChooseDirectories: allowDirectories];
[dialog setCanCreateDirectories: canCreateDirectories];
[dialog setResolvesAliases: resolveAliases];
[dialog setTreatsFilePackagesAsDirectories: treatPackagesAsDirectories];
// Setup callback handler
[dialog beginSheetModalForWindow:self.mainWindow completionHandler:^(NSModalResponse returnCode) {
NSMutableArray *arr = [NSMutableArray new];
for (NSURL *url in [dialog URLs]) {
[arr addObject:[url path]];
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:arr options:0 error:nil];
NSString *nsjson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
processOpenFileDialogResponse([nsjson UTF8String]);
}];
ON_MAIN_THREAD([dialog runModal];)
}
-(void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters; {
// Create the dialog
NSSavePanel *dialog = [NSOpenPanel savePanel];
// Valid but appears to do nothing.... :/
if( title != nil ) {
[dialog setTitle:title];
}
// Filters - semicolon delimited list of file extensions
if( filters != nil ) {
filters = [filters stringByReplacingOccurrencesOfString:@"*." withString:@""];
filters = [filters stringByReplacingOccurrencesOfString:@" " withString:@""];
NSArray *filterList = [filters componentsSeparatedByString:@";"];
[dialog setAllowedFileTypes:filterList];
} else {
[dialog setAllowsOtherFileTypes:true];
}
// Default Filename
if( defaultFilename != nil ) {
[dialog setNameFieldStringValue:defaultFilename];
}
// Default Directory
if( defaultDirectory != nil ) {
NSURL *url = [NSURL fileURLWithPath:defaultDirectory];
[dialog setDirectoryURL:url];
}
// Setup Options
[dialog setCanCreateDirectories: canCreateDirectories];
[dialog setTreatsFilePackagesAsDirectories: treatPackagesAsDirectories];
[dialog setShowsHiddenFiles: showHiddenFiles];
// Setup callback handler
[dialog beginSheetModalForWindow:self.mainWindow completionHandler:^(NSModalResponse returnCode) {
NSURL *url = [dialog URL];
processSaveFileDialogResponse([url.path UTF8String]);
}];
ON_MAIN_THREAD([dialog runModal];)
}
- (void) SetAbout :(NSString*)title :(NSString*)description :(void*)imagedata :(int)datalen {
self.aboutTitle = title;
self.aboutDescription = description;
NSData *imageData = [NSData dataWithBytes:imagedata length:datalen];
self.aboutImage = [[NSImage alloc] initWithData:imageData];
}
-(void) About {
WailsAlert *alert = [WailsAlert new];
[alert setAlertStyle:NSAlertStyleInformational];
if( self.aboutTitle != nil ) {
[alert setMessageText:self.aboutTitle];
}
if( self.aboutDescription != nil ) {
[alert setInformativeText:self.aboutDescription];
}
[alert.window setLevel:NSFloatingWindowLevel];
if ( self.aboutImage != nil) {
[alert setIcon:self.aboutImage];
}
ON_MAIN_THREAD([alert runModal];)
}
@end

View File

@@ -0,0 +1,30 @@
//
// WailsMenu.h
// test
//
// Created by Lea Anthony on 25/10/21.
//
#ifndef WailsMenu_h
#define WailsMenu_h
#import <Cocoa/Cocoa.h>
#import "Role.h"
#import "WailsMenu.h"
#import "WailsContext.h"
@interface WailsMenu : NSMenu
//- (void) AddMenuByRole :(Role)role;
- (WailsMenu*) initWithNSTitle :(NSString*)title;
- (void) appendSubmenu :(WailsMenu*)child;
- (void) appendRole :(WailsContext*)ctx :(Role)role;
- (NSMenuItem*) newMenuItemWithContext :(WailsContext*)ctx :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags;
- (void*) AppendMenuItem :(WailsContext*)ctx :(NSString*)label :(NSString *)shortcutKey :(int)modifiers :(bool)disabled :(bool)checked :(int)menuItemID;
- (void) AppendSeparator;
@end
#endif /* WailsMenu_h */

View File

@@ -0,0 +1,318 @@
//
// WailsMenu.m
// test
//
// Created by Lea Anthony on 25/10/21.
//
#import <Foundation/Foundation.h>
#import "WailsMenu.h"
#import "WailsMenuItem.h"
#import "Role.h"
@implementation WailsMenu
- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
NSMenuItem *result = [[[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:key] autorelease];
[result setKeyEquivalentModifierMask:flags];
return result;
}
- (NSMenuItem*) newMenuItemWithContext :(WailsContext*)ctx :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
NSMenuItem *result = [[NSMenuItem new] autorelease];
if ( title != nil ) {
[result setTitle:title];
}
if (selector != nil) {
[result setAction:selector];
}
if (key) {
[result setKeyEquivalent:key];
}
if( flags != 0 ) {
[result setKeyEquivalentModifierMask:flags];
}
result.target = ctx;
return result;
}
- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key {
return [self newMenuItem :title :selector :key :0];
}
- (WailsMenu*) initWithNSTitle:(NSString *)title {
if( title != nil ) {
[super initWithTitle:title];
}
[self setAutoenablesItems:NO];
return self;
}
- (void) appendSubmenu :(WailsMenu*)child {
NSMenuItem *childMenuItem = [[NSMenuItem new] autorelease];
[childMenuItem setTitle:[child title]];
[self addItem:childMenuItem];
[childMenuItem setSubmenu:child];
}
- (void) appendRole :(WailsContext*)ctx :(Role)role {
switch(role) {
case AppMenu:
{
NSString *appName = [NSRunningApplication currentApplication].localizedName;
if( appName == nil ) {
appName = [[NSProcessInfo processInfo] processName];
}
WailsMenu *appMenu = [[WailsMenu new] initWithNSTitle:appName];
id quitTitle = [@"Quit " stringByAppendingString:appName];
NSMenuItem* quitMenuItem = [self newMenuItem:quitTitle :@selector(Quit) :@"q" :NSEventModifierFlagCommand];
quitMenuItem.target = ctx;
if (ctx.aboutTitle != nil) {
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
}
[appMenu addItem:quitMenuItem];
[self appendSubmenu:appMenu];
break;
}
case EditMenu:
{
WailsMenu *editMenu = [[WailsMenu new] initWithNSTitle:@"Edit"];
[editMenu addItem:[self newMenuItem:@"Undo" :@selector(undoActionName) :@"z" :NSEventModifierFlagCommand]];
[editMenu addItem:[self newMenuItem:@"Redo" :@selector(redoActionName) :@"z" :(NSEventModifierFlagShift | NSEventModifierFlagCommand)]];
[editMenu addItem:[NSMenuItem separatorItem]];
[editMenu addItem:[self newMenuItem:@"Cut" :@selector(cut:) :@"x" :NSEventModifierFlagCommand]];
[editMenu addItem:[self newMenuItem:@"Copy" :@selector(copy:) :@"c" :NSEventModifierFlagCommand]];
[editMenu addItem:[self newMenuItem:@"Paste" :@selector(paste:) :@"v" :NSEventModifierFlagCommand]];
[editMenu addItem:[self newMenuItem:@"Paste and Match Style" :@selector(pasteAsRichText:) :@"v" :(NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand)]];
[editMenu addItem:[self newMenuItem:@"Delete" :@selector(delete:) :[self accel:@"backspace"] :0]];
[editMenu addItem:[self newMenuItem:@"Select All" :@selector(selectAll:) :@"a" :NSEventModifierFlagCommand]];
[editMenu addItem:[NSMenuItem separatorItem]];
// NSMenuItem *speechMenuItem = [[NSMenuItem new] autorelease];
// [speechMenuItem setTitle:@"Speech"];
// [editMenu addItem:speechMenuItem];
WailsMenu *speechMenu = [[WailsMenu new] initWithNSTitle:@"Speech"];
[speechMenu addItem:[self newMenuItem:@"Start Speaking" :@selector(startSpeaking:) :@""]];
[speechMenu addItem:[self newMenuItem:@"Stop Speaking" :@selector(stopSpeaking:) :@""]];
[editMenu appendSubmenu:speechMenu];
[self appendSubmenu:editMenu];
break;
}
}
}
- (void*) AppendMenuItem :(WailsContext*)ctx :(NSString*)label :(NSString *)shortcutKey :(int)modifiers :(bool)disabled :(bool)checked :(int)menuItemID {
NSString *nslabel = @"";
if (label != nil ) {
nslabel = label;
}
WailsMenuItem *menuItem = [WailsMenuItem new];
// Label
menuItem.title = nslabel;
// Process callback
menuItem.menuItemID = menuItemID;
menuItem.action = @selector(handleClick);
menuItem.target = menuItem;
// Shortcut
if (shortcutKey != nil) {
[menuItem setKeyEquivalent:[self accel:shortcutKey]];
[menuItem setKeyEquivalentModifierMask:modifiers];
}
// Enabled/Disabled
[menuItem setEnabled:!disabled];
// Checked
[menuItem setState:(checked ? NSControlStateValueOn : NSControlStateValueOff)];
[self addItem:menuItem];
return menuItem;
}
- (void) AppendSeparator {
[self addItem:[NSMenuItem separatorItem]];
}
- (NSString*) accel :(NSString*)key {
// Guard against no accelerator key
if( key == NULL ) {
return @"";
}
if( [key isEqualToString:@"backspace"] ) {
return unicode(0x0008);
}
if( [key isEqualToString:@"tab"] ) {
return unicode(0x0009);
}
if( [key isEqualToString:@"return"] ) {
return unicode(0x000d);
}
if( [key isEqualToString:@"enter"] ) {
return unicode(0x000d);
}
if( [key isEqualToString:@"escape"] ) {
return unicode(0x001b);
}
if( [key isEqualToString:@"left"] ) {
return unicode(0x001c);
}
if( [key isEqualToString:@"right"] ) {
return unicode(0x001d);
}
if( [key isEqualToString:@"up"] ) {
return unicode(0x001e);
}
if( [key isEqualToString:@"down"] ) {
return unicode(0x001f);
}
if( [key isEqualToString:@"space"] ) {
return unicode(0x0020);
}
if( [key isEqualToString:@"delete"] ) {
return unicode(0x007f);
}
if( [key isEqualToString:@"home"] ) {
return unicode(0x2196);
}
if( [key isEqualToString:@"end"] ) {
return unicode(0x2198);
}
if( [key isEqualToString:@"page up"] ) {
return unicode(0x21de);
}
if( [key isEqualToString:@"page down"] ) {
return unicode(0x21df);
}
if( [key isEqualToString:@"f1"] ) {
return unicode(0xf704);
}
if( [key isEqualToString:@"f2"] ) {
return unicode(0xf705);
}
if( [key isEqualToString:@"f3"] ) {
return unicode(0xf706);
}
if( [key isEqualToString:@"f4"] ) {
return unicode(0xf707);
}
if( [key isEqualToString:@"f5"] ) {
return unicode(0xf708);
}
if( [key isEqualToString:@"f6"] ) {
return unicode(0xf709);
}
if( [key isEqualToString:@"f7"] ) {
return unicode(0xf70a);
}
if( [key isEqualToString:@"f8"] ) {
return unicode(0xf70b);
}
if( [key isEqualToString:@"f9"] ) {
return unicode(0xf70c);
}
if( [key isEqualToString:@"f10"] ) {
return unicode(0xf70d);
}
if( [key isEqualToString:@"f11"] ) {
return unicode(0xf70e);
}
if( [key isEqualToString:@"f12"] ) {
return unicode(0xf70f);
}
if( [key isEqualToString:@"f13"] ) {
return unicode(0xf710);
}
if( [key isEqualToString:@"f14"] ) {
return unicode(0xf711);
}
if( [key isEqualToString:@"f15"] ) {
return unicode(0xf712);
}
if( [key isEqualToString:@"f16"] ) {
return unicode(0xf713);
}
if( [key isEqualToString:@"f17"] ) {
return unicode(0xf714);
}
if( [key isEqualToString:@"f18"] ) {
return unicode(0xf715);
}
if( [key isEqualToString:@"f19"] ) {
return unicode(0xf716);
}
if( [key isEqualToString:@"f20"] ) {
return unicode(0xf717);
}
if( [key isEqualToString:@"f21"] ) {
return unicode(0xf718);
}
if( [key isEqualToString:@"f22"] ) {
return unicode(0xf719);
}
if( [key isEqualToString:@"f23"] ) {
return unicode(0xf71a);
}
if( [key isEqualToString:@"f24"] ) {
return unicode(0xf71b);
}
if( [key isEqualToString:@"f25"] ) {
return unicode(0xf71c);
}
if( [key isEqualToString:@"f26"] ) {
return unicode(0xf71d);
}
if( [key isEqualToString:@"f27"] ) {
return unicode(0xf71e);
}
if( [key isEqualToString:@"f28"] ) {
return unicode(0xf71f);
}
if( [key isEqualToString:@"f29"] ) {
return unicode(0xf720);
}
if( [key isEqualToString:@"f30"] ) {
return unicode(0xf721);
}
if( [key isEqualToString:@"f31"] ) {
return unicode(0xf722);
}
if( [key isEqualToString:@"f32"] ) {
return unicode(0xf723);
}
if( [key isEqualToString:@"f33"] ) {
return unicode(0xf724);
}
if( [key isEqualToString:@"f34"] ) {
return unicode(0xf725);
}
if( [key isEqualToString:@"f35"] ) {
return unicode(0xf726);
}
// if( [key isEqualToString:@"Insert"] ) {
// return unicode(0xf727);
// }
// if( [key isEqualToString:@"PrintScreen"] ) {
// return unicode(0xf72e);
// }
// if( [key isEqualToString:@"ScrollLock"] ) {
// return unicode(0xf72f);
// }
if( [key isEqualToString:@"numLock"] ) {
return unicode(0xf739);
}
return key;
}
@end

View File

@@ -0,0 +1,22 @@
//
// WailsMenuItem.h
// test
//
// Created by Lea Anthony on 27/10/21.
//
#ifndef WailsMenuItem_h
#define WailsMenuItem_h
#import <Cocoa/Cocoa.h>
@interface WailsMenuItem : NSMenuItem
@property int menuItemID;
- (void) handleClick;
@end
#endif /* WailsMenuItem_h */

View File

@@ -0,0 +1,20 @@
//
// WailsMenuItem.m
// test
//
// Created by Lea Anthony on 27/10/21.
//
#import <Foundation/Foundation.h>
#import "WailsMenuItem.h"
#include "message.h"
@implementation WailsMenuItem
- (void) handleClick {
processCallback(self.menuItemID);
}
@end

View File

@@ -0,0 +1,18 @@
//
// WindowDelegate.h
// test
//
// Created by Lea Anthony on 10/10/21.
//
#ifndef WindowDelegate_h
#define WindowDelegate_h
@interface WindowDelegate : NSObject <NSWindowDelegate>
@property bool hideOnClose;
@end
#endif /* WindowDelegate_h */

View File

@@ -0,0 +1,24 @@
//
// WindowDelegate.m
// test
//
// Created by Lea Anthony on 10/10/21.
//
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "WindowDelegate.h"
#import "message.h"
#import "WailsContext.h"
@implementation WindowDelegate
- (BOOL)windowShouldClose:(WailsWindow *)sender {
[sender orderOut:nil];
if( self.hideOnClose == false ) {
processMessage("Q");
}
return !self.hideOnClose;
}
@end

View File

@@ -1,4 +1,5 @@
//go:build darwin
// +build darwin
package darwin

View File

@@ -0,0 +1,51 @@
//go:build darwin
// +build darwin
package darwin
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
#import <Foundation/Foundation.h>
#import "Application.h"
#include <stdlib.h>
*/
import "C"
import (
"errors"
"strconv"
"github.com/wailsapp/wails/v2/pkg/menu"
)
func (f *Frontend) handleCallback(menuItemID uint) error {
menuItem := getMenuItemForID(menuItemID)
if menuItem == nil {
return errors.New("unknown menuItem ID: " + strconv.Itoa(int(menuItemID)))
}
wailsMenuItem := menuItem.wailsMenuItem
if wailsMenuItem.Type == menu.CheckboxType {
wailsMenuItem.Checked = !wailsMenuItem.Checked
C.UpdateMenuItem(menuItem.nsmenuitem, bool2Cint(wailsMenuItem.Checked))
}
if wailsMenuItem.Type == menu.RadioType {
// Ignore if we clicked the item that is already checked
if !wailsMenuItem.Checked {
for _, item := range menuItem.radioGroupMembers {
if item.wailsMenuItem.Checked {
item.wailsMenuItem.Checked = false
C.UpdateMenuItem(item.nsmenuitem, C.int(0))
}
}
wailsMenuItem.Checked = true
C.UpdateMenuItem(menuItem.nsmenuitem, C.int(1))
}
}
if wailsMenuItem.Click != nil {
go wailsMenuItem.Click(&menu.CallbackData{MenuItem: wailsMenuItem})
}
return nil
}

View File

@@ -0,0 +1,32 @@
package darwin
/*
#include <stdlib.h>
*/
import "C"
import "unsafe"
// Calloc handles alloc/dealloc of C data
type Calloc struct {
pool []unsafe.Pointer
}
// NewCalloc creates a new allocator
func NewCalloc() Calloc {
return Calloc{}
}
// String creates a new C string and retains a reference to it
func (c Calloc) String(in string) *C.char {
result := C.CString(in)
c.pool = append(c.pool, unsafe.Pointer(result))
return result
}
// Free frees all allocated C memory
func (c Calloc) Free() {
for _, str := range c.pool {
C.free(str)
}
c.pool = []unsafe.Pointer{}
}

View File

@@ -1,32 +1,193 @@
//go:build darwin
// +build darwin
package darwin
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
#import <Foundation/Foundation.h>
#import "Application.h"
#import "WailsContext.h"
*/
import "C"
import (
"encoding/json"
"fmt"
"strings"
"sync"
"unsafe"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/frontend"
)
// Obj-C dialog methods send the response to this channel
var messageDialogResponse = make(chan int)
var openFileDialogResponse = make(chan string)
var saveFileDialogResponse = make(chan string)
var dialogLock sync.Mutex
// OpenDirectoryDialog prompts the user to select a directory
func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) {
return "", nil
results, err := f.openDialog(&options, false, false, true)
if err != nil {
return "", err
}
var selected string
if len(results) > 0 {
selected = results[0]
}
return selected, nil
}
func (f *Frontend) openDialog(options *frontend.OpenDialogOptions, multiple bool, allowfiles bool, allowdirectories bool) ([]string, error) {
dialogLock.Lock()
defer dialogLock.Unlock()
c := NewCalloc()
defer c.Free()
title := c.String(options.Title)
defaultFilename := c.String(options.DefaultFilename)
defaultDirectory := c.String(options.DefaultDirectory)
allowDirectories := bool2Cint(allowdirectories)
allowFiles := bool2Cint(allowfiles)
canCreateDirectories := bool2Cint(options.CanCreateDirectories)
treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories)
resolveAliases := bool2Cint(options.ResolvesAliases)
showHiddenFiles := bool2Cint(options.ShowHiddenFiles)
allowMultipleFileSelection := bool2Cint(multiple)
var filterStrings slicer.StringSlicer
if options.Filters != nil {
for _, filter := range options.Filters {
thesePatterns := strings.Split(filter.Pattern, ";")
for _, pattern := range thesePatterns {
pattern = strings.TrimSpace(pattern)
if pattern != "" {
filterStrings.Add(pattern)
}
}
}
filterStrings.Deduplicate()
}
filters := filterStrings.Join(";")
C.OpenFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, allowDirectories, allowFiles, canCreateDirectories, treatPackagesAsDirectories, resolveAliases, showHiddenFiles, allowMultipleFileSelection, c.String(filters))
var result = <-openFileDialogResponse
var parsedResults []string
err := json.Unmarshal([]byte(result), &parsedResults)
return parsedResults, err
}
// OpenFileDialog prompts the user to select a file
func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
return "", nil
results, err := f.openDialog(&options, false, options.AllowFiles, options.AllowDirectories)
if err != nil {
return "", err
}
var selected string
if len(results) > 0 {
selected = results[0]
}
return selected, nil
}
// OpenMultipleFilesDialog prompts the user to select a file
func (f *Frontend) OpenMultipleFilesDialog(dialogOptions frontend.OpenDialogOptions) ([]string, error) {
return []string{}, nil
func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) {
return f.openDialog(&options, true, options.AllowFiles, options.AllowDirectories)
}
// SaveFileDialog prompts the user to select a file
func (f *Frontend) SaveFileDialog(dialogOptions frontend.SaveDialogOptions) (string, error) {
return "", nil
func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) {
dialogLock.Lock()
defer dialogLock.Unlock()
c := NewCalloc()
defer c.Free()
title := c.String(options.Title)
defaultFilename := c.String(options.DefaultFilename)
defaultDirectory := c.String(options.DefaultDirectory)
canCreateDirectories := bool2Cint(options.CanCreateDirectories)
treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories)
showHiddenFiles := bool2Cint(options.ShowHiddenFiles)
var filterStrings slicer.StringSlicer
if options.Filters != nil {
for _, filter := range options.Filters {
thesePatterns := strings.Split(filter.Pattern, ";")
for _, pattern := range thesePatterns {
pattern = strings.TrimSpace(pattern)
if pattern != "" {
filterStrings.Add(pattern)
}
}
}
filterStrings.Deduplicate()
}
filters := filterStrings.Join(";")
C.SaveFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, canCreateDirectories, treatPackagesAsDirectories, showHiddenFiles, c.String(filters))
var result = <-saveFileDialogResponse
return result, nil
}
// MessageDialog show a message dialog to the user
func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
return "", nil
dialogLock.Lock()
defer dialogLock.Unlock()
c := NewCalloc()
defer c.Free()
dialogType := c.String(string(options.Type))
title := c.String(options.Title)
message := c.String(options.Message)
defaultButton := c.String(options.DefaultButton)
cancelButton := c.String(options.CancelButton)
const MaxButtons = 4
var buttons [MaxButtons]*C.char
for index, buttonText := range options.Buttons {
if index == MaxButtons {
return "", fmt.Errorf("max %d buttons supported (%d given)", MaxButtons, len(options.Buttons))
}
buttons[index] = c.String(buttonText)
}
var iconData unsafe.Pointer
var iconDataLength C.int
if options.Icon != nil {
iconData = unsafe.Pointer(&options.Icon[0])
iconDataLength = C.int(len(options.Icon))
}
C.MessageDialog(f.mainWindow.context, dialogType, title, message, buttons[0], buttons[1], buttons[2], buttons[3], defaultButton, cancelButton, iconData, iconDataLength)
var result = <-messageDialogResponse
selectedC := buttons[result]
var selected string
if selectedC != nil {
selected = options.Buttons[result]
}
return selected, nil
}
//export processMessageDialogResponse
func processMessageDialogResponse(selection int) {
messageDialogResponse <- selection
}
//export processOpenFileDialogResponse
func processOpenFileDialogResponse(cselection *C.char) {
selection := C.GoString(cselection)
openFileDialogResponse <- selection
}
//export processSaveFileDialogResponse
func processSaveFileDialogResponse(cselection *C.char) {
selection := C.GoString(cselection)
saveFileDialogResponse <- selection
}

View File

@@ -1,13 +1,26 @@
//go:build darwin
// +build darwin
package darwin
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
#import <Foundation/Foundation.h>
#import "Application.h"
#import "WailsContext.h"
#include <stdlib.h>
*/
import "C"
import (
"context"
"encoding/json"
"html/template"
"log"
"runtime"
"strconv"
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
@@ -16,6 +29,15 @@ import (
"github.com/wailsapp/wails/v2/pkg/options"
)
type request struct {
url *C.char
ctx unsafe.Pointer
}
var messageBuffer = make(chan string, 100)
var requestBuffer = make(chan *request, 100)
var callbackBuffer = make(chan uint, 10)
type Frontend struct {
// Context
@@ -29,7 +51,7 @@ type Frontend struct {
assets *assetserver.DesktopAssetServer
// main window handle
//mainWindow *Window
mainWindow *Window
minWidth, minHeight, maxWidth, maxHeight int
bindings *binding.Bindings
dispatcher frontend.Dispatcher
@@ -44,10 +66,6 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
bindings: appBindings,
dispatcher: dispatcher,
ctx: ctx,
minHeight: appoptions.MinHeight,
minWidth: appoptions.MinWidth,
maxHeight: appoptions.MaxHeight,
maxWidth: appoptions.MaxWidth,
}
// Check if we have been given a directory to serve assets from.
@@ -69,9 +87,32 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
}
result.assets = assets
go result.startMessageProcessor()
go result.startRequestProcessor()
go result.startCallbackProcessor()
return result
}
func (f *Frontend) startMessageProcessor() {
for message := range messageBuffer {
f.processMessage(message)
}
}
func (f *Frontend) startRequestProcessor() {
for request := range requestBuffer {
f.processRequest(request)
}
}
func (f *Frontend) startCallbackProcessor() {
for callback := range callbackBuffer {
err := f.handleCallback(callback)
if err != nil {
println(err.Error())
}
}
}
func (f *Frontend) WindowReload() {
f.ExecJS("runtime.WindowReload();")
}
@@ -80,222 +121,104 @@ func (f *Frontend) Run(ctx context.Context) error {
f.ctx = context.WithValue(ctx, "frontend", f)
//mainWindow := NewWindow(nil, f.frontendOptions)
//f.mainWindow = mainWindow
//
//var _debug = ctx.Value("debug")
//if _debug != nil {
// f.debug = _debug.(bool)
//}
//
//f.WindowCenter()
//f.setupChromium()
//
//mainWindow.OnSize().Bind(func(arg *winc.Event) {
// f.chromium.Resize()
//})
//
//mainWindow.OnClose().Bind(func(arg *winc.Event) {
// if f.frontendOptions.HideWindowOnClose {
// f.WindowHide()
// } else {
// f.Quit()
// }
//})
//
//// TODO: Move this into a callback from frontend
//go func() {
// if f.frontendOptions.OnStartup != nil {
// f.frontendOptions.OnStartup(f.ctx)
// }
//}()
//
//mainWindow.Run()
var _debug = ctx.Value("debug")
if _debug != nil {
f.debug = _debug.(bool)
}
mainWindow := NewWindow(f.frontendOptions, f.debug)
f.mainWindow = mainWindow
f.mainWindow.Center()
go func() {
if f.frontendOptions.OnStartup != nil {
f.frontendOptions.OnStartup(f.ctx)
}
}()
mainWindow.Run()
return nil
}
func (f *Frontend) WindowCenter() {
runtime.LockOSThread()
//f.mainWindow.Center()
f.mainWindow.Center()
}
func (f *Frontend) WindowSetPos(x, y int) {
runtime.LockOSThread()
//f.mainWindow.SetPos(x, y)
f.mainWindow.SetPos(x, y)
}
func (f *Frontend) WindowGetPos() (int, int) {
runtime.LockOSThread()
//return f.mainWindow.Pos()
return 0, 0
return f.mainWindow.Pos()
}
func (f *Frontend) WindowSetSize(width, height int) {
runtime.LockOSThread()
//f.mainWindow.SetSize(width, height)
f.mainWindow.SetSize(width, height)
}
func (f *Frontend) WindowGetSize() (int, int) {
runtime.LockOSThread()
//return f.mainWindow.Size()
return 0, 0
return f.mainWindow.Size()
}
func (f *Frontend) WindowSetTitle(title string) {
runtime.LockOSThread()
//f.mainWindow.SetText(title)
f.mainWindow.SetTitle(title)
}
func (f *Frontend) WindowFullscreen() {
runtime.LockOSThread()
//f.mainWindow.SetMaxSize(0, 0)
//f.mainWindow.SetMinSize(0, 0)
//f.mainWindow.Fullscreen()
f.mainWindow.SetMaxSize(0, 0)
f.mainWindow.SetMinSize(0, 0)
f.mainWindow.Fullscreen()
}
func (f *Frontend) WindowUnFullscreen() {
runtime.LockOSThread()
//f.mainWindow.UnFullscreen()
//f.mainWindow.SetMaxSize(f.maxWidth, f.maxHeight)
//f.mainWindow.SetMinSize(f.minWidth, f.minHeight)
f.mainWindow.UnFullscreen()
f.mainWindow.SetMaxSize(f.maxWidth, f.maxHeight)
f.mainWindow.SetMinSize(f.minWidth, f.minHeight)
}
func (f *Frontend) WindowShow() {
runtime.LockOSThread()
//f.mainWindow.Show()
f.mainWindow.Show()
}
func (f *Frontend) WindowHide() {
runtime.LockOSThread()
//f.mainWindow.Hide()
f.mainWindow.Hide()
}
func (f *Frontend) WindowMaximise() {
runtime.LockOSThread()
//f.mainWindow.Maximise()
f.mainWindow.Maximise()
}
func (f *Frontend) WindowUnmaximise() {
runtime.LockOSThread()
//f.mainWindow.Restore()
f.mainWindow.UnMaximise()
}
func (f *Frontend) WindowMinimise() {
runtime.LockOSThread()
//f.mainWindow.Minimise()
f.mainWindow.Minimise()
}
func (f *Frontend) WindowUnminimise() {
runtime.LockOSThread()
//f.mainWindow.Restore()
f.mainWindow.UnMinimise()
}
func (f *Frontend) WindowSetMinSize(width int, height int) {
runtime.LockOSThread()
f.minWidth = width
f.minHeight = height
//f.mainWindow.SetMinSize(width, height)
f.mainWindow.SetMinSize(width, height)
}
func (f *Frontend) WindowSetMaxSize(width int, height int) {
runtime.LockOSThread()
f.maxWidth = width
f.maxHeight = height
//f.mainWindow.SetMaxSize(width, height)
f.mainWindow.SetMaxSize(width, height)
}
func (f *Frontend) WindowSetRGBA(col *options.RGBA) {
runtime.LockOSThread()
if col == nil {
return
}
/*
//f.mainWindow.Dispatch(func() {
controller := f.chromium.GetController()
controller2 := controller.GetICoreWebView2Controller2()
backgroundCol := edge.COREWEBVIEW2_COLOR{
A: col.A,
R: col.R,
G: col.G,
B: col.B,
}
// Webview2 only has 0 and 255 as valid values.
if backgroundCol.A > 0 && backgroundCol.A < 255 {
backgroundCol.A = 255
}
if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.WebviewIsTransparent {
backgroundCol.A = 0
}
err := controller2.PutDefaultBackgroundColor(backgroundCol)
if err != nil {
log.Fatal(err)
}
})
*/
f.mainWindow.SetRGBA(col.R, col.G, col.B, col.A)
}
func (f *Frontend) Quit() {
//winc.Exit()
f.mainWindow.Quit()
if f.frontendOptions.OnShutdown != nil {
f.frontendOptions.OnShutdown(f.ctx)
}
}
/*
const (
ctrlZ int = 90
ctrlX = 88
ctrlC = 67
ctrlV = 86
)
func (f *Frontend) setupChromium() {
chromium := edge.NewChromium()
f.chromium = chromium
chromium.MessageCallback = f.processMessage
chromium.WebResourceRequestedCallback = f.processRequest
chromium.NavigationCompletedCallback = f.navigationCompleted
acceleratorsWebviewShouldProcess := slicer.Int([]int{ctrlV, ctrlC, ctrlX, ctrlZ})
chromium.AcceleratorKeyCallback = func(vkey uint) bool {
// We want webview to handle ctrl-C, ctrl-Z, ctrl-v, ctrl-x
if acceleratorsWebviewShouldProcess.Contains(int(vkey)) {
return false
}
// Post keypress
//w32.PostMessage(f.mainWindow.Handle(), w32.WM_KEYDOWN, uintptr(vkey), 0)
return true
}
chromium.Embed(f.mainWindow.Handle())
chromium.Resize()
settings, err := chromium.GetSettings()
if err != nil {
log.Fatal(err)
}
err = settings.PutAreDefaultContextMenusEnabled(f.debug)
if err != nil {
log.Fatal(err)
}
err = settings.PutAreDevToolsEnabled(f.debug)
if err != nil {
log.Fatal(err)
}
err = settings.PutIsZoomControlEnabled(false)
if err != nil {
log.Fatal(err)
}
err = settings.PutIsStatusBarEnabled(false)
if err != nil {
log.Fatal(err)
}
err = settings.PutIsStatusBarEnabled(false)
if err != nil {
log.Fatal(err)
}
// Set background colour
f.WindowSetRGBA(f.frontendOptions.RGBA)
chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)
chromium.Navigate("file://wails/")
}
*/
type EventNotify struct {
Name string `json:"name"`
Data []interface{} `json:"data"`
@@ -314,47 +237,15 @@ func (f *Frontend) Notify(name string, data ...interface{}) {
f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
}
//func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) {
// //Get the request
// uri, _ := req.GetUri()
//
// // Translate URI
// uri = strings.TrimPrefix(uri, "file://wails")
// if !strings.HasPrefix(uri, "/") {
// return
// }
//
// // Load file from asset store
// content, mimeType, err := f.assets.Load(uri)
// if err != nil {
// return
// }
//
// env := f.chromium.Environment()
// headers := "Content-Type: " + mimeType
// if f.servingFromDisk {
// headers += "\nPragma: no-cache"
// }
// response, err := env.CreateWebResourceResponse(content, 200, "OK", headers)
// if err != nil {
// return
// }
// // Send response back
// err = args.PutResponse(response)
// if err != nil {
// return
// }
// return
//}
func (f *Frontend) processMessage(message string) {
if message == "drag" {
err := f.startDrag()
if err != nil {
f.logger.Error(err.Error())
if message == "DomReady" {
if f.frontendOptions.OnDomReady != nil {
f.frontendOptions.OnDomReady(f.ctx)
}
return
}
result, err := f.dispatcher.ProcessMessage(message, f)
if err != nil {
f.logger.Error(err.Error())
@@ -375,44 +266,50 @@ func (f *Frontend) processMessage(message string) {
}
func (f *Frontend) Callback(message string) {
//f.mainWindow.Dispatch(func() {
// f.chromium.Eval(`window.wails.Callback(` + strconv.Quote(message) + `);`)
//})
}
func (f *Frontend) startDrag() error {
//if !w32.ReleaseCapture() {
// return fmt.Errorf("unable to release mouse capture")
//}
//w32.SendMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0)
return nil
f.ExecJS(`window.wails.Callback(` + strconv.Quote(message) + `);`)
}
func (f *Frontend) ExecJS(js string) {
//f.mainWindow.Dispatch(func() {
// f.chromium.Eval(js)
//})
f.mainWindow.ExecJS(js)
}
//func (f *Frontend) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) {
// if f.frontendOptions.OnDomReady != nil {
// go f.frontendOptions.OnDomReady(f.ctx)
// }
//
// // If you want to start hidden, return
// if f.frontendOptions.StartHidden {
// return
// }
//
// // Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026
// err := f.chromium.Hide()
// if err != nil {
// log.Fatal(err)
// }
// err = f.chromium.Show()
// if err != nil {
// log.Fatal(err)
// }
// f.mainWindow.Show()
//
//}
func (f *Frontend) processRequest(r *request) {
url := C.GoString(r.url)
url = strings.TrimPrefix(url, "wails://wails")
if !strings.HasPrefix(url, "/") {
return
}
_contents, _mimetype, err := f.assets.Load(url)
if err != nil {
f.logger.Error(err.Error())
//TODO: Handle errors
return
}
var data unsafe.Pointer
if _contents != nil {
data = unsafe.Pointer(&_contents[0])
}
mimetype := C.CString(_mimetype)
defer C.free(unsafe.Pointer(mimetype))
C.ProcessURLResponse(r.ctx, r.url, mimetype, data, C.int(len(_contents)))
}
//export processMessage
func processMessage(message *C.char) {
goMessage := C.GoString(message)
messageBuffer <- goMessage
}
//export processURLRequest
func processURLRequest(ctx unsafe.Pointer, url *C.char) {
requestBuffer <- &request{
url: url,
ctx: ctx,
}
}
//export processCallback
func processCallback(callbackID uint) {
callbackBuffer <- callbackID
}

View File

@@ -0,0 +1,237 @@
//go:build ignore
// main.m
// test
//
// Created by Lea Anthony on 10/10/21.
//
// ****** This file is used for testing purposes only ******
#import <Foundation/Foundation.h>
#import "Application.h"
void processMessage(const char*t) {
NSLog(@"processMessage called");
}
void processMessageDialogResponse(int t) {
NSLog(@"processMessage called");
}
void processOpenFileDialogResponse(const char *t) {
NSLog(@"processMessage called %s", t);
}
void processSaveFileDialogResponse(const char *t) {
NSLog(@"processMessage called %s", t);
}
void processCallback(int callbackID) {
NSLog(@"Process callback %d", callbackID);
}
void processURLRequest(void *ctx, const char* url) {
NSLog(@"processURLRequest called");
const char myByteArray[] = { 0x3c,0x68,0x31,0x3e,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21,0x3c,0x2f,0x68,0x31,0x3e };
ProcessURLResponse(ctx, url, "text/html", (void*)myByteArray, 21);
}
unsigned char _Users_username_Pictures_SaltBae_png[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
0x08, 0x06, 0x00, 0x00, 0x00, 0x8d, 0x89, 0x1d, 0x0d, 0x00, 0x00, 0x00,
0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61,
0x05, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, 0x52, 0x4d, 0x00, 0x00, 0x7a,
0x26, 0x00, 0x00, 0x80, 0x84, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x80,
0xe8, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0xea, 0x60, 0x00, 0x00, 0x3a,
0x98, 0x00, 0x00, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x00, 0x00, 0x00,
0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b,
0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x01, 0xd5, 0x69, 0x54,
0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64,
0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78,
0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62,
0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20,
0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, 0x4d, 0x50,
0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22,
0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44,
0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d,
0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f,
0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79,
0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64,
0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78,
0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68,
0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f,
0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f,
0x31, 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x43, 0x6f,
0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c,
0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72,
0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c,
0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x50, 0x68,
0x6f, 0x74, 0x6f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x49, 0x6e, 0x74,
0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e,
0x32, 0x3c, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x50, 0x68, 0x6f, 0x74,
0x6f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72,
0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44,
0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a,
0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46,
0x3e, 0x0a, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74,
0x61, 0x3e, 0x0a, 0x02, 0xd8, 0x80, 0x05, 0x00, 0x00, 0x04, 0xdc, 0x49,
0x44, 0x41, 0x54, 0x38, 0x11, 0x1d, 0x94, 0x49, 0x6c, 0x1b, 0x65, 0x18,
0x86, 0x9f, 0x99, 0xf9, 0x67, 0xc6, 0x6b, 0xbc, 0x26, 0xce, 0xda, 0xa4,
0x25, 0x69, 0x0b, 0x2d, 0x28, 0x34, 0x2c, 0x95, 0x00, 0x89, 0x45, 0x08,
0x5a, 0x95, 0x03, 0x08, 0x09, 0x21, 0xe0, 0x80, 0x38, 0xc3, 0x85, 0x03,
0xe2, 0x00, 0x47, 0xc4, 0x1d, 0x38, 0x70, 0xe3, 0xc6, 0x01, 0x01, 0x42,
0x20, 0x54, 0x7a, 0x2a, 0x6b, 0x0b, 0x94, 0xd2, 0xd2, 0x25, 0x69, 0x9b,
0xa4, 0x0d, 0x2d, 0xa9, 0xb3, 0x78, 0x89, 0x9d, 0xf1, 0x2c, 0x9e, 0x85,
0x2f, 0xb5, 0x35, 0xb6, 0x35, 0x96, 0xde, 0x79, 0xdf, 0xef, 0x7f, 0x9f,
0x4f, 0xfb, 0xe0, 0xad, 0x37, 0x12, 0xfd, 0xf0, 0xb3, 0x9c, 0xfb, 0xb7,
0xc5, 0x8d, 0x46, 0x9b, 0x71, 0x5b, 0xf1, 0xd0, 0xf4, 0x18, 0xdb, 0xeb,
0x4b, 0x1c, 0xff, 0xf1, 0x57, 0x98, 0xdc, 0x87, 0x72, 0x3a, 0x8c, 0x3a,
0xcb, 0x8c, 0xea, 0x31, 0x35, 0xb7, 0xc3, 0x99, 0xba, 0xc3, 0xd7, 0xab,
0x3e, 0x87, 0x2a, 0x8a, 0xb3, 0xff, 0xdc, 0xe0, 0x9b, 0x8f, 0x5f, 0xa2,
0x1c, 0xc5, 0xfc, 0x72, 0xc9, 0x41, 0x99, 0x71, 0x48, 0xca, 0x84, 0x3c,
0x3e, 0xda, 0xd2, 0x05, 0x9a, 0xb1, 0xc7, 0x35, 0x67, 0x1c, 0xdd, 0x4c,
0x68, 0xeb, 0x26, 0xd9, 0x30, 0x26, 0x09, 0x23, 0x5c, 0x3f, 0xc2, 0xd3,
0x43, 0xc2, 0x24, 0x21, 0x4e, 0x34, 0x40, 0x27, 0x89, 0x13, 0xf9, 0x1e,
0x22, 0x6e, 0xd5, 0x45, 0x43, 0x63, 0xc6, 0xd2, 0x50, 0xa9, 0xc4, 0x67,
0x24, 0x15, 0x72, 0xa9, 0x7e, 0x95, 0xfa, 0x4f, 0x27, 0x78, 0x64, 0x76,
0x86, 0x23, 0x61, 0xc0, 0xf0, 0x58, 0x15, 0xc3, 0x29, 0x71, 0x06, 0x45,
0x2e, 0xa5, 0x48, 0xbb, 0x0a, 0x3d, 0x89, 0xa0, 0x8f, 0x08, 0x8a, 0x8e,
0x08, 0xbb, 0xc1, 0x8e, 0xb0, 0x8d, 0xdd, 0x0f, 0xc9, 0x84, 0x06, 0x65,
0x34, 0xf4, 0xed, 0x8d, 0xff, 0x58, 0xbd, 0xfc, 0x27, 0x17, 0x2f, 0x9e,
0xe3, 0xf0, 0x81, 0x49, 0x5e, 0xde, 0x5f, 0xe1, 0x9e, 0x82, 0xcd, 0xdc,
0x78, 0x8d, 0xd9, 0xb2, 0xc9, 0x56, 0x12, 0x32, 0x94, 0x4f, 0x91, 0xcb,
0x88, 0x68, 0xda, 0x42, 0x13, 0x77, 0x11, 0xa2, 0xa8, 0xc3, 0x5a, 0x5f,
0x46, 0x30, 0x65, 0x52, 0x29, 0xe4, 0x24, 0x4d, 0x8e, 0xcc, 0x68, 0x19,
0xe5, 0x76, 0xbb, 0xac, 0x5c, 0x98, 0xa7, 0xb3, 0xed, 0xd0, 0x37, 0x62,
0xa2, 0xb0, 0xc7, 0x89, 0xe5, 0x2e, 0x03, 0x0d, 0x97, 0x95, 0x46, 0x8f,
0x31, 0xd7, 0xa6, 0x63, 0x81, 0x65, 0x25, 0x84, 0xba, 0x45, 0x5f, 0x65,
0x31, 0x2c, 0x71, 0x6b, 0x77, 0x69, 0xf5, 0x7a, 0xbc, 0xb0, 0x3b, 0xcd,
0xf9, 0xa5, 0x90, 0xd1, 0xb0, 0xcd, 0xd4, 0xb0, 0xdc, 0xd7, 0xc4, 0xfa,
0xf0, 0x78, 0x95, 0x7b, 0x27, 0xab, 0x5c, 0x5e, 0x6e, 0xd2, 0xee, 0x05,
0xdc, 0xd8, 0xea, 0xf1, 0xf7, 0xe2, 0x1a, 0xc7, 0xee, 0x1a, 0x62, 0x2e,
0x1f, 0xe3, 0xe8, 0xb6, 0xc4, 0x4c, 0xd3, 0x6d, 0x6e, 0xd0, 0x6b, 0xfc,
0x4c, 0xe3, 0xd4, 0x1f, 0xc4, 0x4b, 0xf3, 0x1c, 0x2c, 0x65, 0x29, 0x67,
0x4d, 0xbe, 0xfb, 0xad, 0x45, 0x65, 0x0c, 0xea, 0x7e, 0x1f, 0x15, 0x6b,
0x09, 0x0b, 0x8b, 0xb7, 0x19, 0xc9, 0xa5, 0x78, 0x75, 0x6e, 0x18, 0xdf,
0xf5, 0x79, 0x72, 0xd0, 0xa2, 0x2d, 0xb3, 0x3a, 0xbb, 0xb4, 0x41, 0x3e,
0x53, 0xe6, 0xf4, 0xca, 0x3c, 0xa5, 0x7c, 0x86, 0xe9, 0xfd, 0x47, 0x18,
0x2e, 0xbd, 0xce, 0xd1, 0x97, 0x26, 0x78, 0xbc, 0x7e, 0x1d, 0xff, 0xcc,
0xa7, 0x5c, 0x71, 0x74, 0x16, 0xe3, 0x18, 0xd7, 0x1e, 0x23, 0xe8, 0xac,
0xa3, 0x0c, 0xcd, 0x60, 0x22, 0x6f, 0x43, 0x36, 0x43, 0x3b, 0x19, 0xc6,
0x08, 0x7a, 0xe0, 0x6c, 0xe3, 0x27, 0x8a, 0xdb, 0x4e, 0xc0, 0xd4, 0xa0,
0xcd, 0x27, 0xaf, 0xbd, 0xcb, 0x86, 0x36, 0xc6, 0xcc, 0xfe, 0x59, 0xd2,
0xca, 0x90, 0x93, 0x36, 0x70, 0xaf, 0x9c, 0xe4, 0xcb, 0x6f, 0x65, 0x54,
0xd9, 0x47, 0x59, 0x70, 0xbb, 0x74, 0x1b, 0x0e, 0x89, 0xe7, 0xa3, 0xc7,
0x12, 0x39, 0x63, 0xea, 0x68, 0x12, 0x6b, 0x53, 0x5c, 0x9e, 0xef, 0x76,
0xf0, 0x55, 0x86, 0x0d, 0x17, 0x56, 0x9a, 0x4d, 0x94, 0x95, 0x65, 0xe6,
0xbe, 0x67, 0x98, 0xbe, 0xfb, 0x21, 0x52, 0xd2, 0x43, 0xaf, 0x5d, 0x47,
0x6b, 0x5c, 0xa3, 0x59, 0xbf, 0xc2, 0x62, 0xdd, 0x26, 0xa5, 0x12, 0x6a,
0x41, 0x44, 0xdf, 0xbd, 0xcd, 0x92, 0x17, 0xa0, 0xb6, 0x03, 0x43, 0xba,
0x66, 0x91, 0xe9, 0xdc, 0xc2, 0xce, 0xed, 0xa1, 0xfc, 0xc0, 0x2b, 0x14,
0xff, 0xfd, 0x1e, 0x4b, 0xb3, 0xa9, 0x29, 0x87, 0x81, 0xd2, 0x04, 0x8e,
0x66, 0x89, 0x58, 0x00, 0x7e, 0x07, 0xaf, 0xdb, 0xa4, 0xbb, 0xb5, 0x49,
0xb9, 0xaa, 0x18, 0xb9, 0x77, 0x8e, 0xcd, 0xdb, 0x6d, 0x1e, 0x1c, 0xb5,
0x38, 0x7d, 0xa5, 0xcf, 0xaa, 0x08, 0xeb, 0x77, 0x3f, 0x35, 0xc7, 0xda,
0xfc, 0x02, 0xaa, 0xf6, 0x1c, 0xbb, 0x9f, 0x78, 0x9f, 0x89, 0x43, 0x47,
0xa4, 0x6f, 0x3d, 0x06, 0xed, 0x90, 0x92, 0x79, 0x95, 0xd4, 0xe4, 0xfd,
0x98, 0x66, 0x4a, 0x6a, 0xd7, 0xc7, 0x0b, 0x62, 0xa4, 0xe3, 0x8c, 0x4d,
0xc4, 0xe8, 0x85, 0x98, 0xe5, 0x46, 0x44, 0x26, 0x97, 0x21, 0xe9, 0xf7,
0xf9, 0x61, 0xc5, 0xe3, 0xd4, 0x66, 0x84, 0xd2, 0x70, 0xc9, 0xee, 0x79,
0x98, 0x43, 0xc7, 0x5e, 0x27, 0xb6, 0x8a, 0xd2, 0x5a, 0x1f, 0xf3, 0xa9,
0xf7, 0x88, 0xce, 0x7d, 0x85, 0x71, 0xe0, 0x79, 0x98, 0x7a, 0x90, 0x9e,
0x1b, 0xd0, 0x13, 0x52, 0x4a, 0x66, 0x97, 0x7d, 0x33, 0x1e, 0xed, 0xae,
0xc7, 0x87, 0x1f, 0x7d, 0xce, 0xc2, 0xd5, 0x3a, 0xe6, 0xde, 0x02, 0xcb,
0xdb, 0x3e, 0xbe, 0xa6, 0x91, 0x95, 0x62, 0x6b, 0x2f, 0xce, 0x90, 0x3c,
0xfd, 0xce, 0x71, 0x0e, 0xcc, 0x3e, 0x82, 0x13, 0xf4, 0x09, 0xd5, 0x00,
0x16, 0x82, 0x98, 0xb3, 0x49, 0x24, 0xb1, 0x83, 0xc8, 0xc0, 0xd6, 0x3a,
0x54, 0x33, 0xab, 0x14, 0x8c, 0x16, 0x4e, 0x38, 0xcc, 0xe5, 0xeb, 0x4d,
0x5e, 0x7b, 0xfb, 0x4d, 0xaa, 0x79, 0xa1, 0x45, 0x1c, 0x9b, 0xd2, 0x94,
0xcc, 0x0e, 0x8c, 0x52, 0x7a, 0x65, 0x17, 0xc7, 0xa9, 0x0c, 0x8e, 0xe2,
0xf7, 0xba, 0xa8, 0xc8, 0x13, 0x87, 0x32, 0x87, 0x0b, 0x27, 0x30, 0x36,
0x57, 0xe8, 0xea, 0x15, 0xce, 0x06, 0x65, 0x5e, 0x3d, 0x5a, 0x94, 0x53,
0xb7, 0x59, 0x58, 0xdf, 0x25, 0xc4, 0xe4, 0xc9, 0x65, 0x3d, 0xb4, 0xb4,
0x4e, 0x37, 0x0c, 0x29, 0x98, 0x4a, 0xe8, 0x11, 0xde, 0x85, 0x42, 0x43,
0x1c, 0xaa, 0x38, 0x55, 0xc4, 0xb4, 0x2c, 0x22, 0x3d, 0xcd, 0xfa, 0xea,
0x0d, 0xf4, 0x8d, 0x1f, 0xc9, 0x5f, 0xfa, 0x82, 0x6d, 0xc7, 0xe1, 0xa6,
0x57, 0xe3, 0x56, 0x6e, 0x96, 0xbf, 0x16, 0x1f, 0xa3, 0x54, 0xaa, 0x91,
0x16, 0x5a, 0xb2, 0xa9, 0x04, 0xaf, 0x67, 0xc9, 0xac, 0x6c, 0xfa, 0x32,
0x9e, 0x48, 0xea, 0xa5, 0x0b, 0x89, 0x3b, 0x54, 0x47, 0xf2, 0xa1, 0xf2,
0x2a, 0x4d, 0xeb, 0xf4, 0x17, 0xdc, 0xd4, 0x72, 0x6c, 0xb5, 0x36, 0x28,
0xb6, 0x7e, 0x17, 0x04, 0xd3, 0xac, 0x7a, 0x42, 0xc1, 0xf4, 0x6e, 0x9e,
0xbf, 0x6b, 0xb7, 0x3c, 0x3a, 0x21, 0x67, 0xcb, 0x41, 0x48, 0x07, 0x91,
0xde, 0x1a, 0xe2, 0xaa, 0x9c, 0xb1, 0x59, 0xdb, 0x12, 0x25, 0xc1, 0x32,
0x92, 0xea, 0xc9, 0xaf, 0x3b, 0x97, 0xca, 0xca, 0xfe, 0x5b, 0xfe, 0xe5,
0x33, 0x29, 0xeb, 0x16, 0x95, 0xd2, 0x24, 0xeb, 0xda, 0x30, 0xeb, 0x95,
0x1a, 0xd3, 0xf7, 0x0f, 0x51, 0x1c, 0xd9, 0x0b, 0x99, 0x12, 0x7a, 0x4a,
0xd0, 0xd3, 0x25, 0x9a, 0x88, 0x45, 0xb1, 0x04, 0x33, 0x2c, 0x8a, 0x99,
0x34, 0x6b, 0x75, 0x19, 0x91, 0x9d, 0x92, 0x29, 0x89, 0xa0, 0x2c, 0x8b,
0x9d, 0xd8, 0x7a, 0x5e, 0x04, 0x07, 0x87, 0x66, 0x28, 0x56, 0x67, 0xb9,
0xd6, 0xd2, 0x39, 0xd9, 0xec, 0x33, 0x30, 0xb2, 0x8b, 0xea, 0xae, 0x83,
0x18, 0xb9, 0x31, 0x34, 0xbb, 0x42, 0x22, 0x0b, 0x21, 0x96, 0x3c, 0x61,
0xac, 0xcb, 0x95, 0x60, 0x2a, 0xe9, 0x68, 0x79, 0x08, 0x36, 0x56, 0x65,
0x27, 0x4a, 0xd9, 0x83, 0x00, 0xcf, 0x0b, 0xf1, 0xfc, 0x10, 0x15, 0x0a,
0x6a, 0x75, 0x77, 0x8b, 0x86, 0xdc, 0x58, 0x57, 0x45, 0x52, 0xe9, 0x84,
0x81, 0x7c, 0x91, 0x28, 0x55, 0x23, 0x96, 0x13, 0xd7, 0x24, 0xbe, 0xac,
0x17, 0xfa, 0xf2, 0x78, 0x63, 0xc7, 0x82, 0x08, 0xda, 0xa6, 0xc5, 0x50,
0x55, 0x04, 0xe5, 0x65, 0x5b, 0x06, 0xde, 0xce, 0xf0, 0x24, 0xf3, 0x4e,
0x70, 0xb5, 0x15, 0x6a, 0x34, 0x7b, 0x11, 0x9d, 0xbe, 0x10, 0x53, 0xd0,
0xa8, 0x86, 0x2e, 0x76, 0xb6, 0x2a, 0x9d, 0x2c, 0x48, 0x3c, 0x5b, 0xa2,
0xc8, 0x3a, 0x37, 0xd4, 0x9d, 0xed, 0x6c, 0x4a, 0xab, 0x95, 0x6e, 0x08,
0x66, 0x3d, 0x5a, 0xad, 0x4d, 0x18, 0xc8, 0xca, 0xfa, 0xd5, 0x85, 0x6f,
0xf9, 0x5f, 0xde, 0x02, 0x30, 0xff, 0x03, 0x8c, 0x47, 0x35, 0xad, 0xbc,
0xbf, 0x26, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82
};
unsigned int _Users_username_Pictures_SaltBae_png_len = 1863;
int main(int argc, const char * argv[]) {
// insert code here...
int frameless = 1;
int resizable = 0;
int fullscreen = 0;
int fullSizeContent = 1;
int hideTitleBar = 0;
int titlebarAppearsTransparent = 0;
int hideTitle = 0;
int useToolbar = 0;
int hideToolbarSeparator = 0;
int webviewIsTransparent = 1;
int alwaysOnTop = 0;
int hideWindowOnClose = 0;
const char* appearance = "NSAppearanceNameDarkAqua";
int windowIsTranslucent = 1;
int debug = 1;
WailsContext *result = Create("OI OI!",400,400, frameless, resizable, fullscreen, fullSizeContent, hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent, alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, debug);
SetRGBA(result, 255, 0, 0, 255);
void *m = NewMenu("");
SetAbout(result, "Fake title", "I am a description", _Users_username_Pictures_SaltBae_png, _Users_username_Pictures_SaltBae_png_len);
// AddMenuByRole(result, 1);
AppendRole(result, m, 1);
AppendRole(result, m, 2);
void* submenu = NewMenu("test");
void* menuITem = AppendMenuItem(result, submenu, "Woohoo", "p", 0, 0, 0, 470);
AppendSubmenu(m, submenu);
UpdateMenuItem(menuITem, 1);
SetAsApplicationMenu(result, m);
SetPosition(result, 100, 100);
Run((void*)CFBridgingRetain(result));
return 0;
}

View File

@@ -1,97 +1,135 @@
//go:build darwin
// +build darwin
package darwin
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
#import <Foundation/Foundation.h>
#import "Application.h"
#import "WailsContext.h"
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
)
type NSMenu struct {
context unsafe.Pointer
nsmenu unsafe.Pointer
}
func NewNSMenu(context unsafe.Pointer, name string) *NSMenu {
c := NewCalloc()
defer c.Free()
title := c.String(name)
nsmenu := C.NewMenu(title)
return &NSMenu{
context: context,
nsmenu: nsmenu,
}
}
func (m *NSMenu) AddSubMenu(label string) *NSMenu {
result := NewNSMenu(m.context, label)
C.AppendSubmenu(m.nsmenu, result.nsmenu)
return result
}
func (m *NSMenu) AppendRole(role menu.Role) {
C.AppendRole(m.context, m.nsmenu, C.int(role))
}
type MenuItem struct {
id uint
nsmenuitem unsafe.Pointer
wailsMenuItem *menu.MenuItem
radioGroupMembers []*MenuItem
}
func (m *NSMenu) AddMenuItem(menuItem *menu.MenuItem) *MenuItem {
c := NewCalloc()
defer c.Free()
var modifier C.int
var key *C.char
if menuItem.Accelerator != nil {
modifier = C.int(keys.ToMacModifier(menuItem.Accelerator))
key = c.String(menuItem.Accelerator.Key)
}
result := &MenuItem{
wailsMenuItem: menuItem,
}
result.id = createMenuItemID(result)
result.nsmenuitem = C.AppendMenuItem(m.context, m.nsmenu, c.String(menuItem.Label), key, modifier, bool2Cint(menuItem.Disabled), bool2Cint(menuItem.Checked), C.int(result.id))
return result
}
//func (w *Window) SetApplicationMenu(menu *menu.Menu) {
//w.applicationMenu = menu
//processMenu(w, menu)
//}
//func processMenu(window *Window, menu *menu.Menu) {
//mainMenu := window.NewMenu()
//for _, menuItem := range menu.Items {
// submenu := mainMenu.AddSubMenu(menuItem.Label)
// for _, menuItem := range menuItem.SubMenu.Items {
// processMenuItem(submenu, menuItem)
// }
//}
//mainMenu.Show()
//}
func processMenu(parent *NSMenu, wailsMenu *menu.Menu) {
var radioGroups []*MenuItem
//func processMenuItem(parent *winc.MenuItem, menuItem *menu.MenuItem) {
// if menuItem.Hidden {
// return
// }
// switch menuItem.Type {
// case menu.SeparatorType:
// parent.AddSeparator()
// case menu.TextType:
// shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
// newItem := parent.AddItem(menuItem.Label, shortcut)
// if menuItem.Tooltip != "" {
// newItem.SetToolTip(menuItem.Tooltip)
// }
// if menuItem.Click != nil {
// newItem.OnClick().Bind(func(e *winc.Event) {
// menuItem.Click(&menu.CallbackData{
// MenuItem: menuItem,
// })
// })
// }
// newItem.SetEnabled(!menuItem.Disabled)
//
// case menu.CheckboxType:
// shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
// newItem := parent.AddItem(menuItem.Label, shortcut)
// newItem.SetCheckable(true)
// newItem.SetChecked(menuItem.Checked)
// if menuItem.Tooltip != "" {
// newItem.SetToolTip(menuItem.Tooltip)
// }
// if menuItem.Click != nil {
// newItem.OnClick().Bind(func(e *winc.Event) {
// toggleCheckBox(menuItem)
// menuItem.Click(&menu.CallbackData{
// MenuItem: menuItem,
// })
// })
// }
// newItem.SetEnabled(!menuItem.Disabled)
// addCheckBoxToMap(menuItem, newItem)
// case menu.RadioType:
// shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
// newItem := parent.AddItemRadio(menuItem.Label, shortcut)
// newItem.SetCheckable(true)
// newItem.SetChecked(menuItem.Checked)
// if menuItem.Tooltip != "" {
// newItem.SetToolTip(menuItem.Tooltip)
// }
// if menuItem.Click != nil {
// newItem.OnClick().Bind(func(e *winc.Event) {
// toggleRadioItem(menuItem)
// menuItem.Click(&menu.CallbackData{
// MenuItem: menuItem,
// })
// })
// }
// newItem.SetEnabled(!menuItem.Disabled)
// addRadioItemToMap(menuItem, newItem)
// case menu.SubmenuType:
// submenu := parent.AddSubMenu(menuItem.Label)
// for _, menuItem := range menuItem.SubMenu.Items {
// processMenuItem(submenu, menuItem)
// }
// }
//}
for _, menuItem := range wailsMenu.Items {
if menuItem.SubMenu != nil {
if len(radioGroups) > 0 {
processRadioGroups(radioGroups)
radioGroups = []*MenuItem{}
}
submenu := parent.AddSubMenu(menuItem.Label)
processMenu(submenu, menuItem.SubMenu)
} else {
lastMenuItem := processMenuItem(parent, menuItem)
if menuItem.Type == menu.RadioType {
radioGroups = append(radioGroups, lastMenuItem)
} else {
if len(radioGroups) > 0 {
processRadioGroups(radioGroups)
radioGroups = []*MenuItem{}
}
}
}
}
}
func processRadioGroups(groups []*MenuItem) {
for _, item := range groups {
item.radioGroupMembers = groups
}
}
func processMenuItem(parent *NSMenu, menuItem *menu.MenuItem) *MenuItem {
if menuItem.Hidden {
return nil
}
if menuItem.Role != 0 {
parent.AppendRole(menuItem.Role)
return nil
}
if menuItem.Type == menu.SeparatorType {
C.AppendSeparator(parent.nsmenu)
return nil
}
return parent.AddMenuItem(menuItem)
}
func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
//f.mainWindow.SetApplicationMenu(menu)
f.mainWindow.SetApplicationMenu(menu)
}
func (f *Frontend) MenuUpdateApplicationMenu() {
//processMenu(f.mainWindow, f.mainWindow.applicationMenu)
f.MenuSetApplicationMenu(f.frontendOptions.Menu)
f.mainWindow.UpdateApplicationMenu()
}

View File

@@ -0,0 +1,51 @@
//go:build darwin
// +build darwin
package darwin
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
#import <Foundation/Foundation.h>
#import "Application.h"
#import "WailsContext.h"
#include <stdlib.h>
*/
import "C"
import (
"log"
"math"
"sync"
)
var menuItemToID = make(map[*MenuItem]uint)
var idToMenuItem = make(map[uint]*MenuItem)
var menuItemLock sync.Mutex
var menuItemIDCounter uint = 0
func createMenuItemID(item *MenuItem) uint {
menuItemLock.Lock()
defer menuItemLock.Unlock()
counter := 0
for {
menuItemIDCounter++
value := idToMenuItem[menuItemIDCounter]
if value == nil {
break
}
counter++
if counter == math.MaxInt {
log.Fatal("insane amounts of menuitems detected! Aborting before the collapse of the world!")
}
}
idToMenuItem[menuItemIDCounter] = item
menuItemToID[item] = menuItemIDCounter
return menuItemIDCounter
}
func getMenuItemForID(id uint) *MenuItem {
menuItemLock.Lock()
defer menuItemLock.Unlock()
return idToMenuItem[id]
}

View File

@@ -0,0 +1,29 @@
//
// message.h
// test
//
// Created by Lea Anthony on 14/10/21.
//
#ifndef export_h
#define export_h
#ifdef __cplusplus
extern "C"
{
#endif
void processMessage(const char *);
void processURLRequest(void*, const char *);
void processMessageDialogResponse(int);
void processOpenFileDialogResponse(const char*);
void processSaveFileDialogResponse(const char*);
void processCallback(int);
#ifdef __cplusplus
}
#endif
#endif /* export_h */

View File

@@ -0,0 +1,226 @@
package darwin
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
#import <Foundation/Foundation.h>
#import "Application.h"
#import "WailsContext.h"
#include <stdlib.h>
*/
import "C"
import (
"log"
"runtime"
"strconv"
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
)
func init() {
runtime.LockOSThread()
}
type Window struct {
context unsafe.Pointer
}
func bool2Cint(value bool) C.int {
if value {
return C.int(1)
}
return C.int(0)
}
func NewWindow(frontendOptions *options.App, debugMode bool) *Window {
c := NewCalloc()
defer c.Free()
frameless := bool2Cint(frontendOptions.Frameless)
resizable := bool2Cint(!frontendOptions.DisableResize)
fullscreen := bool2Cint(frontendOptions.Fullscreen)
alwaysOnTop := bool2Cint(frontendOptions.AlwaysOnTop)
hideWindowOnClose := bool2Cint(frontendOptions.HideWindowOnClose)
debug := bool2Cint(debugMode)
var fullSizeContent, hideTitleBar, hideTitle, useToolbar, webviewIsTransparent C.int
var titlebarAppearsTransparent, hideToolbarSeparator, windowIsTranslucent C.int
var appearance, title *C.char
width := C.int(frontendOptions.Width)
height := C.int(frontendOptions.Height)
title = c.String(frontendOptions.Title)
if frontendOptions.Mac != nil {
mac := frontendOptions.Mac
if mac.TitleBar != nil {
fullSizeContent = bool2Cint(mac.TitleBar.FullSizeContent)
hideTitleBar = bool2Cint(mac.TitleBar.HideTitleBar)
hideTitle = bool2Cint(mac.TitleBar.HideTitle)
useToolbar = bool2Cint(mac.TitleBar.UseToolbar)
titlebarAppearsTransparent = bool2Cint(mac.TitleBar.TitlebarAppearsTransparent)
hideToolbarSeparator = bool2Cint(mac.TitleBar.HideToolbarSeparator)
}
windowIsTranslucent = bool2Cint(mac.WindowIsTranslucent)
webviewIsTransparent = bool2Cint(mac.WebviewIsTransparent)
appearance = c.String(string(mac.Appearance))
}
var context *C.WailsContext = C.Create(title, width, height, frameless, resizable, fullscreen, fullSizeContent,
hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent,
alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, debug)
// Create menu
result := &Window{
context: unsafe.Pointer(context),
}
if frontendOptions.RGBA != nil {
result.SetRGBA(frontendOptions.RGBA.R, frontendOptions.RGBA.G, frontendOptions.RGBA.B, frontendOptions.RGBA.A)
}
if frontendOptions.Mac != nil && frontendOptions.Mac.About != nil {
title := c.String(frontendOptions.Mac.About.Title)
description := c.String(frontendOptions.Mac.About.Message)
var icon unsafe.Pointer
var length C.int
if frontendOptions.Mac.About.Icon != nil {
icon = unsafe.Pointer(&frontendOptions.Mac.About.Icon[0])
length = C.int(len(frontendOptions.Mac.About.Icon))
}
C.SetAbout(result.context, title, description, icon, length)
}
if frontendOptions.Menu != nil {
result.SetApplicationMenu(frontendOptions.Menu)
}
result.SetMinSize(frontendOptions.MinWidth, frontendOptions.MinHeight)
result.SetMaxSize(frontendOptions.MaxWidth, frontendOptions.MaxHeight)
return result
}
func (w *Window) Center() {
C.Center(w.context)
}
func (w *Window) Run() {
C.Run(w.context)
}
func (w *Window) Quit() {
C.Quit(w.context)
}
func (w *Window) SetRGBA(r uint8, g uint8, b uint8, a uint8) {
C.SetRGBA(w.context, C.int(r), C.int(g), C.int(b), C.int(a))
}
func (w *Window) ExecJS(js string) {
_js := C.CString(js)
C.ExecJS(w.context, _js)
C.free(unsafe.Pointer(_js))
}
func (w *Window) SetPos(x int, y int) {
C.SetPosition(w.context, C.int(x), C.int(y))
}
func (w *Window) SetSize(width int, height int) {
C.SetSize(w.context, C.int(width), C.int(height))
}
func (w *Window) SetTitle(title string) {
t := C.CString(title)
C.SetTitle(w.context, t)
C.free(unsafe.Pointer(t))
}
func (w *Window) Maximise() {
C.Maximise(w.context)
}
func (w *Window) UnMaximise() {
C.UnMaximise(w.context)
}
func (w *Window) Minimise() {
C.Minimise(w.context)
}
func (w *Window) UnMinimise() {
C.UnMinimise(w.context)
}
func (w *Window) SetMinSize(width int, height int) {
if width == 0 && height == 0 {
return
}
C.SetMinSize(w.context, C.int(width), C.int(height))
}
func (w *Window) SetMaxSize(width int, height int) {
if width == 0 && height == 0 {
return
}
C.SetMaxSize(w.context, C.int(width), C.int(height))
}
func (w *Window) Fullscreen() {
C.Fullscreen(w.context)
}
func (w *Window) UnFullscreen() {
C.UnFullscreen(w.context)
}
func (w *Window) Show() {
C.Show(w.context)
}
func (w *Window) Hide() {
C.Hide(w.context)
}
func parseIntDuo(temp string) (int, int) {
split := strings.Split(temp, ",")
x, err := strconv.Atoi(split[0])
if err != nil {
log.Fatal(err)
}
y, err := strconv.Atoi(split[1])
if err != nil {
log.Fatal(err)
}
return x, y
}
func (w *Window) Pos() (int, int) {
var _result *C.char = C.GetPos(w.context)
temp := C.GoString(_result)
return parseIntDuo(temp)
}
func (w *Window) Size() (int, int) {
var _result *C.char = C.GetSize(w.context)
temp := C.GoString(_result)
return parseIntDuo(temp)
}
func (w *Window) SetApplicationMenu(inMenu *menu.Menu) {
mainMenu := NewNSMenu(w.context, "")
processMenu(mainMenu, inMenu)
C.SetAsApplicationMenu(w.context, mainMenu.nsmenu)
}
func (w *Window) UpdateApplicationMenu() {
C.UpdateApplicationMenu(w.context)
}

View File

@@ -1,3 +1,6 @@
//go:build windows
// +build windows
package windows
import (

View File

@@ -135,6 +135,7 @@ func (f *Frontend) Run(ctx context.Context) error {
}
mainWindow.Run()
mainWindow.Close()
return nil
}
@@ -257,19 +258,6 @@ func (f *Frontend) Quit() {
winc.Exit()
}
const (
ctrlZ int = 90
ctrlX = 88
ctrlC = 67
ctrlV = 86
ctrlA = 65
arrowUp = 38
arrowDown = 40
arrowRight = 39
arrowLeft = 37
keyDel = 46
)
func (f *Frontend) setupChromium() {
chromium := edge.NewChromium()
f.chromium = chromium
@@ -306,10 +294,15 @@ func (f *Frontend) setupChromium() {
if err != nil {
log.Fatal(err)
}
err = settings.PutIsSwipeNavigationEnabled(false)
if err != nil {
log.Fatal(err)
}
// Set background colour
f.WindowSetRGBA(f.frontendOptions.RGBA)
chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow)
chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)
chromium.Navigate(f.startURL)
}

View File

@@ -30,6 +30,9 @@ func NewWindow(parent winc.Controller, options *options.App) *Window {
exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP
}
}
if options.AlwaysOnTop {
exStyle |= w32.WS_EX_TOPMOST
}
var dwStyle = w32.WS_OVERLAPPEDWINDOW
if options.Frameless {

View File

@@ -2,8 +2,9 @@ package dispatcher
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/frontend"
"strings"
"github.com/wailsapp/wails/v2/internal/frontend"
)
const systemCallPrefix = ":wails:"
@@ -29,7 +30,7 @@ func (d *Dispatcher) processSystemCall(payload callMessage, sender frontend.Fron
return &position{x, y}, nil
case "WindowGetSize":
w, h := sender.WindowGetSize()
return &position{w, h}, nil
return &size{w, h}, nil
default:
return nil, fmt.Errorf("unknown systemcall message: %s", payload.Name)
}

View File

@@ -55,7 +55,7 @@ type MessageDialogOptions struct {
Buttons []string
DefaultButton string
CancelButton string
Icon string
Icon []byte
}
type Frontend interface {

View File

@@ -22,10 +22,18 @@ The electron alternative for Go
while (obj && s.length) obj = obj[s.shift()];
return obj;
};
window.WailsInvoke = _deeptest(["chrome", "webview", "postMessage"]) ||
_deeptest(["webkit", "messageHandlers", "external", "postMessage"]);
let windows = _deeptest(["chrome", "webview", "postMessage"]);
let mac = _deeptest(["webkit", "messageHandlers", "external", "postMessage"]);
if (!window.WailsInvoke) {
if (!windows && !mac) {
console.error("Unsupported Platform");
return;
}
if (windows) {
window.WailsInvoke = (message) => window.chrome.webview.postMessage(message);
}
if (mac) {
window.WailsInvoke = (message) => window.webkit.messageHandlers.external.postMessage(message);
}
})();

View File

@@ -39,7 +39,11 @@ window.wails = {
EventsNotify,
SetBindings,
eventListeners,
callbacks
callbacks,
flags: {
disableScrollbarDrag: false,
disableWailsDefaultContextMenu: false,
}
};
// Set the bindings
@@ -61,9 +65,23 @@ window.addEventListener('mousedown', (e) => {
if (currentElement.hasAttribute('data-wails-no-drag')) {
break;
} else if (currentElement.hasAttribute('data-wails-drag')) {
if (window.wails.flags.disableScrollbarDrag) {
// This checks for clicks on the scroll bar
if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) {
break;
}
}
window.WailsInvoke("drag");
e.preventDefault();
break;
}
currentElement = currentElement.parentElement;
}
});
// Setup context menu hook
window.addEventListener('contextmenu', function (e) {
if (window.wails.flags.disableWailsDefaultContextMenu) {
e.preventDefault();
}
});

View File

@@ -148,6 +148,7 @@
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.5.6.tgz",
"integrity": "sha512-Bz8nU45FrT6sP/Tf3M2rQUuBGxnDSNSPZNIoYwSNt5H+wjSyo/t+zm94tgnOZsR6GgpDMbNQgo4jGbK0NLvEfw==",
"dev": true,
"requires": {
"svelte": "^3.42.6"
},
@@ -155,7 +156,8 @@
"svelte": {
"version": "3.43.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.43.1.tgz",
"integrity": "sha512-nvPIaKx4HLzYlSdquISZpgG1Kqr2VAWQjZOt3Iwm3UhbqmA0LnSx4k1YpRMEhjQYW3ZCqQoK8Egto9tv4YewMA=="
"integrity": "sha512-nvPIaKx4HLzYlSdquISZpgG1Kqr2VAWQjZOt3Iwm3UhbqmA0LnSx4k1YpRMEhjQYW3ZCqQoK8Egto9tv4YewMA==",
"dev": true
}
}
},

View File

@@ -1 +1 @@
(()=>{(function(){let n=function(o){for(var e=window[o.shift()];e&&o.length;)e=e[o.shift()];return e};window.WailsInvoke=n(["chrome","webview","postMessage"])||n(["webkit","messageHandlers","external","postMessage"]),window.WailsInvoke||console.error("Unsupported Platform")})();})();
(()=>{(function(){let o=function(e){for(var s=window[e.shift()];s&&e.length;)s=s[e.shift()];return s},t=o(["chrome","webview","postMessage"]),n=o(["webkit","messageHandlers","external","postMessage"]);if(!t&&!n){console.error("Unsupported Platform");return}t&&(window.WailsInvoke=e=>window.chrome.webview.postMessage(e)),n&&(window.WailsInvoke=e=>window.webkit.messageHandlers.external.postMessage(e))})();})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
(()=>{var g=Object.defineProperty;var h=o=>g(o,"__esModule",{value:!0});var f=(o,n)=>{h(o);for(var e in n)g(o,e,{get:n[e],enumerable:!0})};var W={};f(W,{LogDebug:()=>T,LogError:()=>D,LogFatal:()=>F,LogInfo:()=>C,LogLevel:()=>U,LogPrint:()=>B,LogTrace:()=>R,LogWarning:()=>J,SetLogLevel:()=>G});function l(o,n){window.WailsInvoke("L"+o+n)}function R(o){l("T",o)}function B(o){l("P",o)}function T(o){l("D",o)}function C(o){l("I",o)}function J(o){l("W",o)}function D(o){l("E",o)}function F(o){l("F",o)}function G(o){l("S",o)}var U={TRACE:1,DEBUG:2,INFO:3,WARNING:4,ERROR:5};var E=class{constructor(n,e){e=e||-1,this.Callback=t=>(n.apply(null,t),e===-1?!1:(e-=1,e===0))}},s={};function d(o,n,e){s[o]=s[o]||[];let t=new E(n,e);s[o].push(t)}function I(o,n){d(o,n,-1)}function k(o,n){d(o,n,1)}function m(o){let n=o.name;if(s[n]){let e=s[n].slice();for(let t=0;t<s[n].length;t+=1){let r=s[n][t],i=o.data;r.Callback(i)&&e.splice(t,1)}s[n]=e}}function S(o){let n;try{n=JSON.parse(o)}catch(e){let t="Invalid JSON passed to Notify: "+o;throw new Error(t)}m(n)}function y(o){let n={name:o,data:[].slice.apply(arguments).slice(1)};m(n),window.WailsInvoke("EE"+JSON.stringify(n))}function L(o){s.delete(o),window.WailsInvoke("EX"+o)}var c={};function z(){var o=new Uint32Array(1);return window.crypto.getRandomValues(o)[0]}function A(){return Math.random()*9007199254740991}var p;window.crypto?p=z:p=A;function a(o,n,e){return e==null&&(e=0),new Promise(function(t,r){var i;do i=o+"-"+p();while(c[i]);var w;e>0&&(w=setTimeout(function(){r(Error("Call to "+o+" timed out. Request ID: "+i))},e)),c[i]={timeoutHandle:w,reject:r,resolve:t};try{let u={name:o,args:n,callbackID:i};window.WailsInvoke("C"+JSON.stringify(u))}catch(u){console.error(u)}})}function O(o){var n;try{n=JSON.parse(o)}catch(r){let i=`Invalid JSON passed to callback: ${r.message}. Message: ${o}`;throw wails.LogDebug(i),new Error(i)}var e=n.callbackid,t=c[e];if(!t){let r=`Callback '${e}' not registered!!!`;throw console.error(r),new Error(r)}clearTimeout(t.timeoutHandle),delete c[e],n.error?t.reject(n.error):t.resolve(n.result)}window.go={};function b(o){try{o=JSON.parse(o)}catch(n){console.error(n)}window.go=window.go||{},Object.keys(o).forEach(n=>{window.go[n]=window.go[n]||{},Object.keys(o[n]).forEach(e=>{window.go[n][e]=window.go[n][e]||{},Object.keys(o[n][e]).forEach(t=>{window.go[n][e][t]=function(){let r=0;function i(){let w=[].slice.call(arguments);return a([n,e,t].join("."),w,r)}return i.setTimeout=function(w){r=w},i.getTimeout=function(){return r},i}()})})})}var v={};f(v,{WindowCenter:()=>j,WindowFullscreen:()=>M,WindowGetPosition:()=>Z,WindowGetSize:()=>V,WindowHide:()=>K,WindowMaximise:()=>_,WindowMinimise:()=>no,WindowReload:()=>P,WindowSetMaxSize:()=>q,WindowSetMinSize:()=>N,WindowSetPosition:()=>X,WindowSetRGBA:()=>to,WindowSetSize:()=>Q,WindowSetTitle:()=>H,WindowShow:()=>Y,WindowUnFullscreen:()=>$,WindowUnmaximise:()=>oo,WindowUnminimise:()=>eo});function P(){window.location.reload()}function j(){window.WailsInvoke("Wc")}function H(o){window.WailsInvoke("WT"+o)}function M(){window.WailsInvoke("WF")}function $(){window.WailsInvoke("Wf")}function Q(o,n){window.WailsInvoke("Ws:"+o+":"+n)}function V(){return a(":wails:WindowGetSize")}function q(o,n){window.WailsInvoke("WZ:"+o+":"+n)}function N(o,n){window.WailsInvoke("Wz:"+o+":"+n)}function X(o,n){window.WailsInvoke("Wp:"+o+":"+n)}function Z(){return a(":wails:WindowGetPos")}function K(){window.WailsInvoke("WH")}function Y(){window.WailsInvoke("WS")}function _(){window.WailsInvoke("WM")}function oo(){window.WailsInvoke("WU")}function no(){window.WailsInvoke("Wm")}function eo(){window.WailsInvoke("Wu")}function to(o){let n=JSON.stringify(o);window.WailsInvoke("Wr:"+n)}var x={};f(x,{BrowserOpenURL:()=>io});function io(o){window.WailsInvoke("BO:"+o)}function ro(){window.WailsInvoke("Q")}window.runtime={...W,...v,...x,EventsOn:I,EventsOnce:k,EventsOnMultiple:d,EventsEmit:y,EventsOff:L,Quit:ro};window.wails={Callback:O,EventsNotify:S,SetBindings:b,eventListeners:s,callbacks:c};window.wails.SetBindings(window.wailsbindings);delete window.wails.SetBindings;window.addEventListener("mousedown",o=>{let n=o.target;for(;n!=null&&!n.hasAttribute("data-wails-no-drag");){if(n.hasAttribute("data-wails-drag")){window.WailsInvoke("drag");break}n=n.parentElement}});})();
(()=>{var x=Object.defineProperty;var h=n=>x(n,"__esModule",{value:!0});var u=(n,o)=>{h(n);for(var e in o)x(n,e,{get:o[e],enumerable:!0})};var W={};u(W,{LogDebug:()=>C,LogError:()=>J,LogFatal:()=>F,LogInfo:()=>B,LogLevel:()=>U,LogPrint:()=>R,LogTrace:()=>D,LogWarning:()=>T,SetLogLevel:()=>G});function l(n,o){window.WailsInvoke("L"+n+o)}function D(n){l("T",n)}function R(n){l("P",n)}function C(n){l("D",n)}function B(n){l("I",n)}function T(n){l("W",n)}function J(n){l("E",n)}function F(n){l("F",n)}function G(n){l("S",n)}var U={TRACE:1,DEBUG:2,INFO:3,WARNING:4,ERROR:5};var E=class{constructor(o,e){e=e||-1,this.Callback=t=>(o.apply(null,t),e===-1?!1:(e-=1,e===0))}},s={};function d(n,o,e){s[n]=s[n]||[];let t=new E(o,e);s[n].push(t)}function I(n,o){d(n,o,-1)}function k(n,o){d(n,o,1)}function S(n){let o=n.name;if(s[o]){let e=s[o].slice();for(let t=0;t<s[o].length;t+=1){let r=s[o][t],i=n.data;r.Callback(i)&&e.splice(t,1)}s[o]=e}}function m(n){let o;try{o=JSON.parse(n)}catch(e){let t="Invalid JSON passed to Notify: "+n;throw new Error(t)}S(o)}function b(n){let o={name:n,data:[].slice.apply(arguments).slice(1)};S(o),window.WailsInvoke("EE"+JSON.stringify(o))}function y(n){s.delete(n),window.WailsInvoke("EX"+n)}var a={};function z(){var n=new Uint32Array(1);return window.crypto.getRandomValues(n)[0]}function A(){return Math.random()*9007199254740991}var p;window.crypto?p=z:p=A;function c(n,o,e){return e==null&&(e=0),new Promise(function(t,r){var i;do i=n+"-"+p();while(a[i]);var w;e>0&&(w=setTimeout(function(){r(Error("Call to "+n+" timed out. Request ID: "+i))},e)),a[i]={timeoutHandle:w,reject:r,resolve:t};try{let f={name:n,args:o,callbackID:i};window.WailsInvoke("C"+JSON.stringify(f))}catch(f){console.error(f)}})}function L(n){var o;try{o=JSON.parse(n)}catch(r){let i=`Invalid JSON passed to callback: ${r.message}. Message: ${n}`;throw wails.LogDebug(i),new Error(i)}var e=o.callbackid,t=a[e];if(!t){let r=`Callback '${e}' not registered!!!`;throw console.error(r),new Error(r)}clearTimeout(t.timeoutHandle),delete a[e],o.error?t.reject(o.error):t.resolve(o.result)}window.go={};function O(n){try{n=JSON.parse(n)}catch(o){console.error(o)}window.go=window.go||{},Object.keys(n).forEach(o=>{window.go[o]=window.go[o]||{},Object.keys(n[o]).forEach(e=>{window.go[o][e]=window.go[o][e]||{},Object.keys(n[o][e]).forEach(t=>{window.go[o][e][t]=function(){let r=0;function i(){let w=[].slice.call(arguments);return c([o,e,t].join("."),w,r)}return i.setTimeout=function(w){r=w},i.getTimeout=function(){return r},i}()})})})}var v={};u(v,{WindowCenter:()=>M,WindowFullscreen:()=>j,WindowGetPosition:()=>Y,WindowGetSize:()=>V,WindowHide:()=>Z,WindowMaximise:()=>_,WindowMinimise:()=>on,WindowReload:()=>H,WindowSetMaxSize:()=>X,WindowSetMinSize:()=>q,WindowSetPosition:()=>N,WindowSetRGBA:()=>tn,WindowSetSize:()=>Q,WindowSetTitle:()=>P,WindowShow:()=>K,WindowUnFullscreen:()=>$,WindowUnmaximise:()=>nn,WindowUnminimise:()=>en});function H(){window.location.reload()}function M(){window.WailsInvoke("Wc")}function P(n){window.WailsInvoke("WT"+n)}function j(){window.WailsInvoke("WF")}function $(){window.WailsInvoke("Wf")}function Q(n,o){window.WailsInvoke("Ws:"+n+":"+o)}function V(){return c(":wails:WindowGetSize")}function X(n,o){window.WailsInvoke("WZ:"+n+":"+o)}function q(n,o){window.WailsInvoke("Wz:"+n+":"+o)}function N(n,o){window.WailsInvoke("Wp:"+n+":"+o)}function Y(){return c(":wails:WindowGetPos")}function Z(){window.WailsInvoke("WH")}function K(){window.WailsInvoke("WS")}function _(){window.WailsInvoke("WM")}function nn(){window.WailsInvoke("WU")}function on(){window.WailsInvoke("Wm")}function en(){window.WailsInvoke("Wu")}function tn(n){let o=JSON.stringify(n);window.WailsInvoke("Wr:"+o)}var g={};u(g,{BrowserOpenURL:()=>rn});function rn(n){window.WailsInvoke("BO:"+n)}function sn(){window.WailsInvoke("Q")}window.runtime={...W,...v,...g,EventsOn:I,EventsOnce:k,EventsOnMultiple:d,EventsEmit:b,EventsOff:y,Quit:sn};window.wails={Callback:L,EventsNotify:m,SetBindings:O,eventListeners:s,callbacks:a,flags:{disableScrollbarDrag:!1,disableWailsDefaultContextMenu:!1}};window.wails.SetBindings(window.wailsbindings);delete window.wails.SetBindings;window.addEventListener("mousedown",n=>{let o=n.target;for(;o!=null&&!o.hasAttribute("data-wails-no-drag");){if(o.hasAttribute("data-wails-drag")){if(window.wails.flags.disableScrollbarDrag&&(n.offsetX>n.target.clientWidth||n.offsetY>n.target.clientHeight))break;window.WailsInvoke("drag"),n.preventDefault();break}o=o.parentElement}});window.addEventListener("contextmenu",function(n){window.wails.flags.disableWailsDefaultContextMenu&&n.preventDefault()});})();

View File

@@ -9,9 +9,9 @@ interface Size {
}
interface RGBA {
r,
g,
b,
r: number;
g: number;
b: number;
a: number;
}

View File

@@ -75,7 +75,7 @@ const basicUpdated string = `module changeme
go 1.17
require github.com/wailsapp/wails/v2 v2.0.0-beta.12
require github.com/wailsapp/wails/v2 v2.0.0-beta.15
require (
github.com/andybalholm/brotli v1.0.2 // indirect
@@ -330,7 +330,7 @@ const multilineRequireUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.12
github.com/wailsapp/wails/v2 v2.0.0-beta.15
)
require (
@@ -381,12 +381,12 @@ func TestUpdateGoModVersion(t *testing.T) {
want []byte
wantErr bool
}{
{"basic", args{[]byte(basic), "v2.0.0-beta.12"}, []byte(basicUpdated), false},
{"basicmultiline", args{[]byte(multilineRequire), "v2.0.0-beta.12"}, []byte(multilineRequireUpdated), false},
{"basicmultilinereplace", args{[]byte(multilineReplace), "v2.0.0-beta.12"}, []byte(multilineReplaceUpdated), false},
{"basicmultilinereplaceblock", args{[]byte(multilineReplaceBlock), "v2.0.0-beta.12"}, []byte(multilineReplaceBlockUpdated), false},
{"basicmultilinereplacenoversion", args{[]byte(multilineReplaceNoVersion), "v2.0.0-beta.12"}, []byte(multilineReplaceNoVersionUpdated), false},
{"basicmultilinereplacenoversionblock", args{[]byte(multilineReplaceNoVersionBlock), "v2.0.0-beta.12"}, []byte(multilineReplaceNoVersionBlockUpdated), false},
{"basic", args{[]byte(basic), "v2.0.0-beta.15"}, []byte(basicUpdated), false},
{"basicmultiline", args{[]byte(multilineRequire), "v2.0.0-beta.15"}, []byte(multilineRequireUpdated), false},
{"basicmultilinereplace", args{[]byte(multilineReplace), "v2.0.0-beta.15"}, []byte(multilineReplaceUpdated), false},
{"basicmultilinereplaceblock", args{[]byte(multilineReplaceBlock), "v2.0.0-beta.15"}, []byte(multilineReplaceBlockUpdated), false},
{"basicmultilinereplacenoversion", args{[]byte(multilineReplaceNoVersion), "v2.0.0-beta.15"}, []byte(multilineReplaceNoVersionUpdated), false},
{"basicmultilinereplacenoversionblock", args{[]byte(multilineReplaceNoVersionBlock), "v2.0.0-beta.15"}, []byte(multilineReplaceNoVersionBlockUpdated), false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -413,8 +413,8 @@ func TestGoModOutOfSync(t *testing.T) {
want bool
wantErr bool
}{
{"basic", args{[]byte(basic), "v2.0.0-beta.12"}, true, false},
{"basicmultiline", args{[]byte(multilineRequire), "v2.0.0-beta.12"}, true, false},
{"basic", args{[]byte(basic), "v2.0.0-beta.15"}, true, false},
{"basicmultiline", args{[]byte(multilineRequire), "v2.0.0-beta.15"}, true, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -433,7 +433,7 @@ const multilineReplaceUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.12
github.com/wailsapp/wails/v2 v2.0.0-beta.15
)
require (
@@ -468,14 +468,14 @@ require (
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
)
replace github.com/wailsapp/wails/v2 v2.0.0-beta.12 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
replace github.com/wailsapp/wails/v2 v2.0.0-beta.15 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceNoVersionUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.12
github.com/wailsapp/wails/v2 v2.0.0-beta.15
)
require (
@@ -517,7 +517,7 @@ const multilineReplaceNoVersionBlockUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.12
github.com/wailsapp/wails/v2 v2.0.0-beta.15
)
require (
@@ -562,7 +562,7 @@ const multilineReplaceBlockUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.12
github.com/wailsapp/wails/v2 v2.0.0-beta.15
)
require (
@@ -598,6 +598,6 @@ require (
)
replace (
github.com/wailsapp/wails/v2 v2.0.0-beta.12 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
github.com/wailsapp/wails/v2 v2.0.0-beta.15 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
)
`

View File

@@ -3,6 +3,7 @@ package menumanager
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/menu"
)

View File

@@ -53,6 +53,9 @@ type Project struct {
// The url to use to server assets. Default "https://localhost:34115"
DevServerURL string `json:"devserverurl"`
// Arguments that are forwared to the application in dev mode
AppArgs string `json:"appargs"`
}
func (p *Project) Save() error {

View File

@@ -35,7 +35,7 @@ func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.
result := &Manager{
bus: bus,
logger: logger.CustomLogger("Event Manager"),
logger: logger.CustomLogger("Signal Manager"),
signalchannel: make(chan os.Signal, 2),
ctx: ctx,
cancel: cancel,
@@ -49,7 +49,7 @@ func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.
func (m *Manager) Start() {
// Hook into interrupts
gosignal.Notify(m.signalchannel, os.Interrupt, syscall.SIGTERM)
gosignal.Notify(m.signalchannel, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
m.wg.Add(1)

View File

@@ -2,10 +2,11 @@ package buildassets
import (
"embed"
"github.com/leaanthony/debme"
"github.com/leaanthony/gosod"
"os"
"path/filepath"
"github.com/leaanthony/debme"
"github.com/leaanthony/gosod"
)
//go:embed build
@@ -50,3 +51,17 @@ func RegenerateAppIcon(target string) error {
}
return a.CopyFile("appicon.png", target, 0644)
}
func RegeneratePlist(targetDir string, projectName string) error {
darwinAssets, err := debme.FS(assets, "build/darwin")
if err != nil {
return err
}
templateDir := gosod.New(darwinAssets)
err = templateDir.Extract(targetDir, &assetData{Name: projectName})
if err != nil {
return err
}
return nil
}

View File

@@ -3,9 +3,6 @@ package build
import (
"bytes"
"fmt"
"github.com/leaanthony/gosod"
wailsRuntime "github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/frontend/runtime/wrapper"
"io/ioutil"
"os"
"os/exec"
@@ -13,6 +10,10 @@ import (
"runtime"
"strings"
"github.com/leaanthony/gosod"
wailsRuntime "github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/frontend/runtime/wrapper"
"github.com/pkg/errors"
"github.com/leaanthony/slicer"
@@ -215,8 +216,6 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
commands.Add(`"all=-N -l"`)
}
//commands.Add("-a")
var tags slicer.StringSlicer
tags.Add(options.OutputType)
tags.AddSlice(options.UserTags)
@@ -299,7 +298,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
if v != "" {
v += " "
}
v += "-I" + buildBaseDir
v += "-mmacosx-version-min=10.13"
return v
})
// Use upsertEnv so we don't overwrite user's CGO_CXXFLAGS
@@ -314,6 +313,17 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
cmd.Env = upsertEnv(cmd.Env, "CGO_ENABLED", func(v string) string {
return "1"
})
if runtime.GOOS == "darwin" {
// Set the minimum Mac SDK to 10.13
cmd.Env = upsertEnv(cmd.Env, "CGO_LDFLAGS", func(v string) string {
if v != "" {
v += " "
}
v += "-mmacosx-version-min=10.13"
return v
})
}
}
cmd.Env = upsertEnv(cmd.Env, "GOOS", func(v string) string {
@@ -337,8 +347,6 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
return err
}
println("Done.")
if !options.Compress {
return nil
}
@@ -520,17 +528,24 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
}
// Check if there is a build command
if b.projectData.BuildCommand == "" {
var buildCommand string
switch b.projectData.OutputType {
case "dev":
buildCommand = b.projectData.DevCommand
default:
buildCommand = b.projectData.BuildCommand
}
if buildCommand == "" {
outputLogger.Println("No Build command. Skipping.")
// No - ignore
return nil
}
outputLogger.Print("Compiling frontend: ")
cmd := strings.Split(b.projectData.BuildCommand, " ")
cmd := strings.Split(buildCommand, " ")
if verbose {
outputLogger.Println("")
outputLogger.Println(" Build command: '" + strings.Join(cmd, " ") + "'")
outputLogger.Println(" Build command: '" + buildCommand + "'")
}
stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...)
if verbose || err != nil {

View File

@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"github.com/wailsapp/wails/v2/internal/fs"
@@ -111,17 +112,11 @@ func Build(options *Options) (string, error) {
}
}
// Build the base assets
//err = builder.BuildAssets(options)
//if err != nil {
// return "", err
//}
// If we are building for windows, we will need to generate the asset bundle before
// compilation. This will be a .syso file in the project root
if options.Pack && options.Platform == "windows" {
outputLogger.Print("Generating bundle assets: ")
err := packageApplication(options)
err := packageApplicationForWindows(options)
if err != nil {
return "", err
}
@@ -149,10 +144,10 @@ func Build(options *Options) (string, error) {
options.OutputFile = amd64Filename
options.CleanBuildDirectory = false
if options.Verbosity == VERBOSE {
println()
println(" Building AMD64 Target:", filepath.Join(options.BuildDirectory, options.OutputFile))
outputLogger.Println("\nBuilding AMD64 Target:", filepath.Join(options.BuildDirectory, options.OutputFile))
}
err = builder.CompileProject(options)
if err != nil {
return "", err
}
@@ -161,15 +156,16 @@ func Build(options *Options) (string, error) {
options.OutputFile = arm64Filename
options.CleanBuildDirectory = false
if options.Verbosity == VERBOSE {
println(" Building ARM64 Target:", filepath.Join(options.BuildDirectory, options.OutputFile))
outputLogger.Println("Building ARM64 Target:", filepath.Join(options.BuildDirectory, options.OutputFile))
}
err = builder.CompileProject(options)
if err != nil {
return "", err
}
// Run lipo
if options.Verbosity == VERBOSE {
println(" Running lipo: ", "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename)
outputLogger.Println(" Running lipo: ", "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename)
}
_, stderr, err := shell.RunCommand(options.BuildDirectory, "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename)
if err != nil {
@@ -193,6 +189,8 @@ func Build(options *Options) (string, error) {
}
}
outputLogger.Println("Done.")
// Do we need to pack the app for non-windows?
if options.Pack && options.Platform != "windows" {
@@ -212,6 +210,18 @@ func Build(options *Options) (string, error) {
return "", err
}
return options.CompiledBinary, nil
result := options.CompiledBinary
if options.Pack && options.Platform == "darwin" {
sr := strings.Split(result, "/")
for i := len(sr) - 1; i >= 0; i-- {
if strings.Contains(sr[i], ".app") {
result = strings.Join(sr[:i+1], "/")
break
}
}
}
return result, nil
}

View File

@@ -2,10 +2,19 @@ package build
import (
"fmt"
"image"
"os"
"path"
"path/filepath"
"runtime"
"github.com/leaanthony/winicon"
"github.com/tc-hib/winres"
"github.com/jackmordaunt/icns"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"github.com/wailsapp/wails/v2/internal/fs"
)
@@ -14,8 +23,10 @@ func packageProject(options *Options, platform string) error {
var err error
switch platform {
case "darwin", "windows":
err = packageApplication(options)
case "darwin":
err = packageApplicationForDarwin(options)
case "windows":
err = packageApplicationForWindows(options)
default:
err = fmt.Errorf("packing not supported for %s yet", platform)
}
@@ -65,3 +76,225 @@ func getBuildBaseDirectory(options *Options) (string, error) {
func getPackageAssetsDirectory() string {
return fs.RelativePath("internal/packager", runtime.GOOS)
}
func packageApplicationForDarwin(options *Options) error {
var err error
// Create directory structure
bundlename := options.ProjectData.Name + ".app"
contentsDirectory := filepath.Join(options.BuildDirectory, bundlename, "/Contents")
exeDir := filepath.Join(contentsDirectory, "/MacOS")
err = fs.MkDirs(exeDir, 0755)
if err != nil {
return err
}
resourceDir := filepath.Join(contentsDirectory, "/Resources")
err = fs.MkDirs(resourceDir, 0755)
if err != nil {
return err
}
// Copy binary
packedBinaryPath := filepath.Join(exeDir, options.ProjectData.Name)
err = fs.MoveFile(options.CompiledBinary, packedBinaryPath)
if err != nil {
return errors.Wrap(err, "Cannot move file: "+options.ProjectData.OutputFilename)
}
// Generate Info.plist
err = processPList(options, contentsDirectory)
if err != nil {
return err
}
// Generate Icons
err = processApplicationIcon(resourceDir, options.ProjectData.Path)
if err != nil {
return err
}
options.CompiledBinary = packedBinaryPath
return nil
}
func processPList(options *Options, contentsDirectory string) error {
// Check if plist already exists in project dir
plistFileDir := filepath.Join(options.ProjectData.Path, "build", "darwin")
plistFile := filepath.Join(plistFileDir, "Info.plist")
// If the file doesn't exist, generate it
if !fs.FileExists(plistFile) {
err := buildassets.RegeneratePlist(plistFileDir, options.ProjectData.Name)
if err != nil {
return err
}
}
// Copy it to the contents directory
targetFile := filepath.Join(contentsDirectory, "Info.plist")
return fs.CopyFile(plistFile, targetFile)
}
func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
appIcon := filepath.Join(iconsDir, "appicon.png")
// Install default icon if one doesn't exist
if !fs.FileExists(appIcon) {
// No - Install default icon
err = buildassets.RegenerateAppIcon(appIcon)
if err != nil {
return
}
}
tgtBundle := path.Join(resourceDir, "iconfile.icns")
imageFile, err := os.Open(appIcon)
if err != nil {
return err
}
defer func() {
err = imageFile.Close()
if err == nil {
return
}
}()
srcImg, _, err := image.Decode(imageFile)
if err != nil {
return err
}
dest, err := os.Create(tgtBundle)
if err != nil {
return err
}
defer func() {
err = dest.Close()
if err == nil {
return
}
}()
return icns.Encode(dest, srcImg)
}
func packageApplicationForWindows(options *Options) error {
// Generate icon
var err error
err = generateIcoFile(options)
if err != nil {
return err
}
// Ensure Manifest is present
err = generateManifest(options)
if err != nil {
return err
}
// Create syso file
err = compileResources(options)
if err != nil {
return err
}
return nil
}
func generateManifest(options *Options) error {
filename := options.ProjectData.Name + ".exe.manifest"
manifestFile := filepath.Join(options.ProjectData.Path, "build", "windows", filename)
if !fs.FileExists(manifestFile) {
return buildassets.RegenerateManifest(manifestFile)
}
return nil
}
func generateIcoFile(options *Options) error {
// Check ico file exists already
icoFile := filepath.Join(options.ProjectData.Path, "build", "windows", "icon.ico")
if !fs.FileExists(icoFile) {
// Check icon exists
appicon := filepath.Join(options.ProjectData.Path, "build", "appicon.png")
if !fs.FileExists(appicon) {
return fmt.Errorf("application icon missing: %s", appicon)
}
// Load icon
input, err := os.Open(appicon)
if err != nil {
return err
}
output, err := os.OpenFile(icoFile, os.O_CREATE, 0644)
if err != nil {
return err
}
err = winicon.GenerateIcon(input, output, []int{256, 128, 64, 48, 32, 16})
if err != nil {
return err
}
}
return nil
}
func compileResources(options *Options) error {
currentDir, err := os.Getwd()
if err != nil {
return err
}
defer func() {
os.Chdir(currentDir)
}()
windowsDir := filepath.Join(options.ProjectData.Path, "build", "windows")
err = os.Chdir(windowsDir)
if err != nil {
return err
}
rs := winres.ResourceSet{}
icon := filepath.Join(windowsDir, "icon.ico")
iconFile, err := os.Open(icon)
if err != nil {
return err
}
defer iconFile.Close()
ico, err := winres.LoadICO(iconFile)
if err != nil {
return err
}
err = rs.SetIcon(winres.RT_ICON, ico)
if err != nil {
return err
}
ManifestFilename := options.ProjectData.Name + ".exe.manifest"
manifestData, err := os.ReadFile(ManifestFilename)
xmlData, err := winres.AppManifestFromXML(manifestData)
if err != nil {
return err
}
rs.SetManifest(xmlData)
targetFile := filepath.Join(options.ProjectData.Path, options.ProjectData.Name+"-res.syso")
fout, err := os.Create(targetFile)
if err != nil {
return err
}
defer fout.Close()
archs := map[string]winres.Arch{
"amd64": winres.ArchAMD64,
}
targetArch, supported := archs[options.Arch]
if !supported {
return fmt.Errorf("arch '%s' not supported", options.Arch)
}
err = rs.WriteObject(fout, targetArch)
if err != nil {
return err
}
return nil
}

View File

@@ -1,181 +0,0 @@
package build
import (
"bytes"
"image"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"text/template"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"github.com/jackmordaunt/icns"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/internal/fs"
)
func packageApplication(options *Options) error {
var err error
// Create directory structure
bundlename := options.ProjectData.Name + ".app"
contentsDirectory := filepath.Join(options.BuildDirectory, bundlename, "/Contents")
exeDir := filepath.Join(contentsDirectory, "/MacOS")
err = fs.MkDirs(exeDir, 0755)
if err != nil {
return err
}
resourceDir := filepath.Join(contentsDirectory, "/Resources")
err = fs.MkDirs(resourceDir, 0755)
if err != nil {
return err
}
// Copy binary
packedBinaryPath := filepath.Join(exeDir, options.ProjectData.Name)
err = fs.MoveFile(options.CompiledBinary, packedBinaryPath)
if err != nil {
return errors.Wrap(err, "Cannot move file: "+options.ProjectData.OutputFilename)
}
// Generate Info.plist
err = processPList(options, contentsDirectory)
if err != nil {
return err
}
// Generate Icons
err = processApplicationIcon(resourceDir, options.ProjectData.BuildDir)
if err != nil {
return err
}
options.CompiledBinary = packedBinaryPath
return nil
}
func processPList(options *Options, contentsDirectory string) error {
// Check if plist already exists in project dir
plistFile := filepath.Join(options.ProjectData.BuildDir, "darwin", "Info.plist")
// If the file doesn't exist, generate it
if !fs.FileExists(plistFile) {
err := generateDefaultPlist(options, plistFile)
if err != nil {
return err
}
}
// Copy it to the contents directory
targetFile := filepath.Join(contentsDirectory, "Info.plist")
return fs.CopyFile(plistFile, targetFile)
}
func generateDefaultPlist(options *Options, targetPlistFile string) error {
name := defaultString(options.ProjectData.Name, "WailsTest")
exe := defaultString(options.OutputFile, name)
version := "1.0.0"
author := defaultString(options.ProjectData.Author.Name, "Anonymous")
packageID := strings.Join([]string{"wails", name}, ".")
plistData := newPlistData(name, exe, packageID, version, author)
tmpl := template.New("infoPlist")
plistTemplate := fs.RelativePath("./internal/packager/darwin/Info.plist")
infoPlist, err := ioutil.ReadFile(plistTemplate)
if err != nil {
return errors.Wrap(err, "Cannot open plist template")
}
_, err = tmpl.Parse(string(infoPlist))
if err != nil {
return err
}
// Write the template to a buffer
var tpl bytes.Buffer
err = tmpl.Execute(&tpl, plistData)
if err != nil {
return err
}
// Create the directory if it doesn't exist
err = fs.MkDirs(filepath.Dir(targetPlistFile))
if err != nil {
return err
}
// Save the file
return ioutil.WriteFile(targetPlistFile, tpl.Bytes(), 0644)
}
func defaultString(val string, defaultVal string) string {
if val != "" {
return val
}
return defaultVal
}
type plistData struct {
Title string
Exe string
PackageID string
Version string
Author string
}
func newPlistData(title, exe, packageID, version, author string) *plistData {
return &plistData{
Title: title,
Exe: exe,
Version: version,
PackageID: packageID,
Author: author,
}
}
func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
appIcon := filepath.Join(iconsDir, "appicon.png")
// Install default icon if one doesn't exist
if !fs.FileExists(appIcon) {
// No - Install default icon
err = buildassets.RegenerateAppIcon(appIcon)
if err != nil {
return
}
}
tgtBundle := path.Join(resourceDir, "iconfile.icns")
imageFile, err := os.Open(appIcon)
if err != nil {
return err
}
defer func() {
err = imageFile.Close()
if err == nil {
return
}
}()
srcImg, _, err := image.Decode(imageFile)
if err != nil {
return err
}
dest, err := os.Create(tgtBundle)
if err != nil {
return err
}
defer func() {
err = dest.Close()
if err == nil {
return
}
}()
return icns.Encode(dest, srcImg)
}

View File

@@ -1,129 +0,0 @@
package build
import (
"fmt"
"github.com/leaanthony/winicon"
"github.com/tc-hib/winres"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"os"
"path/filepath"
)
func packageApplication(options *Options) error {
// Generate icon
var err error
err = generateIcoFile(options)
if err != nil {
return err
}
// Ensure Manifest is present
err = generateManifest(options)
if err != nil {
return err
}
// Create syso file
err = compileResources(options)
if err != nil {
return err
}
return nil
}
func generateManifest(options *Options) error {
filename := options.ProjectData.Name + ".exe.manifest"
manifestFile := filepath.Join(options.ProjectData.Path, "build", "windows", filename)
if !fs.FileExists(manifestFile) {
return buildassets.RegenerateManifest(manifestFile)
}
return nil
}
func generateIcoFile(options *Options) error {
// Check ico file exists already
icoFile := filepath.Join(options.ProjectData.Path, "build", "windows", "icon.ico")
if !fs.FileExists(icoFile) {
// Check icon exists
appicon := filepath.Join(options.ProjectData.Path, "build", "appicon.png")
if !fs.FileExists(appicon) {
return fmt.Errorf("application icon missing: %s", appicon)
}
// Load icon
input, err := os.Open(appicon)
if err != nil {
return err
}
output, err := os.OpenFile(icoFile, os.O_CREATE, 0644)
if err != nil {
return err
}
err = winicon.GenerateIcon(input, output, []int{256, 128, 64, 48, 32, 16})
if err != nil {
return err
}
}
return nil
}
func compileResources(options *Options) error {
currentDir, err := os.Getwd()
if err != nil {
return err
}
defer func() {
os.Chdir(currentDir)
}()
windowsDir := filepath.Join(options.ProjectData.Path, "build", "windows")
err = os.Chdir(windowsDir)
if err != nil {
return err
}
rs := winres.ResourceSet{}
icon := filepath.Join(windowsDir, "icon.ico")
iconFile, err := os.Open(icon)
if err != nil {
return err
}
defer iconFile.Close()
ico, err := winres.LoadICO(iconFile)
if err != nil {
return err
}
err = rs.SetIcon(winres.RT_ICON, ico)
if err != nil {
return err
}
ManifestFilename := options.ProjectData.Name + ".exe.manifest"
manifestData, err := os.ReadFile(ManifestFilename)
xmlData, err := winres.AppManifestFromXML(manifestData)
if err != nil {
return err
}
rs.SetManifest(xmlData)
targetFile := filepath.Join(options.ProjectData.Path, options.ProjectData.Name+"-res.syso")
fout, err := os.Create(targetFile)
if err != nil {
return err
}
defer fout.Close()
archs := map[string]winres.Arch{
"amd64": winres.ArchAMD64,
}
targetArch, supported := archs[options.Arch]
if !supported {
return fmt.Errorf("arch '%s' not supported", options.Arch)
}
err = rs.WriteObject(fout, targetArch)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,66 @@
package main
import (
"bytes"
_ "embed"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"text/template"
)
type Rgb struct {
R uint8 `json:"r"`
G uint8 `json:"g"`
B uint8 `json:"b"`
}
type Hsl struct {
H float64 `json:"h"`
S float64 `json:"s"`
L float64 `json:"l"`
}
type InputCol struct {
Colorid uint8 `json:"colorId"`
Hexstring string `json:"hexString"`
Rgb Rgb `json:"rgb"`
Hsl Hsl `json:"hsl"`
Name string `json:"name"`
}
//go:embed gen.tmpl
var Template string
func main() {
var Cols []InputCol
resp, err := http.Get("https://jonasjacek.github.io/colors/data.json")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
err = json.Unmarshal(data, &Cols)
if err != nil {
log.Fatal(err)
}
t, err := template.New("cols").Parse(Template)
if err != nil {
log.Fatal(err)
}
var buffer bytes.Buffer
err = t.Execute(&buffer, Cols)
if err != nil {
log.Fatal(err)
}
os.WriteFile(filepath.Join("..", "cols.go"), buffer.Bytes(), 0755)
}

View File

@@ -0,0 +1,29 @@
package menu
type Rgb struct {
R uint8 `json:"r"`
G uint8 `json:"g"`
B uint8 `json:"b"`
}
type Hsl struct {
H float64 `json:"h"`
S float64 `json:"s"`
L float64 `json:"l"`
}
type Col struct {
Hex string `json:"hex"`
Rgb Rgb `json:"rgb"`
Hsl Hsl `json:"hsl"`
Name string `json:"name"`
}
var Cols = []*Col{ {{range $col := .}}
{
Hex: "{{.Hexstring}}",
Rgb: Rgb{ {{.Rgb.R}}, {{.Rgb.G}}, {{.Rgb.B}} },
Hsl: Hsl{ {{.Hsl.H}}, {{.Hsl.S}}, {{.Hsl.L}} },
Name: "{{.Name}}",
},{{end}}
}

1559
v2/pkg/menu/cols.go Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
package keys
const (
NSEventModifierFlagShift = 1 << 17 // Set if Shift key is pressed.
NSEventModifierFlagControl = 1 << 18 // Set if Control key is pressed.
NSEventModifierFlagOption = 1 << 19 // Set if Option or Alternate key is pressed.
NSEventModifierFlagCommand = 1 << 20 // Set if Command key is pressed.
)
var macModifierMap = map[Modifier]int{
CmdOrCtrlKey: NSEventModifierFlagCommand,
ControlKey: NSEventModifierFlagControl,
OptionOrAltKey: NSEventModifierFlagOption,
ShiftKey: NSEventModifierFlagShift,
}
func ToMacModifier(accelerator *Accelerator) int {
if accelerator == nil {
return 0
}
result := 0
for _, modifier := range accelerator.Modifiers {
result |= macModifierMap[modifier]
}
return result
}

View File

@@ -0,0 +1,31 @@
package keys
import "testing"
func TestToMacModifier(t *testing.T) {
tests := []struct {
name string
accelerator *Accelerator
want int
}{
// TODO: Add test cases.
{"nil", nil, 0},
{"empty", &Accelerator{}, 0},
{"key", &Accelerator{Key: "p"}, 0},
{"cmd", CmdOrCtrl(""), NSEventModifierFlagCommand},
{"ctrl", Control(""), NSEventModifierFlagControl},
{"shift", Shift(""), NSEventModifierFlagShift},
{"option", OptionOrAlt(""), NSEventModifierFlagOption},
{"cmd+ctrl", Combo("", CmdOrCtrlKey, ControlKey), NSEventModifierFlagCommand | NSEventModifierFlagControl},
{"cmd+ctrl+shift", Combo("", CmdOrCtrlKey, ControlKey, ShiftKey), NSEventModifierFlagCommand | NSEventModifierFlagControl | NSEventModifierFlagShift},
{"cmd+ctrl+shift+option", Combo("", CmdOrCtrlKey, ControlKey, ShiftKey, OptionOrAltKey), NSEventModifierFlagCommand | NSEventModifierFlagControl | NSEventModifierFlagShift | NSEventModifierFlagOption},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ToMacModifier(tt.accelerator); got != tt.want {
t.Errorf("ToMacModifier() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -11,9 +11,9 @@ type MenuItem struct {
// Label is what appears as the menu text
Label string
// Role is a predefined menu type
//Role Role `json:"Role,omitempty"`
Role Role
// Accelerator holds a representation of a key binding
Accelerator *keys.Accelerator `json:"Accelerator,omitempty"`
Accelerator *keys.Accelerator
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
Type Type
// Disabled makes the item unselectable
@@ -24,10 +24,10 @@ type MenuItem struct {
Checked bool
// Submenu contains a list of menu items that will be shown as a submenu
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
SubMenu *Menu `json:"SubMenu,omitempty"`
SubMenu *Menu
// Callback function when menu clicked
Click Callback `json:"-"`
Click Callback
/*
// Text Colour
RGBA string
@@ -267,16 +267,3 @@ func SubMenu(label string, menu *Menu) *MenuItem {
return result
}
// SubMenuWithID is a helper to create Submenus with an ID
func SubMenuWithID(label string, menu *Menu) *MenuItem {
result := &MenuItem{
Label: label,
SubMenu: menu,
Type: SubmenuType,
}
menu.setParent(result)
return result
}

View File

@@ -3,37 +3,39 @@
// Electron License: https://github.com/electron/electron/blob/master/LICENSE
package menu
/*
type Role string
// Role is a type to identify menu roles
type Role int
// These constants need to be kept in sync with `v2/internal/frontend/desktop/darwin/Role.h`
const (
AboutRole Role = "about"
UndoRole Role = "undo"
RedoRole Role = "redo"
CutRole Role = "cut"
CopyRole Role = "copy"
PasteRole Role = "paste"
PasteAndMatchStyleRole Role = "pasteAndMatchStyle"
SelectAllRole Role = "selectAll"
DeleteRole Role = "delete"
MinimizeRole Role = "minimize"
QuitRole Role = "quit"
TogglefullscreenRole Role = "togglefullscreen"
FileMenuRole Role = "fileMenu"
EditMenuRole Role = "editMenu"
ViewMenuRole Role = "viewMenu"
WindowMenuRole Role = "windowMenu"
AppMenuRole Role = "appMenu"
HideRole Role = "hide"
HideOthersRole Role = "hideOthers"
UnhideRole Role = "unhide"
FrontRole Role = "front"
ZoomRole Role = "zoom"
WindowSubMenuRole Role = "windowSubMenu"
HelpSubMenuRole Role = "helpSubMenu"
SeparatorItemRole Role = "separatorItem"
AppMenuRole Role = 1
EditMenuRole = 2
//AboutRole Role = "about"
//UndoRole Role = "undo"
//RedoRole Role = "redo"
//CutRole Role = "cut"
//CopyRole Role = "copy"
//PasteRole Role = "paste"
//PasteAndMatchStyleRole Role = "pasteAndMatchStyle"
//SelectAllRole Role = "selectAll"
//DeleteRole Role = "delete"
//MinimizeRole Role = "minimize"
//QuitRole Role = "quit"
//TogglefullscreenRole Role = "togglefullscreen"
//FileMenuRole Role = "fileMenu"
//ViewMenuRole Role = "viewMenu"
//WindowMenuRole Role = "windowMenu"
//HideRole Role = "hide"
//HideOthersRole Role = "hideOthers"
//UnhideRole Role = "unhide"
//FrontRole Role = "front"
//ZoomRole Role = "zoom"
//WindowSubMenuRole Role = "windowSubMenu"
//HelpSubMenuRole Role = "helpSubMenu"
//SeparatorItemRole Role = "separatorItem"
)
/*
// About provides a MenuItem with the About role
func About() *MenuItem {
return &MenuItem{
@@ -111,8 +113,8 @@ func Quit() *MenuItem {
}
}
// Togglefullscreen provides a MenuItem with the Togglefullscreen role
func Togglefullscreen() *MenuItem {
// ToggleFullscreen provides a MenuItem with the ToggleFullscreen role
func ToggleFullscreen() *MenuItem {
return &MenuItem{
Role: TogglefullscreenRole,
}
@@ -124,6 +126,7 @@ func FileMenu() *MenuItem {
Role: FileMenuRole,
}
}
*/
// EditMenu provides a MenuItem with the whole default "Edit" menu (Undo, Copy, etc.).
func EditMenu() *MenuItem {
@@ -132,6 +135,7 @@ func EditMenu() *MenuItem {
}
}
/*
// ViewMenu provides a MenuItem with the whole default "View" menu (Reload, Toggle Developer Tools, etc.)
func ViewMenu() *MenuItem {
return &MenuItem{
@@ -145,7 +149,7 @@ func WindowMenu() *MenuItem {
Role: WindowMenuRole,
}
}
*/
// These roles are Mac only
// AppMenu provides a MenuItem with the whole default "App" menu (About, Services, etc.)
@@ -155,6 +159,7 @@ func AppMenu() *MenuItem {
}
}
/*
// Hide provides a MenuItem that maps to the hide action.
func Hide() *MenuItem {
return &MenuItem{
@@ -169,8 +174,8 @@ func HideOthers() *MenuItem {
}
}
// Unhide provides a MenuItem that maps to the unhideAllApplications action.
func Unhide() *MenuItem {
// UnHide provides a MenuItem that maps to the unHideAllApplications action.
func UnHide() *MenuItem {
return &MenuItem{
Role: UnhideRole,
}

256
v2/pkg/menu/styledlabel.go Normal file
View File

@@ -0,0 +1,256 @@
package menu
import (
"fmt"
"strconv"
"strings"
)
type TextStyle int
const (
Bold TextStyle = 1 << 0
Faint TextStyle = 1 << 1
Italic TextStyle = 1 << 2
Blinking TextStyle = 1 << 3
Inversed TextStyle = 1 << 4
Invisible TextStyle = 1 << 5
Underlined TextStyle = 1 << 6
Strikethrough TextStyle = 1 << 7
)
type StyledText struct {
Label string
FgCol *Col
BgCol *Col
Style TextStyle
}
func (s *StyledText) Bold() bool {
return s.Style&Bold == Bold
}
func (s *StyledText) Faint() bool {
return s.Style&Faint == Faint
}
func (s *StyledText) Italic() bool {
return s.Style&Italic == Italic
}
func (s *StyledText) Blinking() bool {
return s.Style&Blinking == Blinking
}
func (s *StyledText) Inversed() bool {
return s.Style&Inversed == Inversed
}
func (s *StyledText) Invisible() bool {
return s.Style&Invisible == Invisible
}
func (s *StyledText) Underlined() bool {
return s.Style&Underlined == Underlined
}
func (s *StyledText) Strikethrough() bool {
return s.Style&Strikethrough == Strikethrough
}
var ansiColorMap = map[string]map[string]*Col{
"Normal": {
"30": Cols[0],
"31": Cols[1],
"32": Cols[2],
"33": Cols[3],
"34": Cols[4],
"35": Cols[5],
"36": Cols[6],
"37": Cols[7],
},
"Bold": {
"30": Cols[8],
"31": Cols[9],
"32": Cols[10],
"33": Cols[11],
"34": Cols[12],
"35": Cols[13],
"36": Cols[14],
"37": Cols[15],
},
"Faint": {
"30": Cols[0],
"31": Cols[1],
"32": Cols[2],
"33": Cols[3],
"34": Cols[4],
"35": Cols[5],
"36": Cols[6],
"37": Cols[7],
},
}
func ParseANSI(input string) ([]*StyledText, error) {
var result []*StyledText
invalid := fmt.Errorf("invalid ansi string")
missingTerminator := fmt.Errorf("missing escape terminator 'm'")
invalidTrueColorSequence := fmt.Errorf("invalid TrueColor sequence")
invalid256ColSequence := fmt.Errorf("invalid 256 colour sequence")
index := 0
var currentStyledText *StyledText = &StyledText{}
if len(input) == 0 {
return nil, invalid
}
for {
// Read all chars to next escape code
esc := strings.Index(input, "\033[")
// If no more esc chars, save what's left and return
if esc == -1 {
text := input[index:]
if len(text) > 0 {
currentStyledText.Label = text
result = append(result, currentStyledText)
}
return result, nil
}
label := input[:esc]
if len(label) > 0 {
currentStyledText.Label = label
result = append(result, currentStyledText)
currentStyledText = &StyledText{
Label: "",
FgCol: currentStyledText.FgCol,
BgCol: currentStyledText.BgCol,
Style: currentStyledText.Style,
}
}
input = input[esc:]
// skip
input = input[2:]
// Read in params
endesc := strings.Index(input, "m")
if endesc == -1 {
return nil, missingTerminator
}
paramText := input[:endesc]
input = input[endesc+1:]
params := strings.Split(paramText, ";")
colourMap := ansiColorMap["Normal"]
skip := 0
for index, param := range params {
if skip > 0 {
skip--
continue
}
switch param {
case "0":
// Reset styles
if len(params) == 1 {
if len(currentStyledText.Label) > 0 {
result = append(result, currentStyledText)
currentStyledText = &StyledText{
Label: "",
FgCol: currentStyledText.FgCol,
BgCol: currentStyledText.BgCol,
Style: currentStyledText.Style,
}
continue
}
}
currentStyledText.Style = 0
currentStyledText.FgCol = nil
currentStyledText.BgCol = nil
case "1":
// Bold
colourMap = ansiColorMap["Bold"]
currentStyledText.Style |= Bold
case "2":
// Dim/Feint
colourMap = ansiColorMap["Faint"]
currentStyledText.Style |= Faint
case "3":
// Italic
currentStyledText.Style |= Italic
case "4":
// Underlined
currentStyledText.Style |= Underlined
case "5":
// Blinking
currentStyledText.Style |= Blinking
case "7":
// Inverse
currentStyledText.Style |= Inversed
case "8":
// Invisible
currentStyledText.Style |= Invisible
case "9":
// Strikethrough
currentStyledText.Style |= Strikethrough
case "30", "31", "32", "33", "34", "35", "36", "37":
currentStyledText.FgCol = colourMap[param]
case "40", "41", "42", "43", "44", "45", "46", "47":
currentStyledText.BgCol = colourMap[param]
case "38", "48":
if len(params)-index < 2 {
return nil, invalid
}
// 256 colours
if params[index+1] == "5" {
skip = 2
colIndexText := params[index+2]
colIndex, err := strconv.Atoi(colIndexText)
if err != nil {
return nil, invalid256ColSequence
}
if colIndex < 0 || colIndex > 255 {
return nil, invalid256ColSequence
}
if param == "38" {
currentStyledText.FgCol = Cols[colIndex]
continue
}
currentStyledText.BgCol = Cols[colIndex]
continue
}
// we must have 4 params left
if len(params)-index < 4 {
return nil, invalidTrueColorSequence
}
if params[index+1] != "2" {
return nil, invalidTrueColorSequence
}
var r, g, b uint8
ri, err := strconv.Atoi(params[index+2])
if err != nil {
return nil, invalidTrueColorSequence
}
gi, err := strconv.Atoi(params[index+3])
if err != nil {
return nil, invalidTrueColorSequence
}
bi, err := strconv.Atoi(params[index+4])
if err != nil {
return nil, invalidTrueColorSequence
}
if bi > 255 || gi > 255 || ri > 255 {
return nil, invalidTrueColorSequence
}
if bi < 0 || gi < 0 || ri < 0 {
return nil, invalidTrueColorSequence
}
r = uint8(ri)
g = uint8(gi)
b = uint8(bi)
skip = 4
colvalue := fmt.Sprintf("#%02x%02x%02x", r, g, b)
if param == "38" {
currentStyledText.FgCol = &Col{Hex: colvalue, Rgb: Rgb{r, g, b}}
continue
}
currentStyledText.BgCol = &Col{Hex: colvalue}
default:
return nil, invalid
}
}
}
return result, nil
}

View File

@@ -0,0 +1,197 @@
package menu
import (
"testing"
"github.com/matryer/is"
)
func TestParseAnsi16SingleColour(t *testing.T) {
is := is.New(t)
tests := []struct {
name string
input string
wantText string
wantColor string
wantErr bool
}{
{"No formatting", "Hello World", "Hello World", "", false},
{"Black", "\u001b[0;30mHello World\033[0m", "Hello World", "Black", false},
{"Red", "\u001b[0;31mHello World\033[0m", "Hello World", "Maroon", false},
{"Green", "\u001b[0;32m\033[0m", "", "Green", false},
{"Yellow", "\u001b[0;33m😀\033[0m", "😀", "Olive", false},
{"Blue", "\u001b[0;34m123\033[0m", "123", "Navy", false},
{"Purple", "\u001b[0;35m👩🏽🔧\u001B[0m", "👩🏽‍🔧", "Purple", false},
{"Cyan", "\033[0;36m😀\033[0m", "😀", "Teal", false},
{"White", "\u001b[0;37m[0;37m\033[0m", "[0;37m", "Silver", false},
{"Black Bold", "\u001b[1;30mHello World\033[0m", "Hello World", "Grey", false},
{"Red Bold", "\u001b[1;31mHello World\033[0m", "Hello World", "Red", false},
{"Green Bold", "\u001b[1;32m\033[0m", "", "Lime", false},
{"Yellow Bold", "\u001b[1;33m😀\033[0m", "😀", "Yellow", false},
{"Blue Bold", "\u001b[1;34m123\033[0m", "123", "Blue", false},
{"Purple Bold", "\u001b[1;35m👩🏽🔧\u001B[0m", "👩🏽‍🔧", "Fuchsia", false},
{"Cyan Bold", "\033[1;36m😀\033[0m", "😀", "Aqua", false},
{"White Bold", "\u001b[1;37m[0;37m\033[0m", "[0;37m", "White", false},
{"Blank", "", "", "", true},
{"Emoji", "😀👩🏽‍🔧", "😀👩🏽‍🔧", "", false},
{"Spaces", " ", " ", "", false},
{"Bad code", "\u001b[1 ", "", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseANSI(tt.input)
is.Equal(err != nil, tt.wantErr)
expectedLength := 1
if tt.wantErr {
expectedLength = 0
}
is.Equal(len(got), expectedLength)
if expectedLength == 1 {
if len(tt.wantColor) > 0 {
is.True(got[0].FgCol != nil)
is.Equal(got[0].FgCol.Name, tt.wantColor)
}
}
})
}
}
func TestParseAnsi16MultiColour(t *testing.T) {
is := is.New(t)
tests := []struct {
name string
input string
want []*StyledText
wantErr bool
}{
{"Black & Red", "\u001B[0;30mHello World\u001B[0m\u001B[0;31mHello World\u001B[0m", []*StyledText{
{Label: "Hello World", FgCol: &Col{Name: "Black"}},
{Label: "Hello World", FgCol: &Col{Name: "Maroon"}},
}, false},
{"Text then Black & Red", "This is great!\u001B[0;30mHello World\u001B[0m\u001B[0;31mHello World\u001B[0m", []*StyledText{
{Label: "This is great!"},
{Label: "Hello World", FgCol: &Col{Name: "Black"}},
{Label: "Hello World", FgCol: &Col{Name: "Maroon"}},
}, false},
{"Text Reset then Black & Red", "This is great!\u001B[0m\u001B[0;30mHello World\u001B[0m\u001B[0;31mHello World\u001B[0m", []*StyledText{
{Label: "This is great!"},
{Label: "Hello World", FgCol: &Col{Name: "Black"}},
{Label: "Hello World", FgCol: &Col{Name: "Maroon"}},
}, false},
{"Black & Red no reset", "\u001B[0;30mHello World\u001B[0;31mHello World", []*StyledText{
{Label: "Hello World", FgCol: &Col{Name: "Black"}},
{Label: "Hello World", FgCol: &Col{Name: "Maroon"}},
}, false},
{"Black,space,Red", "\u001B[0;30mHello World\u001B[0m \u001B[0;31mHello World\u001B[0m", []*StyledText{
{Label: "Hello World", FgCol: &Col{Name: "Black"}},
{Label: " "},
{Label: "Hello World", FgCol: &Col{Name: "Maroon"}},
}, false},
{"Black,Red,Blue,Green underlined", "\033[4;30mBlack\u001B[0m\u001B[4;31mRed\u001B[0m\u001B[4;34mBlue\u001B[0m\u001B[4;32mGreen\u001B[0m", []*StyledText{
{Label: "Black", FgCol: &Col{Name: "Black"}, Style: Underlined},
{Label: "Red", FgCol: &Col{Name: "Maroon"}, Style: Underlined},
{Label: "Blue", FgCol: &Col{Name: "Navy"}, Style: Underlined},
{Label: "Green", FgCol: &Col{Name: "Green"}, Style: Underlined},
}, false},
{"Black,Red,Blue,Green bold", "\033[1;30mBlack\u001B[0m\u001B[1;31mRed\u001B[0m\u001B[1;34mBlue\u001B[0m\u001B[1;32mGreen\u001B[0m", []*StyledText{
{Label: "Black", FgCol: &Col{Name: "Grey"}, Style: Bold},
{Label: "Red", FgCol: &Col{Name: "Red"}, Style: Bold},
{Label: "Blue", FgCol: &Col{Name: "Blue"}, Style: Bold},
{Label: "Green", FgCol: &Col{Name: "Lime"}, Style: Bold},
}, false},
{"Green Feint & Yellow Italic", "\u001B[2;32m👩🏽🔧\u001B[0m\u001B[0;3;33m👩🏽🔧\u001B[0m", []*StyledText{
{Label: "👩🏽‍🔧", FgCol: &Col{Name: "Green"}, Style: Faint},
{Label: "👩🏽‍🔧", FgCol: &Col{Name: "Olive"}, Style: Italic},
}, false},
{"Green Blinking & Yellow Inversed", "\u001B[5;32m👩🏽🔧\u001B[0m\u001B[0;7;33m👩🏽🔧\u001B[0m", []*StyledText{
{Label: "👩🏽‍🔧", FgCol: &Col{Name: "Green"}, Style: Blinking},
{Label: "👩🏽‍🔧", FgCol: &Col{Name: "Olive"}, Style: Inversed},
}, false},
{"Green Invisible & Yellow Invisible & Strikethrough", "\u001B[8;32m👩🏽🔧\u001B[0m\u001B[9;33m👩🏽🔧\u001B[0m", []*StyledText{
{Label: "👩🏽‍🔧", FgCol: &Col{Name: "Green"}, Style: Invisible},
{Label: "👩🏽‍🔧", FgCol: &Col{Name: "Olive"}, Style: Invisible | Strikethrough},
}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseANSI(tt.input)
is.Equal(err != nil, tt.wantErr)
for index, w := range tt.want {
is.Equal(got[index].Label, w.Label)
if w.FgCol != nil {
is.Equal(got[index].FgCol.Name, w.FgCol.Name)
}
is.Equal(got[index].Style, w.Style)
}
})
}
}
func TestParseAnsi256(t *testing.T) {
is := is.New(t)
tests := []struct {
name string
input string
want []*StyledText
wantErr bool
}{
{"Grey93 & DarkViolet", "\u001B[38;5;255mGrey93\u001B[0m\u001B[38;5;128mDarkViolet\u001B[0m", []*StyledText{
{Label: "Grey93", FgCol: &Col{Name: "Grey93"}},
{Label: "DarkViolet", FgCol: &Col{Name: "DarkViolet"}},
}, false},
{"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;38;5;255mGrey93\u001B[0m\u001B[0;3;38;5;128mDarkViolet\u001B[0m", []*StyledText{
{Label: "Grey93", FgCol: &Col{Name: "Grey93"}, Style: Bold},
{Label: "DarkViolet", FgCol: &Col{Name: "DarkViolet"}, Style: Italic},
}, false},
{"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;38;5;256mGrey93\u001B[0m", nil, true},
{"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;38;5;-1mGrey93\u001B[0m", nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseANSI(tt.input)
is.Equal(err != nil, tt.wantErr)
for index, w := range tt.want {
is.Equal(got[index].Label, w.Label)
if w.FgCol != nil {
is.Equal(got[index].FgCol.Name, w.FgCol.Name)
}
is.Equal(got[index].Style, w.Style)
}
})
}
}
func TestParseAnsiTrueColor(t *testing.T) {
is := is.New(t)
tests := []struct {
name string
input string
want []*StyledText
wantErr bool
}{
{"Red", "\u001B[38;2;255;0;0mRed\u001B[0m", []*StyledText{
{Label: "Red", FgCol: &Col{Rgb: Rgb{255, 0, 0}, Hex: "#ff0000"}},
}, false},
{"Red, text, Green", "\u001B[38;2;255;0;0mRed\u001B[0mI am plain text\u001B[38;2;0;255;0mGreen\u001B[0m", []*StyledText{
{Label: "Red", FgCol: &Col{Rgb: Rgb{255, 0, 0}, Hex: "#ff0000"}},
{Label: "I am plain text"},
{Label: "Green", FgCol: &Col{Rgb: Rgb{0, 255, 0}, Hex: "#00ff00"}},
}, false},
{"Bad 1", "\u001B[38;2;256;0;0mRed\u001B[0m", nil, true},
{"Bad 2", "\u001B[38;2;-1;0;0mRed\u001B[0m", nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseANSI(tt.input)
is.Equal(err != nil, tt.wantErr)
for index, w := range tt.want {
is.Equal(got[index].Label, w.Label)
if w.FgCol != nil {
is.Equal(got[index].FgCol.Hex, w.FgCol.Hex)
is.Equal(got[index].FgCol.Rgb, w.FgCol.Rgb)
}
is.Equal(got[index].Style, w.Style)
}
})
}
}

View File

@@ -2,6 +2,7 @@ package options
import (
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/menu"
)
// Default options for creating the App
@@ -11,3 +12,8 @@ var Default = &App{
Logger: logger.NewDefaultLogger(),
LogLevel: logger.INFO,
}
var defaultMacMenu = menu.NewMenuFromItems(
menu.AppMenu(),
menu.EditMenu(),
)

View File

@@ -1,12 +1,18 @@
package mac
type ActivationPolicy int
//type ActivationPolicy int
//
//const (
// NSApplicationActivationPolicyRegular ActivationPolicy = 0
// NSApplicationActivationPolicyAccessory ActivationPolicy = 1
// NSApplicationActivationPolicyProhibited ActivationPolicy = 2
//)
const (
NSApplicationActivationPolicyRegular ActivationPolicy = 0
NSApplicationActivationPolicyAccessory ActivationPolicy = 1
NSApplicationActivationPolicyProhibited ActivationPolicy = 2
)
type AboutInfo struct {
Title string
Message string
Icon []byte
}
// Options are options specific to Mac
type Options struct {
@@ -14,6 +20,7 @@ type Options struct {
Appearance AppearanceType
WebviewIsTransparent bool
WindowIsTranslucent bool
ActivationPolicy ActivationPolicy
URLHandlers map[string]func(string)
//ActivationPolicy ActivationPolicy
About *AboutInfo
//URLHandlers map[string]func(string)
}

View File

@@ -3,8 +3,11 @@ package options
import (
"context"
"embed"
"github.com/wailsapp/wails/v2/pkg/options/windows"
"log"
"runtime"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows"
"github.com/wailsapp/wails/v2/pkg/menu"
@@ -26,6 +29,7 @@ type App struct {
MaxHeight int
StartHidden bool
HideWindowOnClose bool
AlwaysOnTop bool
RGBA *RGBA
Assets embed.FS
Menu *menu.Menu
@@ -39,7 +43,7 @@ type App struct {
//ContextMenus []*menu.ContextMenu
//TrayMenus []*menu.TrayMenu
Windows *windows.Options
//Mac *mac.Options
Mac *mac.Options
}
type RGBA struct {
@@ -91,4 +95,11 @@ func MergeDefaults(appoptions *App) {
appoptions.Height = appoptions.MaxHeight
}
switch runtime.GOOS {
case "darwin":
if appoptions.Menu == nil {
appoptions.Menu = defaultMacMenu
}
}
}

View File

@@ -2,6 +2,7 @@ package runtime
import (
"context"
"github.com/wailsapp/wails/v2/pkg/options"
)
@@ -53,6 +54,11 @@ func WindowSetSize(ctx context.Context, width int, height int) {
appFrontend.WindowSetSize(width, height)
}
func WindowGetSize(ctx context.Context) (int, int) {
appFrontend := getFrontend(ctx)
return appFrontend.WindowGetSize()
}
// WindowSetMinSize sets the minimum size of the window
func WindowSetMinSize(ctx context.Context, width int, height int) {
appFrontend := getFrontend(ctx)
@@ -71,6 +77,11 @@ func WindowSetPosition(ctx context.Context, x int, y int) {
appFrontend.WindowSetPos(x, y)
}
func WindowGetPos(ctx context.Context) (int, int) {
appFrontend := getFrontend(ctx)
return appFrontend.WindowGetPos()
}
// WindowMaximise the window
func WindowMaximise(ctx context.Context) {
appFrontend := getFrontend(ctx)

View File

@@ -0,0 +1,161 @@
---
slug: wails-v2-beta-for-mac
title: Wails v2 Beta for MacOS
authors: [leaanthony]
tags: [wails, v2]
---
<div class="text--center">
<img src="/img/wails-mac.png" width="60%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
Today marks the first beta release of Wails v2 for Mac! It's taken quite a while to get to this point and I'm hoping
that today's release will give you something that's reasonably useful. There have been a number of twists and turns
to get to this point and I'm hoping, with your help, to iron out the crinkles and get the Mac port polished for the
final v2 release.
You mean this isn't ready for production? For your use case, it may well be ready, but there are still a number of
known issues so keep your eye on [this project board](https://github.com/wailsapp/wails/projects/7) and if you would
like to contribute, you'd be very welcome!
So what's new for Wails v2 for Mac vs v1? Hint: They're pretty similar to the Windows Beta :wink:
### New Features
<div class="text--center">
<img src="/img/wails-menus-mac.png" width="80%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
There were a lot of requests for native menu support. Wails has finally got you covered. Application menus are now available
and include support for most native menu features. This includes standard menu items, checkboxes, radio groups, submenus
and separators.
There were a huge number of requests in v1 for the ability to have greater control of the window itself.
I'm happy to announce that there's new runtime APIs specifically for this.
It's feature-rich and supports multi-monitor configurations. There is also an improved dialogs API: Now, you can have modern, native
dialogs with rich configuration to cater for all your dialog needs.
### Mac Specific Options
In addition to the normal application options, Wails v2 for Mac also brings some Mac extras:
- Make your window all funky and translucent, like all the pretty swift apps!
- Highly customisable titlebar
- We support the NSAppearance options for the application
- Simple config to auto-create an "About" menu
### No requirement to bundle assets
A huge pain-point of v1 was the need to condense your entire application down to single JS & CSS files. I'm happy to
announce that for v2, there is no requirement to bundle assets, in any way, shape or form. Want to load a local image? Use an
`<img>` tag with a local src path. Want to use a cool font? Copy it in and add the path to it in your CSS.
> Wow, that sounds like a webserver...
Yes, it works just like a webserver, except it isn't.
> So how do I include my assets?
You just pass a single `embed.FS` that contains all your assets into your application configuration.
They don't even need to be in the top directory - Wails will just work it out for you.
### New Development Experience
Now that assets don't need to be bundled, it's enabled a whole new development experience. The new `wails dev`
command will build and run your application, but instead of using the assets in the `embed.FS`, it loads them directly
from disk.
It also provides the additional features:
- Hot reload - Any changes to frontend assets will trigger and auto reload of the application frontend
- Auto rebuild - Any changes to your Go code will rebuild and relaunch your application
In addition to this, a webserver will start on port 34115. This will serve your application to any browser that
connects to it. All connected web browsers will respond to system events like hot reload on asset change.
In Go, we are used to dealing with structs in our applications. It's often useful to send structs to our frontend
and use them as state in our application. In v1, this was a very manual process and a bit of a burden on the
developer. I'm happy to announce that in v2, any application run in dev mode will automatically generate Typescript
models for all structs that are input or output parameters to bound methods. This enables seamless interchange of data
models between the two worlds.
In addition to this, another JS module is dynamically generated wrapping all your bound methods. This provides
JSDoc for your methods, providing code completion and hinting in your IDE. It's really cool when you get data models
auto-imported when hitting tab in an auto-generated module wrapping your Go code!
### Remote Templates
<div class="text--center">
<img src="/img/remote-mac.png" width="80%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
Getting an application up and running quickly was always a key goal for the Wails project. When we launched, we tried
to cover a lot of the modern frameworks at the time: react, vue and angular. The world of frontend development is very
opinionated, fast moving and hard to keep on top of! As a result, we found our base templates getting out of date pretty
quickly and this caused a maintenance headache. It also meant that we didn't have cool modern templates for the latest
and greatest tech stacks.
With v2, I wanted to empower the community by giving you the ability to create and host templates yourselves, rather
than rely on the Wails project. So now you can create projects using community supported templates! I hope this will
inspire developers to create a vibrant ecosystem of project templates. I'm really quite excited about what our developer
community can create!
### Native M1 Support
Thanks to the amazing support of [Mat Ryer](https://github.com/matryer/), the Wails project now supports M1 native
builds:
<div class="text--center">
<img src="/img/build-darwin-arm.png" width="80%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
You can also specify `darwin/amd64` as a target too:
<div class="text--center">
<img src="/img/build-darwin-amd.png" width="80%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
Oh, I almost forgot.... you can also do `darwin/universal`.... :wink:
<div class="text--center">
<img src="/img/build-darwin-universal.png" width="80%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
### Cross Compilation to Windows
Because Wails v2 for Windows is pure Go, you can target Windows builds without docker.
<div class="text--center">
<img src="/img/build-cross-windows.png" width="80%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
### WKWebView Renderer
V1 relied on a (now deprecated) WebView component. V2 uses the most recent WKWebKit component so expect the latest and greatest from Apple.
### In Conclusion
As I'd said in the Windows release notes, Wails v2 represents a new foundation for the project.
The aim of this release is to get feedback on the new approach, and to iron out any bugs before a full release.
Your input would be most welcome! Please direct any feedback to the [v2 Beta](https://github.com/wailsapp/wails/discussions/828)
discussion board.
And finally, I'd like to give a special thank you to all the [project sponsors](/docs/credits#sponsors), including [JetBrains](https://www.jetbrains.com?from=Wails),
whose support drive the project in many ways behind the scenes.
I look forward to seeing what people build with Wails in this next exciting phase of the project!
Lea.
PS: Linux users, you're next!
PPS: If you or your company find Wails useful, please consider [sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!

View File

@@ -25,3 +25,6 @@ Example: `wails init -n "Your Project Name" -t https://github.com/misitebao/wail
- [wails-vite-vue-ts](https://github.com/codydbentley/wails-vite-vue-ts) - Vue 3 TypeScript with Vite (and instructions to add features)
- [wails-vite-vue-the-works](https://github.com/codydbentley/wails-vite-vue-the-works) - Vue 3 TypeScript with Vite, Vuex, Vue Router, Sass, and ESLint + Prettier
## Angular
- [wails-angular-template](https://github.com/TAINCER/wails-angular-template) - Angular with TypeScript, Sass, Hot-Reload, Code-Splitting and i18n

View File

@@ -20,6 +20,9 @@ sidebar_position: 99
<a href="https://github.com/snider" style="width:100px;">
<img src="https://github.com/snider.png?size=100" width="100"/>
</a>
<a href="https://github.com/codydbentley" style="width:100px">
<img src="https://github.com/codydbentley.png?size=100" width="100"/>
</a>
<br/>
<br/>
<a href="https://github.com/matryer" style="width:100px">
@@ -58,11 +61,8 @@ sidebar_position: 99
<a href="https://github.com/jugglingjsons" style="width:50px">
<img src="https://github.com/jugglingjsons.png?size=50" width="50"/>
</a>
<a href="https://github.com/marcus-crane" style="width:50px">
<img src="https://github.com/marcus-crane.png?size=50" width="50"/>
</a>
<a href="https://github.com/codydbentley" style="width:65px">
<img src="https://github.com/codydbentley.png?size=65" width="65"/>
<a href="https://github.com/marcus-crane" style="width:65px">
<img src="https://github.com/marcus-crane.png?size=65" width="65"/>
</a>
<a href="https://github.com/bbergshaven" style="width:45px">
<img src="https://github.com/bbergshaven.png?size=45" width="45"/>
@@ -70,6 +70,18 @@ sidebar_position: 99
<a href="https://github.com/Gilgames000" style="width:45px">
<img src="https://github.com/Gilgames000.png?size=45" width="45"/>
</a>
<a href="https://github.com/ilgityildirim" style="width:50px">
<img src="https://github.com/ilgityildirim.png?size=50" width="50"/>
</a>
<a href="https://github.com/ondoki" style="width:65px">
<img src="https://github.com/ondoki.png?size=65" width="65"/>
</a>
<a href="https://github.com/questrail" style="width:50px">
<img src="https://github.com/questrail.png?size=50" width="50"/>
</a>
<a href="https://github.com/DonTomato" style="width:45px">
<img src="https://github.com/DonTomato.png?size=45" width="45"/>
</a>
`,
}}
/>

View File

@@ -7,8 +7,9 @@ sidebar_position: 1
## Supported Platforms
- Windows 10
- MacOS x64 & arm64 (due October '21)
- Linux (due December '21)
- MacOS 10.13+ (amd64)
- MacOS 11.0+ (arm64)
- Linux (due Jan '22)
## Dependencies
@@ -47,7 +48,11 @@ import TabItem from "@theme/TabItem";
{ label: "Linux", value: "Linux" },
]}
>
<TabItem value="MacOS">Coming Soon...</TabItem>
<TabItem value="MacOS">
Wails requires that the xcode command line tools are installed. This can be done by running:<br/>
<code>xcode-select --install</code>
</TabItem>
<TabItem value="Windows">
Wails requires that the <a href="https://developer.microsoft.com/en-us/microsoft-edge/webview2/">WebView2</a>{" "}
runtime is installed. Some Windows installations will already have this installed. You can check using the{" "}
@@ -62,7 +67,7 @@ import TabItem from "@theme/TabItem";
## Installing Wails
Run `go install github.com/wailsapp/wails/v2/cmd/wails@v2.0.0-beta.12` to install the Wails CLI.
Run `go install github.com/wailsapp/wails/v2/cmd/wails@v2.0.0-beta.15` to install the Wails CLI.
## System Check

View File

@@ -199,7 +199,7 @@ The format of the file is slightly different. Here is a comparison:
| frontend / serve | | Removed |
| tags | | Removed |
| | wailsjsdir | The directory to generate wailsjs modules |
| | assetdir | The directory of the frontend assets for `dev` mode |
| | assetdir | The directory of the compiled frontend assets for `dev` mode |
</p>

View File

@@ -0,0 +1,11 @@
# Overscroll
[Overscroll](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior) is the "bounce effect" you sometimes
get when you scroll beyond a page's content boundaries. This is common in mobile apps. This can be disabled using CSS:
```css
body {
overscroll-behavior: none;
}
```

View File

@@ -74,6 +74,12 @@ Example:
`wails build -clean -o myproject.exe`
:::info UPX on Apple Silicon
There are [issues](https://github.com/upx/upx/issues/446) with using UPX with Apple Silicon.
:::
## doctor
`wails doctor` will run diagnostics to ensure that your system is ready for development.
@@ -134,6 +140,8 @@ Your system is ready for Wails development!
| -wailsjsdir | The directory to generate the generated Wails JS modules | Value in `wails.json` |
| -debounce | The time to wait for reload after an asset change is detected | 100 (milliseconds) |
| -devserverurl "url" | Use 3rd party dev server url, EG Vite | "http://localhost:34115" |
| -appargs "args" | Arguments passed to the application in shell style | |
| -platform "platform" | Platform/Arch to target | `runtime.GOOS` |
If the `assetdir`, `wailsjsdir`, `debounce` or `devserverurl` flags are provided on the command line, they are saved in
`wails.json`, and become the defaults for subsequent invocations.

View File

@@ -5,7 +5,7 @@ sidebar_position: 4
# Menus
It is possible to add an application menu to Wails projects. This is achieved by defining a [Menu](#menu) struct and
calling the runtime method [MenuSetApplicationMenu](/docs/reference/runtime/menu#menusetapplicationmenu).
setting the [`Menu`](/docs/reference/options#menu) option, or by calling the runtime method [MenuSetApplicationMenu](/docs/reference/runtime/menu#menusetapplicationmenu).
It is also possible to dynamically update the menu, by updating the menu struct and calling
[MenuUpdateApplicationMenu](/docs/reference/runtime/menu#menuupdateapplicationmenu).
@@ -79,6 +79,7 @@ type MenuItem struct {
| Checked | bool | Adds check to item (Checkbox & Radio types) |
| SubMenu | [\*Menu](#menu) | Sets the submenu |
| Click | [Callback](#callback) | Callback function when menu clicked |
| Role | string | Defines a [role](#roles) for this menu item. Mac only for now. |
### Accelerator
@@ -231,3 +232,19 @@ type CallbackData struct {
The function is given a `CallbackData` struct which indicates which menu item triggered the callback. This is useful when
using radio groups that may share a callback.
### Role
:::info Roles
Roles are currently supported on Mac only.
:::
A menu item may have a role, which is essentially a pre-defined menu item. We currently support the following roles:
| Role | Description |
| ---- | ----------- |
| AppMenuRole | The standard Mac application menu. Can be created using `menu.AppMenu()` |
| EditMenuRole | The standard Mac edit menu. Can be created using `menu.EditMenu()` |

View File

@@ -28,6 +28,7 @@ func main() {
StartHidden: false,
HideWindowOnClose: false,
RGBA: &options.RGBA{R: 0, G: 0, B: 0, A: 255},
AlwaysOnTop: false,
Assets: assets,
Menu: app.applicationMenu(),
Logger: nil,
@@ -43,6 +44,24 @@ func main() {
WindowIsTranslucent: false,
DisableWindowIcon: false,
},
Mac: &mac.Options{
TitleBar: &mac.TitleBar{
TitlebarAppearsTransparent: true,
HideTitle: false,
HideTitleBar: false,
FullSizeContent: false,
UseToolbar: false,
HideToolbarSeparator: true,
},
Appearance: mac.NSAppearanceNameDarkAqua,
WebviewIsTransparent: true,
WindowIsTranslucent: false,
About: &mac.AboutInfo{
Title: "My Application",
Message: "© 2021 Me",
Icon: icon,
},
},
})
if err != nil {
log.Fatal(err)
@@ -168,6 +187,14 @@ Example: 0xFF000088 - Red at 50% transparency
This value is the RGBA value to set the window by default.
Default: 0xFFFFFFFF.
### AlwaysOnTop
Name: AlwaysOnTop
Type: bool
Indicates that the window should stay above other windows when losing focus.
### Assets
Name: Assets
@@ -184,6 +211,8 @@ Type: \*menu.Menu
The menu to be used by the application. More details about Menus in the [Menu Reference](/docs/reference/runtime/menu).
NOTE: On Mac, if no menu is specified, a default menu will be created.
### Logger
Name: Logger
@@ -273,3 +302,153 @@ Name: DisableWindowIcon
Type: bool
Setting this to true will remove the icon in the top left corner of the title bar.
## Mac Specific Options
### TitleBar
Name: TitleBar
Type: [*mac.TitleBar](#titlebar-struct)
The TitleBar struct provides the ability to configure the look and feel of the title bar.
### Appearance
Name: Appearance
Type: [AppearanceType](#appearance-type)
Appearance is used to set the style of your app in accordance with Apple's [NSAppearance](https://developer.apple.com/documentation/appkit/nsappearancename?language=objc) names.
### WebviewIsTransparent
Name: WebviewIsTransparent
Type: bool
Setting this to `true` will make the webview background transparent when an alpha value of `0` is used.
This means that if you use `rgba(0,0,0,0)`, the host window will show through.
Often combined with [WindowIsTranslucent](#WindowIsTranslucent) to make frosty-looking applications.
### WindowIsTranslucent
Name: WindowIsTranslucent
Type: bool
Setting this to `true` will make the window background translucent. Often combined
with [WebviewIsTransparent](#WebviewIsTransparent) to make frosty-looking applications.
### About
Name: About
Type: [About](#about-struct)
This configuration lets you set the title, message and icon for the "About" menu item in the app menu created by the "AppMenu" role.
#### Titlebar struct
The titlebar of the application can be customised by using the TitleBar options:
```go
type TitleBar struct {
TitlebarAppearsTransparent bool
HideTitle bool
HideTitleBar bool
FullSizeContent bool
UseToolbar bool
HideToolbarSeparator bool
}
```
| Name | Description |
| ---- | ----------- |
| TitlebarAppearsTransparent | Makes the titlebar transparent. [Apple Docs](https://developer.apple.com/documentation/appkit/nswindow/1419167-titlebarappearstransparent?language=objc) |
| HideTitle | Hides the title of the window. [Apple Docs](https://developer.apple.com/documentation/appkit/nswindowtitlevisibility?language=objc) |
| HideTitleBar | Removes [NSWindowStyleMaskTitled](https://developer.apple.com/documentation/appkit/nswindowstylemask/nswindowstylemasktitled/) from the style mask |
| FullSizeContent | Makes the webview fill the entire window. [Apple Docs](https://developer.apple.com/documentation/appkit/nswindowstylemask/nswindowstylemaskfullsizecontentview)|
| UseToolbar | Adds a default toolbar to the window. [Apple Docs](https://developer.apple.com/documentation/appkit/nstoolbar?language=objc) |
| HideToolbarSeparator | Removes the line beneath the toolbar. [Apple Docs](https://developer.apple.com/documentation/appkit/nstoolbar/1516954-showsbaselineseparator?language=objc) |
Preconfigured titlebar settings are available:
| Setting | Example |
| ------- | ------- |
|`mac.TitleBarDefault()` | ![](/img/reference/titlebar-default.png) |
|`mac.TitleBarHidden()` | ![](/img/reference/titlebar-hidden.png) |
|`mac.TitleBarHiddenInset()` | ![](/img/reference/titlebar-hidden-inset.png) |
Example:
```go
Mac: &mac.Options{
TitleBar: mac.TitleBarHiddenInset(),
}
```
Click [here](https://github.com/lukakerr/NSWindowStyles) for some inspiration on customising the titlebar.
#### Appearance type
You can specify the application's [appearance](https://developer.apple.com/documentation/appkit/nsappearance?language=objc).
| Value | Description |
| --------------- | ------------------ |
| DefaultAppearance | DefaultAppearance uses the default system value |
| NSAppearanceNameAqua | The standard light system appearance |
| NSAppearanceNameDarkAqua | The standard dark system appearance |
| NSAppearanceNameVibrantLight | The light vibrant appearance |
| NSAppearanceNameAccessibilityHighContrastAqua | A high-contrast version of the standard light system appearance |
| NSAppearanceNameAccessibilityHighContrastDarkAqua | A high-contrast version of the standard dark system appearance |
| NSAppearanceNameAccessibilityHighContrastVibrantLight | A high-contrast version of the light vibrant appearance |
| NSAppearanceNameAccessibilityHighContrastVibrantDark | A high-contrast version of the dark vibrant appearance |
Example:
```go
Mac: &mac.Options{
Appearance: mac.NSAppearanceNameDarkAqua,
}
```
#### About struct
```go
type AboutInfo struct {
Title string
Message string
Icon []byte
}
```
If these settings are provided, an "About" menu item will appear in the app menu (when using the `AppMenu` role).
Given this configuration:
```go
//go:embed build/appicon.png
var icon []byte
func main() {
err := wails.Run(&options.App{
...
Mac: &mac.Options{
About: &mac.AboutInfo{
Title: "My Application",
Message: "© 2021 Me",
Icon: icon,
},
},
})
```
The "About" menu item will appear in the app menu:
<div class="text--center">
<img src="/img/reference/about-menu.png" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
When clicked, that will open an about message box:
<div class="text--center">
<img src="/img/reference/about-dialog.png" width="40%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>

View File

@@ -9,7 +9,7 @@ The project config resides in the `wails.json` file in the project directory. Th
```json
{
"name": "[The project name]",
"assetdir": "[Relative path to your assets directory]",
"assetdir": "[Relative path to the directory containing the compiled assets]",
"frontend:install": "[The command to install node dependencies, run in the frontend directory - often `npm install`]",
"frontend:build": "[The command to build the assets, run in the frontend directory - often `npm run build`]",
"frontend:dev": "[This command is run in a separate process on `wails dev`. Useful for 3rd party watchers]",
@@ -17,7 +17,9 @@ The project config resides in the `wails.json` file in the project directory. Th
"version": "[Project config version]",
"outputfilename": "[The name of the binary]",
"debounceMS": 100, // The default time the dev server waits to reload when it detects a vhange in assets
"devserverurl": "[URL to the dev server serving local assets. Default: http://localhost:34115]"
"devserverurl": "[URL to the dev server serving local assets. Default: http://localhost:34115]",
"appargs": "[Arguments passed to the application in shell style when in dev mode]"
}
```

View File

@@ -6,50 +6,59 @@ sidebar_position: 5
## Overview
This part of the runtime provides access to native dialogs, such as File Selectors and Message boxes.Context
This part of the runtime provides access to native dialogs, such as File Selectors and Message boxes.
:::info Javascript
Dialog is currently unsupported in the JS runtime.
:::
### OpenDirectoryDialog
Opens a dialog that prompts the user to select a directory. Can be customised using [OpenDialogOptions](#opendialogoptions).
Go Signature: `OpenDirectoryDialog(ctx context.Context, dialogOptions OpenDialogOptions) (string, error)`
Returns: Selected directory (blank if the user cancelled) or an error
Opens a dialog that prompts the user to select a directory. Can be customised using [OpenDialogOptions](#OpenDialogOptions).
### OpenFileDialog
Opens a dialog that prompts the user to select a file. Can be customised using [OpenDialogOptions](#opendialogoptions).
Go Signature: `OpenFileDialog(ctx context.Context, dialogOptions OpenDialogOptions) (string, error)`
Returns: Selected file (blank if the user cancelled) or an error
Opens a dialog that prompts the user to select a file. Can be customised using [OpenDialogOptions](#OpenDialogOptions).
### OpenMultipleFilesDialog
Opens a dialog that prompts the user to select multiple files. Can be customised using [OpenDialogOptions](#opendialogoptions).
Go Signature: `OpenMultipleFilesDialog(ctx context.Context, dialogOptions OpenDialogOptions) ([]string, error)`
Returns: Selected files (nil if the user cancelled) or an error
Opens a dialog that prompts the user to select multiple files. Can be customised using [OpenDialogOptions](#OpenDialogOptions).
### SaveFileDialog
Opens a dialog that prompts the user to select a filename for the purposes of saving. Can be customised using [SaveDialogOptions](#savedialogoptions).
Go Signature: `SaveFileDialog(ctx context.Context, dialogOptions SaveDialogOptions) (string, error)`
Returns: The selected file (blank if the user cancelled) or an error
Opens a dialog that prompts the user to select a filename for the purposes of saving. Can be customised using [SaveDialogOptions](#SaveDialogOptions).
### MessageDialog
Displays a message using a message dialog. Can be customised using [MessageDialogOptions](#messagedialogoptions).
Go Signature: `MessageDialog(ctx context.Context, dialogOptions MessageDialogOptions) (string, error)`
Returns: The text of the selected button or an error
Displays a message using a message dialog. Can be customised using [MessageDialogOptions](#MessageDialogOptions).
## Options
### OpenDialogOptions
@@ -60,14 +69,27 @@ type OpenDialogOptions struct {
DefaultFilename string
Title string
Filters []FileFilter
AllowFiles bool // Mac Only
AllowDirectories bool // Mac Only
ShowHiddenFiles bool // Mac Only
CanCreateDirectories bool // Mac Only
ResolvesAliases bool // Mac Only
TreatPackagesAsDirectories bool // Mac Only
AllowFiles bool
AllowDirectories bool
ShowHiddenFiles bool
CanCreateDirectories bool
ResolvesAliases bool
TreatPackagesAsDirectories bool
}
```
| Field | Description | Win | Mac |
| -------------------------- | ---------------------------------------------- | --- | --- |
| DefaultDirectory | The directory the dialog will show when opened | ✅ | ✅ |
| DefaultFilename | The default filename | ✅ | ✅ |
| Title | Title for the dialog | ✅ | ✅ |
| [Filters](#filefilter) | A list of file filters | ✅ | ✅ |
| AllowFiles | Allow files to be selected | | ✅ |
| AllowDirectories | Allow directories to be selected | | ✅ |
| ShowHiddenFiles | Show files hidden by the system | | ✅ |
| CanCreateDirectories | Allow user to create directories | | ✅ |
| ResolvesAliases | If true, returns the file not the alias | | ✅ |
| TreatPackagesAsDirectories | Allow navigating into packages | | ✅ |
### SaveDialogOptions
@@ -77,12 +99,22 @@ type SaveDialogOptions struct {
DefaultFilename string
Title string
Filters []FileFilter
ShowHiddenFiles bool // Mac Only
CanCreateDirectories bool // Mac Only
TreatPackagesAsDirectories bool // Mac Only
ShowHiddenFiles bool
CanCreateDirectories bool
TreatPackagesAsDirectories bool
}
```
| Field | Description | Win | Mac |
| -------------------------- | ---------------------------------------------- | --- | --- |
| DefaultDirectory | The directory the dialog will show when opened | ✅ | ✅ |
| DefaultFilename | The default filename | ✅ | ✅ |
| Title | Title for the dialog | ✅ | ✅ |
| [Filters](#filefilter) | A list of file filters | ✅ | ✅ |
| ShowHiddenFiles | Show files hidden by the system | | ✅ |
| CanCreateDirectories | Allow user to create directories | | ✅ |
| TreatPackagesAsDirectories | Allow navigating into packages | | ✅ |
### MessageDialogOptions
```go
@@ -91,33 +123,76 @@ type MessageDialogOptions struct {
Title string
Message string
Buttons []string
DefaultButton string // Mac Only
CancelButton string // Mac Only
Icon string // Mac Only
DefaultButton string
CancelButton string
}
```
| Field | Description | Win | Mac |
| ------------- | ------------------------------------------------------------------------- | --- | --- |
| Type | The type of message dialog, eg question, info... | ✅ | ✅ |
| Title | Title for the dialog | ✅ | ✅ |
| Message | The message to show the user | ✅ | ✅ |
| Buttons | A list of button titles | | ✅ |
| DefaultButton | The button with this text should be treated as default. Bound to `return` | | ✅ |
| CancelButton | The button with this text should be treated as cancel. Bound to `escape` | | ✅ |
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
#### Windows
Windows has standard dialog types in which the buttons are not customisable.
The value returned will be one of: "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "Try Again" or "Continue"
#### Mac
A message dialog on Mac may specify up to 4 buttons. If no `DefaultButton` or `CancelButton` is given, the first button
is considered default and is bound to the `return` key.
For the following code:
```go
selection, err := runtime.MessageDialog(b.ctx, runtime.MessageDialogOptions{
Title: "It's your turn!",
Message: "Select a number",
Buttons: []string{"one", "two", "three", "four"},
})
```
the first button is shown as default:
<div class="text--center">
<img src="/img/runtime/dialog_no_defaults.png" width="30%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
And if we specify `DefaultButton` to be "two":
```go
selection, err := runtime.MessageDialog(b.ctx, runtime.MessageDialogOptions{
Title: "It's your turn!",
Message: "Select a number",
Buttons: []string{"one", "two", "three", "four"},
DefaultButton: "two",
})
```
the second button is shown as default. When `return` is pressed, the value "two" is returned.
<div class="text--center">
<img src="/img/runtime/dialog_default_button.png" width="30%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
If we now specify `CancelButton` to be "three":
```go
selection, err := runtime.MessageDialog(b.ctx, runtime.MessageDialogOptions{
Title: "It's your turn!",
Message: "Select a number",
Buttons: []string{"one", "two", "three", "four"},
DefaultButton: "two",
CancelButton: "three",
})
```
the button with "three" is shown at the bottom of the dialog. When `escape` is pressed, the value "three" is returned:
<div class="text--center">
<img src="/img/runtime/dialog_default_cancel.png" width="30%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
<br/>
<br/>
<Tabs
defaultValue="Windows"
values={[
{label: 'Windows', value: 'Windows'},
{label: 'MacOS', value: 'MacOS'},
{label: 'Linux', value: 'Linux'},
]}>
<TabItem value="MacOS">
Both "DefaultButton" and "CancelButton" should match a value in "Buttons".
</TabItem>
<TabItem value="Windows">
Windows has standard dialog types and the buttons are not customisable. The
value returned will be one of: "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "Try Again" or "Continue"
</TabItem>
<TabItem value="Linux">
Coming Soon...
</TabItem>
</Tabs>
#### DialogType
@@ -138,3 +213,37 @@ type FileFilter struct {
Pattern string // semi-colon separated list of extensions, EG: "*.jpg;*.png"
}
```
#### Windows
Windows allows you to use multiple file filters in dialog boxes. Each FileFilter will show up as a separate entry in the
dialog:
<div class="text--center">
<img src="/img/runtime/dialog_win_filters.png" width="50%" style={{"box-shadow": "rgb(255 255 255 / 20%) 0px 4px 8px 0px, rgb(104 104 104) 0px 6px 20px 0px"}}/>
</div>
<br/>
<br/>
<br/>
#### Mac
Mac dialogs only have the concept of a single set of patterns to filter files. If multiple FileFilters are provided,
Wails will use all the Patterns defined.
Example:
```go
selection, err := runtime.OpenFileDialog(b.ctx, runtime.OpenDialogOptions{
Title: "Select File",
Filters: []runtime.FileFilter{
{
DisplayName: "Images (*.png;*.jpg)",
Pattern: "*.png;*.jpg",
}, {
DisplayName: "Videos (*.mov;*.mp4)",
Pattern: "*.mov;*.mp4",
},
},
})
```
This will result in the Open File dialog using `*.png,*.jpg,*.mov,*.mp4` as a filter.

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