Compare commits

...

196 Commits

Author SHA1 Message Date
Lea Anthony
c5e76c50b0 v2.0.0-alpha.64 2021-04-13 21:07:24 +10:00
Lea Anthony
e40226ff7a ANSI support for tray labels and submenus 2021-04-13 21:05:56 +10:00
Lea Anthony
53a3638fa8 ANSI support for tray labels 2021-04-13 05:43:37 +10:00
Lea Anthony
1344638c52 Add OS detection 2021-04-12 06:25:15 +10:00
Lea Anthony
6fdc87454a Initial support for desktop notifications 2021-04-07 06:32:21 +10:00
Lea Anthony
c36f9501a9 Dialog warning on JS Error. Remove shutdown hack. Optional dialog callback. 2021-04-06 20:18:29 +10:00
Lea Anthony
c23b43c352 Improve vanilla app JS 2021-04-04 15:47:03 +10:00
Lea Anthony
a76851463b Font smoothing for vanilla app 2021-04-04 15:45:24 +10:00
Lea Anthony
e17b432c8f Tidy up templates 2021-04-04 15:36:55 +10:00
Lea Anthony
5d444cd6dd Support user tags 2021-04-04 13:42:48 +10:00
Lea Anthony
be43049fc6 Remove debug code 2021-04-04 05:25:21 +10:00
Lea Anthony
2e01710412 v2.0.0-alpha.63 2021-04-04 05:15:09 +10:00
Lea Anthony
1b0193161c Improvements for handling Info.plist 2021-04-04 05:14:16 +10:00
Lea Anthony
1b377fb575 Support enter as alias for return 2021-04-03 16:52:54 +11:00
Lea Anthony
1203ae64b8 v2.0.0-alpha.62 2021-03-31 15:44:28 +11:00
Lea Anthony
26ed8002b9 Menu bar font fix 2021-03-31 15:43:28 +11:00
Lea Anthony
cf23bffc67 v2.0.0-alpha.61 2021-03-30 22:22:48 +11:00
Lea Anthony
d70f6fffe7 Remove autorelease of colour. Fixes high sierra. 2021-03-30 22:21:49 +11:00
Lea Anthony
54c99fc386 Move some methods to main thread 2021-03-29 20:45:50 +11:00
Lea Anthony
86c1ea5e6a Initial support for compression 2021-03-27 20:59:14 +11:00
Lea Anthony
b394c1914c v2.0.0-alpha.60 2021-03-26 20:54:42 +11:00
Lea Anthony
91c2ddf155 Allow styling of menu items that have submenus 2021-03-26 20:54:19 +11:00
Lea Anthony
712ad96d2a v2.0.0-alpha.59 2021-03-26 18:30:00 +11:00
Lea Anthony
86b4a4f2f5 Don't clean directory when doing universal builds 2021-03-26 18:29:28 +11:00
Lea Anthony
4b9786abc9 v2.0.0-alpha.58 2021-03-26 18:13:10 +11:00
Lea Anthony
fd96ebc050 Better verbose output 2021-03-26 18:12:42 +11:00
Lea Anthony
939e0f5975 Use default broken image for invalid images 2021-03-26 17:51:49 +11:00
Lea Anthony
6a7a288a0f Limit StartsAtLogin to app bundles 2021-03-26 16:52:59 +11:00
Lea Anthony
0564d0aa98 v2.0.0-alpha.57 2021-03-26 16:32:10 +11:00
Lea Anthony
3a136a73ca Add package for mac functions 2021-03-26 15:57:30 +11:00
Lea Anthony
50c219307f Add clean flag 2021-03-26 14:10:25 +11:00
Lea Anthony
de3038b302 v2.0.0-alpha.56 2021-03-25 21:13:40 +11:00
Lea Anthony
6eb4b0a419 Fix packaging universal builds 2021-03-25 21:12:29 +11:00
Lea Anthony
41d2158375 Support building arm64 & universal binaries. 2021-03-25 21:12:29 +11:00
Lea Anthony
5d7f57e80b Initial ARM support! 2021-03-25 21:12:29 +11:00
Lea Anthony
4ce3e1d1bf v2.0.0-alpha.55 2021-03-21 20:59:23 +11:00
Lea Anthony
e5f2746810 Better font name support 2021-03-21 20:57:41 +11:00
Lea Anthony
92ebf506dd Get app compiling 2021-03-20 18:32:09 +11:00
Lea Anthony
9ab06152c5 Refactor doctor for windows 2021-03-20 16:27:35 +11:00
Lea Anthony
bf36b6a59d Doctor working for Windows 2021-03-20 16:24:02 +11:00
Lea Anthony
4b9f6c4fb1 Update CONTRIBUTORS.md 2021-03-20 15:55:14 +11:00
Amaury Tobias Quiroz
b1a42c8dea fix: support rel="modulepreload" on assetbundle as javascript (#621)
In some bundler enviroments like vite the output on index.html uses link
tag with rel="modulepreload" to load javascript, add support to handle
this files on assetbundle, more info
https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/modulepreload

fix #620

Co-authored-by: amaury-tobias <amaury.tobiasqr@gmai.com>
2021-03-20 15:51:57 +11:00
Travis McLane
cbd98b5a1a update scripts/build.sh to run test only on v1 2021-03-20 15:01:09 +11:00
Lea Anthony
c8e0aea69c v2.0.0-alpha.54 2021-03-19 09:17:18 +11:00
Lea Anthony
7c0b236eb0 Fix for hiding window on close 2021-03-19 09:14:49 +11:00
Lea Anthony
16debbd109 v2.0.0-alpha.53 2021-03-18 20:55:26 +11:00
Lea Anthony
39bfa5d910 Support disabling tray menu. Fix font sizing. Tooltip in tray menu support. 2021-03-18 20:54:53 +11:00
Lea Anthony
6424579a9e v2.0.0-alpha.52 2021-03-17 23:30:42 +11:00
Lea Anthony
a962ae6f63 Support rich text in Tray labels 2021-03-17 23:30:08 +11:00
Lea Anthony
c7dee158ba tray menu Icon->Image. Support template images. 2021-03-17 22:24:09 +11:00
Lea Anthony
237d25089d Update CLI banner 2021-03-17 21:59:31 +11:00
Lea Anthony
265328d648 v2.0.0-alpha.51 2021-03-15 06:13:51 +11:00
Lea Anthony
6f40e85a6e Support startup urls when app not running 2021-03-15 06:13:00 +11:00
Lea Anthony
96d8509da3 v2.0.0-alpha.50 2021-03-13 15:17:35 +11:00
Lea Anthony
598445ab0f v2.0.0-alpha.49 2021-03-13 15:00:19 +11:00
Lea Anthony
24788e2fd3 Fix template images 2021-03-13 14:59:12 +11:00
Lea Anthony
bbf4dde43f Support upserting environment variables 2021-03-12 23:41:13 +11:00
Lea Anthony
0afd27ab45 Add FileLogger option 2021-03-12 23:40:35 +11:00
Lea Anthony
d498423ec2 v2.0.0-alpha.48 2021-03-09 22:28:07 +11:00
Mat Ryer
66ce84973c fixes for machines running TouchBar addresses https://github.com/matryer/xbar/issues/610 2021-03-09 11:24:46 +00:00
Lea Anthony
55e6a0f312 v2.0.0-alpha.47 2021-03-07 16:24:20 +11:00
Lea Anthony
81e83fdf18 Ensure modifiers are lowercase when parsing 2021-03-07 16:21:30 +11:00
Lea Anthony
f9b79d24f8 Guard against nil url messages 2021-03-06 15:51:06 +11:00
Lea Anthony
0599a47bfe v2.0.0-alpha.46 2021-03-06 15:43:44 +11:00
Lea Anthony
817c55d318 Support base64 images in tray 2021-03-06 15:43:11 +11:00
Lea Anthony
14146c8c0c v2.0.0-alpha.45 2021-03-06 00:29:00 +11:00
Lea Anthony
18adac20d4 Tray menu open/close events 2021-03-06 00:25:34 +11:00
Lea Anthony
eb4bff89da v2.0.0-alpha.44 2021-03-04 06:18:31 +11:00
Lea Anthony
c66dc777f3 Remove debug logging 2021-03-04 06:18:11 +11:00
Lea Anthony
9003462457 v2.0.0-alpha.43 2021-03-04 06:09:17 +11:00
Lea Anthony
e124f0a220 Support Alternative menu items 2021-03-04 06:07:45 +11:00
Lea Anthony
c6d3f57712 v2.0.0-alpha.42 2021-03-01 08:49:31 +11:00
Lea Anthony
b4c669ff86 Support custom protocols 2021-02-28 22:08:23 +11:00
Lea Anthony
2d1b2c0947 Guard app signing 2021-02-28 15:29:15 +11:00
Lea Anthony
4a0c5aa785 v2.0.0-alpha.41 2021-02-27 20:33:42 +11:00
Lea Anthony
f48d7f8f60 Add support for -sign 2021-02-27 20:32:29 +11:00
Lea Anthony
651f24f641 update vanilla template 2021-02-27 20:06:49 +11:00
Lea Anthony
8fd77148ca update vanilla template 2021-02-27 16:59:16 +11:00
Lea Anthony
0dc0762fdf update vanilla template 2021-02-27 16:45:30 +11:00
Lea Anthony
1a92550709 update vanilla template 2021-02-27 16:08:53 +11:00
Lea Anthony
bffc15bc14 fix: terminate app on window close 2021-02-27 14:49:44 +11:00
Lea Anthony
198d206c46 Update basic template to new API 2021-02-27 14:07:27 +11:00
Lea Anthony
bb8e848ef6 Run go mod tidy before compilation 2021-02-27 14:03:54 +11:00
Lea Anthony
bac3e9e5c1 Fix asset dir perms 2021-02-27 13:54:39 +11:00
Lea Anthony
bc5eddeb66 v2.0.0-alpha.40 2021-02-26 15:31:37 +11:00
Lea Anthony
8e7258d812 Add locking for tray operations 2021-02-26 15:23:39 +11:00
Lea Anthony
7118762cec v2.0.0-alpha.39 2021-02-25 22:05:38 +11:00
Lea Anthony
6af92cf0a4 Delete Tray for bridge 2021-02-25 19:57:36 +11:00
matryer
1bb91634f7 avoid fmt in happy path 2021-02-25 07:39:42 +00:00
Lea Anthony
f71ce7913f v2.0.0-alpha.38 2021-02-24 21:50:39 +11:00
Lea Anthony
53db687a26 Support DeleteTrayMenuByID 2021-02-24 21:49:55 +11:00
Lea Anthony
13939d3d6b v2.0.0-alpha.37 2021-02-23 20:15:01 +11:00
Lea Anthony
552c6b8711 fix: modifiers 2021-02-23 18:57:59 +11:00
Lea Anthony
feee2b3db2 v2.0.0-alpha.36 2021-02-23 08:53:26 +11:00
Lea Anthony
9889c2bdbb Support Activation Policy 2021-02-23 08:52:56 +11:00
Lea Anthony
2432fccf71 v2.0.0-alpha.35 2021-02-22 20:17:22 +11:00
Lea Anthony
70510fd180 @wails/runtime v1.3.10 2021-02-22 20:16:14 +11:00
Lea Anthony
17c6201469 Menu off by default in dev. Toggle with backtick. 2021-02-22 20:14:44 +11:00
Lea Anthony
0f209c8900 v2.0.0-alpha.34 2021-02-22 19:39:07 +11:00
Lea Anthony
cbf043585c v2.0.0-alpha.33 2021-02-22 19:33:44 +11:00
Lea Anthony
5ae621ceaa Start signal handler a little later 2021-02-22 19:33:18 +11:00
Lea Anthony
1231b59443 better signal handling for shutdown 2021-02-22 17:39:33 +11:00
Lea Anthony
b18d4fbf41 v2.0.0-alpha.32 2021-02-22 09:00:47 +11:00
Lea Anthony
9ec5605e63 fix: loglevel duplication 2021-02-22 09:00:09 +11:00
Lea Anthony
98a4de8878 v2.0.0-alpha.31 2021-02-21 20:38:14 +11:00
Lea Anthony
5fe709f558 Trigger clean shutdown on Quit 2021-02-21 20:37:42 +11:00
Lea Anthony
5231a6893b v2.0.0-alpha.30 2021-02-21 19:28:07 +11:00
Lea Anthony
74f3ce990f Support loglevel flag in dev mode 2021-02-21 19:26:20 +11:00
Lea Anthony
998a913853 Hide dev warnings by default 2021-02-21 16:36:56 +11:00
Lea Anthony
964844835c Better Wails update messaging 2021-02-21 06:12:19 +11:00
Lea Anthony
4e152bb849 v2.0.0-alpha.29 2021-02-21 05:56:11 +11:00
Lea Anthony
51133d098c Slight refactor of backend module generation 2021-02-21 05:52:42 +11:00
Lea Anthony
d4191e7d1b Support parsing keyboard shortcuts 2021-02-20 21:32:32 +11:00
Lea Anthony
9c273bc745 v2.0.0-alpha.28 2021-02-20 16:04:42 +11:00
Lea Anthony
c6f6ad6beb Improved dev reload. Early abort for bad app. Don't reload if bad build. 2021-02-20 16:04:03 +11:00
Lea Anthony
4362a14459 Dev colours 2021-02-20 15:25:40 +11:00
Lea Anthony
0080e9e311 Gimme some colour 2021-02-20 15:14:20 +11:00
Lea Anthony
83d9297cac v2.0.0-alpha.27 2021-02-20 14:51:38 +11:00
Lea Anthony
d8ad250608 Template Image support 2021-02-19 05:25:23 +11:00
Lea Anthony
eac8880f6d Initial support for tray menus 2021-02-13 20:26:21 +11:00
Lea Anthony
f47100e71c Sort generated methods to appease git diff. 2021-02-13 18:04:00 +11:00
Lea Anthony
9a81a57d13 Revert bootstrapping. 2021-02-12 15:08:54 +11:00
Lea Anthony
354429bc28 Support UpdateTrayLabel in dev mode. Small refactor. 2021-02-12 14:52:47 +11:00
Lea Anthony
99d4d9490c Improved dialog support for dev mode 2021-02-12 14:50:58 +11:00
Lea Anthony
61afe711bd v2.0.0-alpha.26 2021-02-11 07:17:28 +11:00
Lea Anthony
6e3cfc157f Add bridge.js 2021-02-11 07:09:40 +11:00
Lea Anthony
30d762372f @wails/runtime v1.3.9 2021-02-11 07:04:01 +11:00
Lea Anthony
2dbaabb74c Keep package.json files 2021-02-11 07:02:57 +11:00
Lea Anthony
f8bae0430f Add windowDrag shim for bridge 2021-02-11 06:52:07 +11:00
Lea Anthony
21c07497d7 Move bridge to Svelte build. Initial support for trays in dev mode 2021-02-10 22:53:14 +11:00
Lea Anthony
9b9bcd657f v2.0.0-alpha.25 2021-02-09 21:21:51 +11:00
Lea Anthony
02038aa543 Bugfix dialogs 2021-02-09 21:21:31 +11:00
Lea Anthony
9910d1127a v2.0.0-alpha.24 2021-02-09 21:11:11 +11:00
Lea Anthony
21a0245985 Generate bindings in package. Support dialogs in dev mode. 2021-02-09 21:10:06 +11:00
Lea Anthony
e860f3a00e v2.0.0-alpha.23 2021-02-08 08:57:32 +11:00
Lea Anthony
2e480d2c52 Move to Go 1.16 2021-02-08 08:55:18 +11:00
Lea Anthony
2c0f93d647 v2.0.0-alpha.22 2021-02-08 06:52:21 +11:00
Lea Anthony
41f973c7d5 Update runtime package 2021-02-08 06:51:49 +11:00
Lea Anthony
2d1447d558 darwin bridge js 2021-02-08 06:37:57 +11:00
Lea Anthony
7c22cbcf38 Bridge working for calls. Notify TBD 2021-02-08 06:36:13 +11:00
Lea Anthony
e4b03f510b First step to bridge support 2021-02-06 21:50:21 +11:00
Lea Anthony
8dfd206aa9 Updates for using the bridge 2021-02-06 13:36:56 +11:00
Lea Anthony
6dabab1d2e v2.0.0-alpha.21 2021-02-03 07:20:08 +11:00
Lea Anthony
c3bd8b1a85 Remove debug info 2021-02-03 07:15:52 +11:00
Lea Anthony
e1b7332c47 Graceful shutdown 2021-02-03 07:14:44 +11:00
Lea Anthony
5cd08e45f0 v2.0.0-alpha.20 2021-01-31 22:50:01 +11:00
Lea Anthony
c2d03f0e6e Update NSFontWeightRegular to float const 2021-01-31 22:49:29 +11:00
Lea Anthony
d3501f4cb7 v2.0.0-alpha.19 2021-01-31 21:59:16 +11:00
Lea Anthony
ee82cd25b7 Menu Items default to 12 pt on Mac 2021-01-31 21:58:41 +11:00
Lea Anthony
bbb07e17d9 v2.0.0-alpha.18 2021-01-31 21:20:40 +11:00
Lea Anthony
e6b40b55c4 Add nil guard for binding 2021-01-31 21:10:30 +11:00
Lea Anthony
7573f68df3 Add argument guard for methods 2021-01-31 15:35:33 +11:00
Lea Anthony
ceaacc7ba9 Re-added Store.Set() using deep copy 2021-01-30 21:36:59 +11:00
Lea Anthony
0e24f75753 Deep copy initial value in store 2021-01-30 21:29:36 +11:00
Lea Anthony
82b9deeee4 Unexport set 2021-01-30 20:47:10 +11:00
Lea Anthony
cfa40b797f More race condition fixes 2021-01-29 13:03:05 +11:00
Lea Anthony
5aeb68acb7 Fix for calculating new window size 2021-01-28 19:26:26 +11:00
Lea Anthony
b81101414f Support Min/Max Size in Window runtime 2021-01-28 18:48:07 +11:00
Lea Anthony
7ae89d04bb Fixed data race in store. Handle nils better 2021-01-28 07:15:29 +11:00
Lea Anthony
1c566f3802 Fixed data race in servicebus 2021-01-28 06:23:49 +11:00
Lea Anthony
c9c3c9ab90 Don't bind startup/shutdown methods 2021-01-27 21:12:17 +11:00
Lea Anthony
56394ac50e Use mutex when doing a Store resync 2021-01-27 06:03:17 +11:00
Lea Anthony
f7c2f12ab2 Added error handler for dealing with loading user js code 2021-01-26 16:01:06 +11:00
Lea Anthony
a6bb6e0c93 v2.0.0-alpha.17 2021-01-26 12:30:45 +11:00
Lea Anthony
4a5863e6ac Ported update command 2021-01-26 12:30:14 +11:00
Lea Anthony
913fe8d184 v2.0.0-alpha.16 2021-01-26 11:11:23 +11:00
Lea Anthony
4ce8130cdf Replace CreateApp with single Run() method 2021-01-26 11:09:17 +11:00
Lea Anthony
fe87463b78 Move Bind() into app config 2021-01-26 07:04:12 +11:00
Lea Anthony
fe0f0e29e8 Update CreateApp API 2021-01-26 06:45:23 +11:00
Lea Anthony
83d6dac7cf Add appType to builds. Update server code to compile 2021-01-26 06:40:41 +11:00
Lea Anthony
02500e0930 Update vanilla template with new API 2021-01-26 06:40:18 +11:00
Lea Anthony
5e1187f437 Startup/Shutdown hook warnings 2021-01-26 06:39:54 +11:00
Lea Anthony
064ff3b65e Change build wording 2021-01-26 06:38:54 +11:00
Lea Anthony
b5c7019bf0 Fix compile warnings 2021-01-26 06:37:33 +11:00
Lea Anthony
e9d16e77a3 Add support for loglevel flag in debug builds 2021-01-25 21:42:31 +11:00
Lea Anthony
2415d4c531 v2.0.0-alpha.15 2021-01-25 21:21:44 +11:00
Lea Anthony
3f75213ce3 Small linting fixes 2021-01-25 21:05:20 +11:00
Lea Anthony
6120ceabf1 Support Fonts & Colours for menuitems 2021-01-25 21:04:57 +11:00
Lea Anthony
95a95d1750 Ensure store does initial resync 2021-01-25 21:02:36 +11:00
Lea Anthony
d923e84456 v2.0.0-alpha.14 2021-01-23 21:00:04 +11:00
Lea Anthony
343f573e78 Fix menu tooltips 2021-01-23 20:59:02 +11:00
Lea Anthony
c6d87da4f0 v2.0.0-alpha.13 2021-01-23 20:39:43 +11:00
Lea Anthony
a9faebe51a MenuItem image support 2021-01-23 20:32:42 +11:00
Lea Anthony
d436f5d1be v2.0.0-alpha.12 2021-01-23 16:22:00 +11:00
Lea Anthony
f40899821f Support ToolTips 2021-01-23 16:14:48 +11:00
Lea Anthony
2a64ed19a3 v2.0.0-alpha.11 2021-01-22 14:57:50 +11:00
Lea Anthony
47bca0be88 Support updating tray labels in an efficient manner 2021-01-16 11:35:49 +11:00
Lea Anthony
7ac8cc6b8b Add Menu.Merge() to combine 2 menus 2021-01-15 11:53:55 +11:00
Lea Anthony
b435ec1217 v2.0.0-alpha.10 2021-01-14 20:43:26 +11:00
Lea Anthony
688d4fee6a UpdateTrayMenu -> SetTrayMenu (upsert). Support no menus on trays. 2021-01-14 16:13:59 +11:00
Lea Anthony
29ffeaa9f3 Misc tidy up 2021-01-14 13:55:20 +11:00
Lea Anthony
742e4ba2cb Remove WailsInit and WailsShutdown methodsr 2021-01-14 11:07:06 +11:00
Lea Anthony
0a0063de1f bump version 2021-01-14 00:28:15 +11:00
Lea Anthony
1b7d1e61cc v2.0.0-alpha.8 2021-01-13 23:47:15 +11:00
Lea Anthony
15a273458e Ensure build directory exists when building 2021-01-13 23:46:47 +11:00
Lea Anthony
eef8eb756f Bump version 2021-01-13 22:56:09 +11:00
189 changed files with 9206 additions and 2383 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +1 @@
bridge.js
index.js

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
echo "**** Checking if Wails passes unit tests ****"
if ! go test ./...
if ! go test ./lib/... ./runtime/... ./cmd/...
then
echo ""
echo "ERROR: Unit tests failed!"

View File

@@ -3,8 +3,10 @@ package build
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"text/tabwriter"
"time"
"github.com/leaanthony/clir"
@@ -23,8 +25,8 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
command := app.NewSubCommand("build", "Builds the application")
// Setup target type flag
description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
//description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
//command.StringFlag("t", description, &outputType)
// Setup production flag
production := false
@@ -37,28 +39,50 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
compress := false
command.BoolFlag("compress", "Compress final binary", &compress)
// Setup Platform flag
platform := runtime.GOOS
command.StringFlag("platform", "Platform to target", &platform)
// Quiet Build
quiet := false
command.BoolFlag("q", "Suppress output to console", &quiet)
// Verbosity
verbosity := 1
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
// ldflags to pass to `go`
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
// tags to pass to `go`
tags := ""
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &tags)
// Log to file
logFile := ""
command.StringFlag("l", "Log to file", &logFile)
//logFile := ""
//command.StringFlag("l", "Log to file", &logFile)
// Retain assets
keepAssets := false
command.BoolFlag("k", "Keep generated assets", &keepAssets)
// Retain assets
outputFilename := ""
command.StringFlag("o", "Output filename", &outputFilename)
// Clean build directory
cleanBuildDirectory := false
command.BoolFlag("clean", "Clean the build directory before building", &cleanBuildDirectory)
appleIdentity := ""
if runtime.GOOS == "darwin" {
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
}
command.Action(func() error {
quiet := verbosity == 0
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
@@ -72,9 +96,10 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
app.PrintBanner()
}
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
// Ensure package is used with apple identity
if appleIdentity != "" && pack == false {
return fmt.Errorf("must use `-package` flag when using `-sign`")
}
// Setup mode
mode := build.Debug
@@ -82,18 +107,87 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
mode = build.Production
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
"darwin/amd64",
"darwin/arm64",
"darwin/universal",
//"linux/amd64",
//"linux/arm-7",
//"windows/amd64",
})
if !validPlatformArch.Contains(platform) {
return fmt.Errorf("platform %s is not supported", platform)
}
if compress && platform == "darwin/universal" {
println("Warning: compress flag unsupported for universal binaries. Ignoring.")
compress = false
}
// Tags
userTags := []string{}
for _, tag := range strings.Split(tags, " ") {
thisTag := strings.TrimSpace(tag)
if thisTag != "" {
userTags = append(userTags, thisTag)
}
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Mode: mode,
Pack: pack,
Platform: platform,
LDFlags: ldflags,
Compiler: compilerCommand,
KeepAssets: keepAssets,
Logger: logger,
OutputType: outputType,
OutputFile: outputFilename,
CleanBuildDirectory: cleanBuildDirectory,
Mode: mode,
Pack: pack,
LDFlags: ldflags,
Compiler: compilerCommand,
KeepAssets: keepAssets,
AppleIdentity: appleIdentity,
Verbosity: verbosity,
Compress: compress,
UserTags: userTags,
}
// Calculate platform and arch
platformSplit := strings.Split(platform, "/")
buildOptions.Platform = platformSplit[0]
buildOptions.Arch = runtime.GOARCH
if len(platformSplit) == 2 {
buildOptions.Arch = platformSplit[1]
}
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
buildModeText := "debug"
if production {
buildModeText = "production"
}
// Write out the system information
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "App Type: \t%s\n", buildOptions.OutputType)
fmt.Fprintf(w, "Platform: \t%s\n", buildOptions.Platform)
fmt.Fprintf(w, "Arch: \t%s\n", buildOptions.Arch)
fmt.Fprintf(w, "Compiler: \t%s\n", buildOptions.Compiler)
fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
fmt.Fprintf(w, "Build Mode: \t%s\n", buildModeText)
fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
fmt.Fprintf(w, "Clean Build Dir: \t%t\n", buildOptions.CleanBuildDirectory)
fmt.Fprintf(w, "KeepAssets: \t%t\n", buildOptions.KeepAssets)
fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
if len(buildOptions.OutputFile) > 0 {
fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
}
fmt.Fprintf(w, "\n")
w.Flush()
return doBuild(buildOptions)
})
}

View File

@@ -5,33 +5,42 @@ import (
"io"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
func LogGreen(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.Green(text))
}
func LogRed(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.Red(text))
}
func LogDarkYellow(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.DarkYellow(text))
}
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("dev", "Development mode")
outputType := "desktop"
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
// Setup target type flag
description := "Type of application to develop. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
// Passthrough ldflags
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
@@ -42,15 +51,17 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// extensions to trigger rebuilds
extensions := "go"
command.StringFlag("m", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
// extensions to trigger rebuilds
showWarnings := false
command.BoolFlag("w", "Show warnings", &showWarnings)
loglevel := ""
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
command.Action(func() error {
// Validate inputs
if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType)
}
// Create logger
logger := clilogger.New(w)
app.PrintBanner()
@@ -64,7 +75,6 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
defer watcher.Close()
var debugBinaryProcess *process.Process = nil
var buildFrontend bool = true
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
// Setup signal handler
@@ -75,8 +85,13 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Do initial build
logger.Println("Building application for development...")
debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
newProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
if newProcess != nil {
debugBinaryProcess = newProcess
}
if err != nil {
return err
}
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Println("event: %+v", event)
@@ -89,7 +104,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
if err != nil {
logger.Fatal("%s", err.Error())
}
logger.Println("Watching directory: %s", event.Name)
LogGreen("[New Directory] Watching new directory: %s", event.Name)
}
}
return
@@ -98,38 +113,33 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
// logger.Println("modified file: %s", event.Name)
var rebuild bool = false
// Iterate all file patterns
for _, pattern := range extensionsThatTriggerARebuild {
rebuild = strings.HasSuffix(event.Name, pattern)
if err != nil {
logger.Fatal(err.Error())
}
if rebuild {
// Only build frontend when the file isn't a Go file
buildFrontend = !strings.HasSuffix(event.Name, "go")
if strings.HasSuffix(event.Name, pattern) {
rebuild = true
break
}
}
if !rebuild {
logger.Println("Filename change: %s did not match extension list %s", event.Name, extensions)
if showWarnings {
LogDarkYellow("[File change] %s did not match extension list (%s)", event.Name, extensions)
}
return
}
if buildFrontend {
logger.Println("Full rebuild triggered: %s updated", event.Name)
} else {
logger.Println("Partial build triggered: %s updated", event.Name)
}
LogGreen("[Attempting rebuild] %s updated", event.Name)
// Do a rebuild
// Try and build the app
newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
if err != nil {
fmt.Printf("Error during build: %s", err.Error())
return
}
// If we have a new process, save it
if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess
@@ -139,23 +149,28 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
})
// Get project dir
dir, err := os.Getwd()
projectDir, err := os.Getwd()
if err != nil {
return err
}
// Get all subdirectories
dirs, err := fs.GetSubdirectories(dir)
dirs, err := fs.GetSubdirectories(projectDir)
if err != nil {
return err
}
LogGreen("Watching (sub)/directory: %s", projectDir)
// Setup a watcher for non-node_modules directories
dirs.Each(func(dir string) {
if strings.Contains(dir, "node_modules") {
return
}
logger.Println("Watching directory: %s", dir)
// Ignore build directory
if strings.HasPrefix(dir, filepath.Join(projectDir, "build")) {
return
}
err = watcher.Add(dir)
if err != nil {
logger.Fatal(err.Error())
@@ -167,7 +182,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
for quit == false {
select {
case <-quitChannel:
println()
LogGreen("\nCaught quit")
// Notify debouncer to quit
debounceQuit <- true
quit = true
@@ -182,7 +197,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
}
}
logger.Println("Development mode exited")
LogGreen("Development mode exited")
return nil
})
@@ -209,15 +224,15 @@ exit:
}
}
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string) (*process.Process, error) {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend)
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
println()
if err != nil {
logger.Println("[ERROR] Build Failed: %s", err.Error())
return nil
LogRed("Build error - continuing to run current version")
LogDarkYellow(err.Error())
return nil, nil
}
logger.Println("Build new binary: %s", appBinary)
// Kill existing binary if need be
if debugBinaryProcess != nil {
@@ -233,7 +248,7 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
// TODO: Generate `backend.js`
// Start up new binary
newProcess := process.NewProcess(logger, appBinary)
newProcess := process.NewProcess(logger, appBinary, "-loglevel", loglevel)
err = newProcess.Start()
if err != nil {
// Remove binary
@@ -244,13 +259,13 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
logger.Fatal("Unable to start application: %s", err.Error())
}
return newProcess
return newProcess, nil
}
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
// Create random output file
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
outputFile := fmt.Sprintf("dev-%d", time.Now().Unix())
// Create BuildOptions
buildOptions := &build.Options{
@@ -262,7 +277,7 @@ func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, co
LDFlags: ldflags,
Compiler: compilerCommand,
OutputFile: outputFile,
IgnoreFrontend: !buildFrontend,
IgnoreFrontend: true,
}
return build.Build(buildOptions)

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
@@ -9,7 +10,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@@ -39,11 +39,10 @@ github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGn
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
@@ -62,17 +61,13 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
@@ -84,6 +79,10 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c=
github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28=
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY=
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -100,33 +99,26 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82 h1:shxDsb9Dz27xzk3A0DxP0JuJnZMpKrdg8+E14eiUAX4=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

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

View File

@@ -1,4 +1,4 @@
// +build !desktop,!hybrid,!server
// +build !desktop,!hybrid,!server,!dev
package app
@@ -8,9 +8,10 @@ package app
// will be unknown and the application will not work as expected.
import (
"github.com/wailsapp/wails/v2/internal/logger"
"os"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
@@ -38,7 +39,3 @@ func (a *App) Run() error {
os.Exit(1)
return nil
}
// Bind the dummy interface
func (a *App) Bind(_ interface{}) {
}

View File

@@ -3,6 +3,9 @@
package app
import (
"context"
"sync"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger"
@@ -17,6 +20,8 @@ import (
// App defines a Wails application structure
type App struct {
appType string
window *ffenestri.Application
servicebus *servicebus.ServiceBus
logger *logger.Logger
@@ -24,12 +29,13 @@ type App struct {
options *options.App
// Subsystems
log *subsystem.Log
runtime *subsystem.Runtime
event *subsystem.Event
binding *subsystem.Binding
log *subsystem.Log
runtime *subsystem.Runtime
event *subsystem.Event
//binding *subsystem.Binding
call *subsystem.Call
menu *subsystem.Menu
url *subsystem.URL
dispatcher *messagedispatcher.Dispatcher
menuManager *menumanager.Manager
@@ -43,6 +49,10 @@ type App struct {
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
}
// Create App
@@ -75,12 +85,18 @@ func CreateApp(appoptions *options.App) (*App, error) {
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
result := &App{
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
menuManager: menuManager,
appType: "desktop",
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
menuManager: menuManager,
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
}
result.options = appoptions
@@ -97,13 +113,10 @@ func (a *App) Run() error {
var err error
// Setup signal handler
signalsubsystem, err := signal.NewManager(a.servicebus, a.logger)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
// Setup a context
var subsystemWaitGroup sync.WaitGroup
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// Start the service bus
a.servicebus.Debug()
@@ -112,7 +125,7 @@ func (a *App) Run() error {
return err
}
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger)
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
if err != nil {
return err
}
@@ -126,17 +139,6 @@ func (a *App) Run() error {
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
// Start the binding subsystem
bindingsubsystem, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime())
if err != nil {
return err
}
a.binding = bindingsubsystem
err = a.binding.Start()
if err != nil {
return err
}
// Start the logging subsystem
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
@@ -159,19 +161,32 @@ func (a *App) Run() error {
return err
}
if a.options.Mac.URLHandlers != nil {
// Start the url handler subsystem
url, err := subsystem.NewURL(a.servicebus, a.logger, a.options.Mac.URLHandlers)
if err != nil {
return err
}
a.url = url
err = a.url.Start()
if err != nil {
return err
}
}
// Start the eventing subsystem
event, err := subsystem.NewEvent(a.servicebus, a.logger)
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil {
return err
}
a.event = event
a.event = eventsubsystem
err = a.event.Start()
if err != nil {
return err
}
// Start the menu subsystem
menusubsystem, err := subsystem.NewMenu(a.servicebus, a.logger, a.menuManager)
menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
if err != nil {
return err
}
@@ -182,11 +197,11 @@ func (a *App) Run() error {
}
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
a.call = call
a.call = callSubsystem
err = a.call.Start()
if err != nil {
return err
@@ -198,23 +213,42 @@ func (a *App) Run() error {
return err
}
result := a.window.Run(dispatcher, bindingDump, a.debug)
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
err = a.window.Run(dispatcher, bindingDump, a.debug)
a.logger.Trace("Ffenestri.Run() exited")
if err != nil {
return err
}
// Close down all the subsystems
a.logger.Trace("Cancelling subsystems")
cancel()
subsystemWaitGroup.Wait()
a.logger.Trace("Cancelling dispatcher")
dispatcher.Close()
// Close log
a.logger.Trace("Stopping log")
log.Close()
a.logger.Trace("Stopping Service bus")
err = a.servicebus.Stop()
if err != nil {
return err
}
return result
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil
}

244
v2/internal/app/dev.go Normal file
View File

@@ -0,0 +1,244 @@
// +build dev
package app
import (
"context"
"sync"
"github.com/wailsapp/wails/v2/internal/bridge"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/signal"
"github.com/wailsapp/wails/v2/internal/subsystem"
)
// App defines a Wails application structure
type App struct {
appType string
servicebus *servicebus.ServiceBus
logger *logger.Logger
signal *signal.Manager
options *options.App
// Subsystems
log *subsystem.Log
runtime *subsystem.Runtime
event *subsystem.Event
//binding *subsystem.Binding
call *subsystem.Call
menu *subsystem.Menu
dispatcher *messagedispatcher.Dispatcher
menuManager *menumanager.Manager
// Indicates if the app is in debug mode
debug bool
// This is our binding DB
bindings *binding.Bindings
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
// Bridge
bridge *bridge.Bridge
}
// Create App
func CreateApp(appoptions *options.App) (*App, error) {
// Merge default options
options.MergeDefaults(appoptions)
// Set up logger
myLogger := logger.New(appoptions.Logger)
// Create the menu manager
menuManager := menumanager.NewManager()
// Process the application menu
menuManager.SetApplicationMenu(options.GetApplicationMenu(appoptions))
// Process context menus
contextMenus := options.GetContextMenus(appoptions)
for _, contextMenu := range contextMenus {
menuManager.AddContextMenu(contextMenu)
}
// Process tray menus
trayMenus := options.GetTrayMenus(appoptions)
for _, trayMenu := range trayMenus {
menuManager.AddTrayMenu(trayMenu)
}
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
result := &App{
appType: "dev",
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
logger: myLogger,
servicebus: servicebus.New(myLogger),
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
bridge: bridge.NewBridge(myLogger),
menuManager: menuManager,
}
result.options = appoptions
// Initialise the app
err := result.Init()
return result, err
}
// Run the application
func (a *App) Run() error {
var err error
// Setup a context
var subsystemWaitGroup sync.WaitGroup
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// Start the service bus
a.servicebus.Debug()
err = a.servicebus.Start()
if err != nil {
return err
}
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
if err != nil {
return err
}
a.runtime = runtimesubsystem
err = a.runtime.Start()
if err != nil {
return err
}
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
// Start the logging subsystem
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
return err
}
a.log = log
err = a.log.Start()
if err != nil {
return err
}
// create the dispatcher
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
if err != nil {
return err
}
a.dispatcher = dispatcher
err = dispatcher.Start()
if err != nil {
return err
}
// Start the eventing subsystem
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil {
return err
}
a.event = eventsubsystem
err = a.event.Start()
if err != nil {
return err
}
// Start the menu subsystem
menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
if err != nil {
return err
}
a.menu = menusubsystem
err = a.menu.Start()
if err != nil {
return err
}
// Start the call subsystem
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
a.call = callSubsystem
err = a.call.Start()
if err != nil {
return err
}
// Dump bindings as a debug
bindingDump, err := a.bindings.ToJSON()
if err != nil {
return err
}
// Generate backend.js
a.bindings.GenerateBackendJS()
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug)
a.logger.Trace("Bridge.Run() exited")
if err != nil {
return err
}
// Close down all the subsystems
a.logger.Trace("Cancelling subsystems")
cancel()
subsystemWaitGroup.Wait()
a.logger.Trace("Cancelling dispatcher")
dispatcher.Close()
// Close log
a.logger.Trace("Stopping log")
log.Close()
a.logger.Trace("Stopping Service bus")
err = a.servicebus.Stop()
if err != nil {
return err
}
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil
}

View File

@@ -75,7 +75,7 @@ func CreateApp(options *Options) *App {
webserver: webserver.NewWebServer(myLogger),
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
bindings: binding.NewBindings(myLogger, options.Bind),
}
// Initialise the app
@@ -192,14 +192,3 @@ func (a *App) Run() error {
return cli.Run()
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

View File

@@ -6,10 +6,13 @@ import (
"os"
"path/filepath"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/internal/webserver"
@@ -17,12 +20,16 @@ import (
// App defines a Wails application structure
type App struct {
appType string
binding *subsystem.Binding
call *subsystem.Call
event *subsystem.Event
log *subsystem.Log
runtime *subsystem.Runtime
options *options.App
bindings *binding.Bindings
logger *logger.Logger
dispatcher *messagedispatcher.Dispatcher
@@ -30,28 +37,40 @@ type App struct {
webserver *webserver.WebServer
debug bool
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
}
// Create App
func CreateApp(options *Options) *App {
options.mergeDefaults()
// We ignore the inputs (for now)
func CreateApp(appoptions *options.App) (*App, error) {
// TODO: Allow logger output override on CLI
myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.TRACE)
// Merge default options
options.MergeDefaults(appoptions)
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
result := &App{
bindings: binding.NewBindings(myLogger),
logger: myLogger,
servicebus: servicebus.New(myLogger),
webserver: webserver.NewWebServer(myLogger),
appType: "server",
bindings: binding.NewBindings(myLogger, options.Bind),
logger: myLogger,
servicebus: servicebus.New(myLogger),
webserver: webserver.NewWebServer(myLogger),
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
}
// Initialise app
result.Init()
return result
return result, nil
}
// Run the application
@@ -88,8 +107,21 @@ func (a *App) Run() error {
if debugMode {
a.servicebus.Debug()
}
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
a.servicebus.Start()
log, err := subsystem.NewLog(a.servicebus, a.logger)
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
return err
}
@@ -102,14 +134,6 @@ func (a *App) Run() error {
a.dispatcher = dispatcher
a.dispatcher.Start()
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
if err != nil {
@@ -127,7 +151,7 @@ func (a *App) Run() error {
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
@@ -147,14 +171,3 @@ func (a *App) Run() error {
return cli.Run()
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "backend",
"version": "1.0.0",
"description": "Package to wrap backend method calls",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

View File

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

View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// BoundMethod defines all the data related to a Go method that is
@@ -17,58 +16,6 @@ type BoundMethod struct {
Method reflect.Value `json:"-"`
}
// IsWailsInit returns true if the method name is "WailsInit"
func (b *BoundMethod) IsWailsInit() bool {
return strings.HasSuffix(b.Name, "WailsInit")
}
// IsWailsShutdown returns true if the method name is "WailsShutdown"
func (b *BoundMethod) IsWailsShutdown() bool {
return strings.HasSuffix(b.Name, "WailsShutdown")
}
// VerifyWailsInit checks if the WailsInit signature is correct
func (b *BoundMethod) VerifyWailsInit() error {
// Must only have 1 input
if b.InputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check input type
if !b.Inputs[0].IsType("*runtime.Runtime") {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Must only have 1 output
if b.OutputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check output type
if !b.Outputs[0].IsError() {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Input must be of type Runtime
return nil
}
// VerifyWailsShutdown checks if the WailsShutdown signature is correct
func (b *BoundMethod) VerifyWailsShutdown() error {
// Must have no inputs
if b.InputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Must have no outputs
if b.OutputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Input must be of type Runtime
return nil
}
// InputCount returns the number of inputs this bound method has
func (b *BoundMethod) InputCount() int {
return len(b.Inputs)
@@ -83,6 +30,9 @@ func (b *BoundMethod) OutputCount() int {
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
result := make([]interface{}, b.InputCount())
if len(args) != b.InputCount() {
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
}
for index, arg := range args {
typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface()

View File

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

View File

@@ -0,0 +1,174 @@
package binding
import (
"bytes"
_ "embed"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/leaanthony/slicer"
)
const _comment = `
const backend = {
main: {
"xbarApp": {
"GetCategories": () => {
window.backend.main.xbarApp.GetCategories.call(arguments);
},
/**
* @param {string} arg1
*/
"InstallPlugin": (arg1) => {
window.backend.main.xbarApp.InstallPlugin.call(arguments);
},
"GetPlugins": () => {
window.backend.main.xbarApp.GetPlugins.call(arguments);
}
}
}
}
export default backend;`
//go:embed assets/package.json
var packageJSON []byte
func (b *Bindings) GenerateBackendJS() {
store := b.db.store
var output bytes.Buffer
output.WriteString(`// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
const backend = {`)
output.WriteString("\n")
var sortedPackageNames slicer.StringSlicer
for packageName := range store {
sortedPackageNames.Add(packageName)
}
sortedPackageNames.Sort()
sortedPackageNames.Each(func(packageName string) {
packages := store[packageName]
output.WriteString(fmt.Sprintf(" \"%s\": {", packageName))
output.WriteString("\n")
var sortedStructNames slicer.StringSlicer
for structName := range packages {
sortedStructNames.Add(structName)
}
sortedStructNames.Sort()
sortedStructNames.Each(func(structName string) {
structs := packages[structName]
output.WriteString(fmt.Sprintf(" \"%s\": {", structName))
output.WriteString("\n")
var sortedMethodNames slicer.StringSlicer
for methodName := range structs {
sortedMethodNames.Add(methodName)
}
sortedMethodNames.Sort()
sortedMethodNames.Each(func(methodName string) {
methodDetails := structs[methodName]
output.WriteString(" /**\n")
output.WriteString(" * " + methodName + "\n")
var args slicer.StringSlicer
for count, input := range methodDetails.Inputs {
arg := fmt.Sprintf("arg%d", count+1)
args.Add(arg)
output.WriteString(fmt.Sprintf(" * @param {%s} %s - Go Type: %s\n", goTypeToJSDocType(input.TypeName), arg, input.TypeName))
}
returnType := "Promise"
returnTypeDetails := ""
if methodDetails.OutputCount() > 0 {
firstType := goTypeToJSDocType(methodDetails.Outputs[0].TypeName)
returnType += "<" + firstType
if methodDetails.OutputCount() == 2 {
secondType := goTypeToJSDocType(methodDetails.Outputs[1].TypeName)
returnType += "|" + secondType
}
returnType += ">"
returnTypeDetails = " - Go Type: " + methodDetails.Outputs[0].TypeName
}
output.WriteString(" * @returns {" + returnType + "} " + returnTypeDetails + "\n")
output.WriteString(" */\n")
argsString := args.Join(", ")
output.WriteString(fmt.Sprintf(" \"%s\": (%s) => {", methodName, argsString))
output.WriteString("\n")
output.WriteString(fmt.Sprintf(" return window.backend.%s.%s.%s(%s);", packageName, structName, methodName, argsString))
output.WriteString("\n")
output.WriteString(fmt.Sprintf(" },"))
output.WriteString("\n")
})
output.WriteString(fmt.Sprintf(" }"))
output.WriteString("\n")
})
output.WriteString(fmt.Sprintf(" }\n"))
output.WriteString("\n")
})
output.WriteString(`};
export default backend;`)
output.WriteString("\n")
dirname, err := fs.RelativeToCwd("frontend/src/backend")
if err != nil {
log.Fatal(err)
}
if !fs.DirExists(dirname) {
err := fs.Mkdir(dirname)
if err != nil {
log.Fatal(err)
}
}
packageJsonFile := filepath.Join(dirname, "package.json")
if !fs.FileExists(packageJsonFile) {
err := os.WriteFile(packageJsonFile, packageJSON, 0755)
if err != nil {
log.Fatal(err)
}
}
filename := filepath.Join(dirname, "index.js")
err = os.WriteFile(filename, output.Bytes(), 0755)
if err != nil {
log.Fatal(err)
}
}
func goTypeToJSDocType(input string) string {
switch true {
case input == "string":
return "string"
case input == "error":
return "Error"
case
strings.HasPrefix(input, "int"),
strings.HasPrefix(input, "uint"),
strings.HasPrefix(input, "float"):
return "number"
case input == "bool":
return "boolean"
case strings.HasPrefix(input, "[]"):
arrayType := goTypeToJSDocType(input[2:])
return "Array.<" + arrayType + ">"
default:
return "any"
}
}

View File

@@ -23,7 +23,7 @@ func isStruct(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Struct
}
func getMethods(value interface{}) ([]*BoundMethod, error) {
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
// Create result placeholder
var result []*BoundMethod
@@ -56,6 +56,11 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
fullMethodName := baseName + "." + methodName
method := structValue.MethodByName(methodName)
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
if b.exemptions.Contains(methodReflectName) {
continue
}
// Create new method
boundMethod := &BoundMethod{
Name: fullMethodName,

View File

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

View File

@@ -0,0 +1,134 @@
package bridge
import (
"github.com/wailsapp/wails/v2/pkg/options/dialog"
)
type BridgeClient struct {
session *session
// Tray menu cache to send to reconnecting clients
messageCache chan string
}
func (b BridgeClient) DeleteTrayMenuByID(id string) {
b.session.sendMessage("TD" + id)
}
func NewBridgeClient() *BridgeClient {
return &BridgeClient{
messageCache: make(chan string, 100),
}
}
func (b BridgeClient) Quit() {
b.session.log.Info("Quit unsupported in Bridge mode")
}
func (b BridgeClient) NotifyEvent(message string) {
//b.session.sendMessage("n" + message)
b.session.log.Info("NotifyEvent: %s", message)
b.session.log.Info("NotifyEvent unsupported in Bridge mode")
}
func (b BridgeClient) CallResult(message string) {
b.session.sendMessage("c" + message)
}
func (b BridgeClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
// Handled by dialog_client
}
func (b BridgeClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
// Handled by dialog_client
}
func (b BridgeClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
// Handled by dialog_client
}
func (b BridgeClient) WindowSetTitle(title string) {
b.session.log.Info("WindowSetTitle unsupported in Bridge mode")
}
func (b BridgeClient) WindowShow() {
b.session.log.Info("WindowShow unsupported in Bridge mode")
}
func (b BridgeClient) WindowHide() {
b.session.log.Info("WindowHide unsupported in Bridge mode")
}
func (b BridgeClient) WindowCenter() {
b.session.log.Info("WindowCenter unsupported in Bridge mode")
}
func (b BridgeClient) WindowMaximise() {
b.session.log.Info("WindowMaximise unsupported in Bridge mode")
}
func (b BridgeClient) WindowUnmaximise() {
b.session.log.Info("WindowUnmaximise unsupported in Bridge mode")
}
func (b BridgeClient) WindowMinimise() {
b.session.log.Info("WindowMinimise unsupported in Bridge mode")
}
func (b BridgeClient) WindowUnminimise() {
b.session.log.Info("WindowUnminimise unsupported in Bridge mode")
}
func (b BridgeClient) WindowPosition(x int, y int) {
b.session.log.Info("WindowPosition unsupported in Bridge mode")
}
func (b BridgeClient) WindowSize(width int, height int) {
b.session.log.Info("WindowSize unsupported in Bridge mode")
}
func (b BridgeClient) WindowSetMinSize(width int, height int) {
b.session.log.Info("WindowSetMinSize unsupported in Bridge mode")
}
func (b BridgeClient) WindowSetMaxSize(width int, height int) {
b.session.log.Info("WindowSetMaxSize unsupported in Bridge mode")
}
func (b BridgeClient) WindowFullscreen() {
b.session.log.Info("WindowFullscreen unsupported in Bridge mode")
}
func (b BridgeClient) WindowUnFullscreen() {
b.session.log.Info("WindowUnFullscreen unsupported in Bridge mode")
}
func (b BridgeClient) WindowSetColour(colour int) {
b.session.log.Info("WindowSetColour unsupported in Bridge mode")
}
func (b BridgeClient) DarkModeEnabled(callbackID string) {
b.session.log.Info("DarkModeEnabled unsupported in Bridge mode")
}
func (b BridgeClient) SetApplicationMenu(menuJSON string) {
b.session.log.Info("SetApplicationMenu unsupported in Bridge mode")
}
func (b BridgeClient) SetTrayMenu(trayMenuJSON string) {
b.session.sendMessage("TS" + trayMenuJSON)
}
func (b BridgeClient) UpdateTrayMenuLabel(trayMenuJSON string) {
b.session.sendMessage("TU" + trayMenuJSON)
}
func (b BridgeClient) UpdateContextMenu(contextMenuJSON string) {
b.session.log.Info("UpdateContextMenu unsupported in Bridge mode")
}
func newBridgeClient(session *session) *BridgeClient {
return &BridgeClient{
session: session,
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,144 @@
package bridge
import (
"fmt"
"os/exec"
"strconv"
"strings"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
)
type DialogClient struct {
dispatcher *messagedispatcher.DispatchClient
log *logger.Logger
}
func (d *DialogClient) DeleteTrayMenuByID(id string) {
}
func NewDialogClient(log *logger.Logger) *DialogClient {
return &DialogClient{
log: log,
}
}
func (d *DialogClient) Quit() {
}
func (d *DialogClient) NotifyEvent(message string) {
}
func (d *DialogClient) CallResult(message string) {
}
func (d *DialogClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
}
func (d *DialogClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
}
func (d *DialogClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
osa, err := exec.LookPath("osascript")
if err != nil {
d.log.Info("MessageDialog unavailable (osascript not found)")
return
}
var btns slicer.StringSlicer
defaultButton := ""
cancelButton := ""
for index, btn := range dialogOptions.Buttons {
btns.Add(strconv.Quote(btn))
if btn == dialogOptions.DefaultButton {
defaultButton = fmt.Sprintf("default button %d", index+1)
}
if btn == dialogOptions.CancelButton {
cancelButton = fmt.Sprintf("cancel button %d", index+1)
}
}
buttons := "{" + btns.Join(",") + "}"
script := fmt.Sprintf("display dialog \"%s\" buttons %s %s %s with title \"%s\"", dialogOptions.Message, buttons, defaultButton, cancelButton, dialogOptions.Title)
go func() {
out, err := exec.Command(osa, "-e", script).Output()
if err != nil {
// Assume user has pressed cancel button
if dialogOptions.CancelButton != "" {
d.dispatcher.DispatchMessage("DM" + callbackID + "|" + dialogOptions.CancelButton)
return
}
d.log.Error("Dialog had bad exit code. If this was a Cancel button, add 'CancelButton' to the dialog.MessageDialog struct. Error: %s", err.Error())
d.dispatcher.DispatchMessage("DM" + callbackID + "|error - check logs")
return
}
buttonPressed := strings.TrimSpace(strings.TrimPrefix(string(out), "button returned:"))
d.dispatcher.DispatchMessage("DM" + callbackID + "|" + buttonPressed)
}()
}
func (d *DialogClient) WindowSetTitle(title string) {
}
func (d *DialogClient) WindowShow() {
}
func (d *DialogClient) WindowHide() {
}
func (d *DialogClient) WindowCenter() {
}
func (d *DialogClient) WindowMaximise() {
}
func (d *DialogClient) WindowUnmaximise() {
}
func (d *DialogClient) WindowMinimise() {
}
func (d *DialogClient) WindowUnminimise() {
}
func (d *DialogClient) WindowPosition(x int, y int) {
}
func (d *DialogClient) WindowSize(width int, height int) {
}
func (d *DialogClient) WindowSetMinSize(width int, height int) {
}
func (d *DialogClient) WindowSetMaxSize(width int, height int) {
}
func (d *DialogClient) WindowFullscreen() {
}
func (d *DialogClient) WindowUnFullscreen() {
}
func (d *DialogClient) WindowSetColour(colour int) {
}
func (d *DialogClient) DarkModeEnabled(callbackID string) {
}
func (d *DialogClient) SetApplicationMenu(menuJSON string) {
}
func (d *DialogClient) SetTrayMenu(trayMenuJSON string) {
}
func (d *DialogClient) UpdateTrayMenuLabel(trayMenuJSON string) {
}
func (d *DialogClient) UpdateContextMenu(contextMenuJSON string) {
}

View File

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

View File

@@ -0,0 +1,89 @@
package colour
import (
"fmt"
"strings"
"github.com/wzshiming/ctc"
)
func Col(col ctc.Color, text string) string {
return fmt.Sprintf("%s%s%s", col, text, ctc.Reset)
}
func Yellow(text string) string {
return Col(ctc.ForegroundBrightYellow, text)
}
func Red(text string) string {
return Col(ctc.ForegroundBrightRed, text)
}
func Blue(text string) string {
return Col(ctc.ForegroundBrightBlue, text)
}
func Green(text string) string {
return Col(ctc.ForegroundBrightGreen, text)
}
func Cyan(text string) string {
return Col(ctc.ForegroundBrightCyan, text)
}
func Magenta(text string) string {
return Col(ctc.ForegroundBrightMagenta, text)
}
func White(text string) string {
return Col(ctc.ForegroundBrightWhite, text)
}
func Black(text string) string {
return Col(ctc.ForegroundBrightBlack, text)
}
func DarkYellow(text string) string {
return Col(ctc.ForegroundYellow, text)
}
func DarkRed(text string) string {
return Col(ctc.ForegroundRed, text)
}
func DarkBlue(text string) string {
return Col(ctc.ForegroundBlue, text)
}
func DarkGreen(text string) string {
return Col(ctc.ForegroundGreen, text)
}
func DarkCyan(text string) string {
return Col(ctc.ForegroundCyan, text)
}
func DarkMagenta(text string) string {
return Col(ctc.ForegroundMagenta, text)
}
func DarkWhite(text string) string {
return Col(ctc.ForegroundWhite, text)
}
func DarkBlack(text string) string {
return Col(ctc.ForegroundBlack, text)
}
var rainbowCols = []func(string) string{Red, Yellow, Green, Cyan, Blue, Magenta}
func Rainbow(text string) string {
var builder strings.Builder
for i := 0; i < len(text); i++ {
fn := rainbowCols[i%len(rainbowCols)]
builder.WriteString(fn(text[i : i+1]))
}
return builder.String()
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Joel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -86,3 +86,10 @@ bool getJSONInt(JsonNode *item, const char* key, int *result) {
return false;
}
JsonNode* mustParseJSON(const char* JSON) {
JsonNode* parsedUpdate = json_decode(JSON);
if ( parsedUpdate == NULL ) {
ABORT("Unable to decode JSON: %s\n", JSON);
}
return parsedUpdate;
}

View File

@@ -5,10 +5,6 @@
#ifndef COMMON_H
#define COMMON_H
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
#include <objc/objc-runtime.h>
#include <CoreGraphics/CoreGraphics.h>
#include <stdio.h>
#include <stdarg.h>
#include "string.h"
@@ -35,4 +31,6 @@ JsonNode* mustJSONObject(JsonNode *node, const char* key);
bool getJSONBool(JsonNode *item, const char* key, bool *result);
bool getJSONInt(JsonNode *item, const char* key, int *result);
JsonNode* mustParseJSON(const char* JSON);
#endif //ASSETS_C_COMMON_H

View File

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

View File

@@ -1,11 +1,12 @@
package ffenestri
import (
"github.com/wailsapp/wails/v2/internal/menumanager"
"runtime"
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/pkg/options"
@@ -118,7 +119,8 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
fullscreen := a.bool2Cint(a.config.Fullscreen)
startHidden := a.bool2Cint(a.config.StartHidden)
logLevel := C.int(a.config.LogLevel)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel)
hideWindowOnClose := a.bool2Cint(a.config.HideWindowOnClose)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel, hideWindowOnClose)
// Save app reference
a.app = (*C.struct_Application)(app)

View File

@@ -4,7 +4,7 @@
#include <stdio.h>
struct Application;
extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel);
extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose);
extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight);
extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight);
extern void Run(struct Application*, int argc, char **argv);
@@ -36,7 +36,9 @@ extern void MessageDialog(struct Application*, char *callbackID, char *type, cha
extern void DarkModeEnabled(struct Application*, char *callbackID);
extern void SetApplicationMenu(struct Application*, const char *);
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
extern void UpdateTrayMenu(struct Application*, const char *menuTrayJSON);
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
extern void DeleteTrayMenuByID(struct Application*, const char *id);
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);

View File

@@ -14,8 +14,9 @@ import "C"
import (
"strconv"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
// Client is our implentation of messageDispatcher.Client
@@ -113,6 +114,14 @@ func (c *Client) WindowSize(width int, height int) {
C.SetSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMinSize(width int, height int) {
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMaxSize(width int, height int) {
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour int) {
r, g, b, a := intToColour(colour)
@@ -120,7 +129,7 @@ func (c *Client) WindowSetColour(colour int) {
}
// OpenDialog will open a dialog with the given title and filter
func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string) {
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
@@ -138,7 +147,7 @@ func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
C.SaveDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
@@ -152,7 +161,7 @@ func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string
}
// MessageDialog will open a message dialog with the given options
func (c *Client) MessageDialog(dialogOptions *options.MessageDialog, callbackID string) {
func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
// Sanity check button length
if len(dialogOptions.Buttons) > 4 {
@@ -188,10 +197,18 @@ func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
}
func (c *Client) UpdateTrayMenu(trayMenuJSON string) {
C.UpdateTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
func (c *Client) SetTrayMenu(trayMenuJSON string) {
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
}
func (c *Client) UpdateTrayMenuLabel(JSON string) {
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
}
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
}
func (c *Client) DeleteTrayMenuByID(id string) {
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ package ffenestri
/*
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -lobjc
#cgo darwin LDFLAGS: -framework WebKit -framework CoreFoundation -lobjc
#include "ffenestri.h"
#include "ffenestri_darwin.h"
@@ -48,6 +48,9 @@ func (a *Application) processPlatformSettings() error {
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
}
// Set activation policy
C.SetActivationPolicy(a.app, C.int(mac.ActivationPolicy))
// Check if the webview should be transparent
if mac.WebviewIsTransparent {
C.WebviewIsTransparent(a.app)
@@ -87,5 +90,10 @@ func (a *Application) processPlatformSettings() error {
}
}
// Process URL Handlers
if a.config.Mac.URLHandlers != nil {
C.HasURLHandlers(a.app)
}
return nil
}

View File

@@ -6,29 +6,56 @@
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
#include <objc/objc-runtime.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
#include "json.h"
#include "hashmap.h"
#include "stdlib.h"
typedef struct {
long maj;
long min;
long patch;
} OSVersion;
// Macros to make it slightly more sane
#define msg objc_msgSend
#define msg_reg ((id(*)(id, SEL))objc_msgSend)
#define msg_id ((id(*)(id, SEL, id))objc_msgSend)
#define msg_id_id ((id(*)(id, SEL, id, id))objc_msgSend)
#define msg_bool ((id(*)(id, SEL, BOOL))objc_msgSend)
#define msg_int ((id(*)(id, SEL, int))objc_msgSend)
#define msg_uint ((id(*)(id, SEL, unsigned int))objc_msgSend)
#define msg_float ((id(*)(id, SEL, float))objc_msgSend)
#define kInternetEventClass 'GURL'
#define kAEGetURL 'GURL'
#define keyDirectObject '----'
#define c(str) (id)objc_getClass(str)
#define s(str) sel_registerName(str)
#define u(str) sel_getUid(str)
#define str(input) msg(c("NSString"), s("stringWithUTF8String:"), input)
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
#define cstr(input) (const char *)msg(input, s("UTF8String"))
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
#define str(input) ((id(*)(id, SEL, const char *))objc_msgSend)(c("NSString"), s("stringWithUTF8String:"), input)
#define strunicode(input) ((id(*)(id, SEL, id, unsigned short))objc_msgSend)(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
#define cstr(input) (const char *)msg_reg(input, s("UTF8String"))
#define url(input) msg_id(c("NSURL"), s("fileURLWithPath:"), str(input))
#define ALLOC(classname) msg_reg(c(classname), s("alloc"))
#define ALLOC_INIT(classname) msg_reg(msg_reg(c(classname), s("alloc")), s("init"))
#define ALLOC(classname) msg(c(classname), s("alloc"))
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
#if defined (__aarch64__)
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("frame"))
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("bounds"))
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend)(processInfo, s("operatingSystemVersion"));
#endif
#if defined (__x86_64__)
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))msg)(receiver, s("backingScaleFactor"))
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend_stret)(processInfo, s("operatingSystemVersion"));
#endif
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))objc_msgSend)(receiver, s("backingScaleFactor"))
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
#define MAIN_WINDOW_CALL(str) msg_reg(app->mainWindow, s((str)))
#define NSBackingStoreBuffered 2
@@ -66,6 +93,10 @@
#define NSControlStateValueOff 0
#define NSControlStateValueOn 1
#define NSApplicationActivationPolicyRegular 0
#define NSApplicationActivationPolicyAccessory 1
#define NSApplicationActivationPolicyProhibited 2
// Unbelievably, if the user swaps their button preference
// then right buttons are reported as left buttons
#define NSEventMaskLeftMouseDown 1 << 1
@@ -94,6 +125,8 @@
#define NSAlertSecondButtonReturn 1001
#define NSAlertThirdButtonReturn 1002
#define BrokenImage "iVBORw0KGgoAAAANSUhEUgAAABAAAAASCAMAAABl5a5YAAABj1BMVEWopan///+koqSWk5P9/v3///////////+AgACMiovz8/PB0fG9z+3i4+WysbGBfX1Erh80rACLiYqBxolEsDhHlDEbqQDDx+CNho7W1tj4+/bw+O3P5Mn4/f/W1tbK6sX////b2dn////////////8/Pz6+vro6Ojj4+P////G1PL////EzNydmp2cmZnd3eDF1PHs8v/o8P/Q3vrS3vfE0vCdmpqZkpr19/3N2vXI1vPH1fOgnqDg6frP3PbCytvHx8irqq6HhIZtuGtjnlZetU1Xs0NWskBNsi7w9v/d6P7w9P3S4Pzr8Pvl7PrY5PrU4PjQ3fjD1Ozo6Om30NjGzNi7ubm34K+UxKmbnaWXlJeUjpSPi4tppF1TtjxSsTf2+f7L2PTr7e3H2+3V7+q+0uXg4OPg4eLR1uG7z+Hg4ODGzODV2N7V1trP5dmxzs65vcfFxMWq0cKxxr+/vr+0s7apxbWaxrCv2qao05+dlp2Uuo2Dn4F8vIB6xnyAoHmAym9zqGpctENLryNFsgoblJpnAAAAKnRSTlP+hP7+5ZRmYgL+/f39/f39/f38/Pz8/Pv69+7j083My8GocnBPTTMWEgjxeITOAAABEklEQVQY0y3KZXuCYBiG4ceYuu7u3nyVAaKOMBBQ7O5Yd3f3fvheDnd9u8/jBkGwNxP6sjOWVQvY/ftrzfT6bd3yEhCnYZqiaYoKiwX/gXkFiHySTcUTLJMsZ9v8nQvgssWYOEKedKpcOO6CUXD5IlGEY5hLUbyDAAZ6HRf1bnkoavOsFQibg+Q4nuNYL+ON5PHD5nBaraRVyxnzGf6BJzUi2QQCQgMyk8tleL7dg1owpJ17D5IkvV100EingeOopPyo6vfAuXF+9hbDTknZCIaUoeK4efKwG4iT6xDewd7imGlid7gGwv37b6Oh9jwaTdOf/Tc1qH7UZVmuP6G5qZfBr9cAGNy4KiDd4tXIs7tS+QO9aUKvPAIKuQAAAABJRU5ErkJggg=="
struct Application;
int releaseNSObject(void *const context, struct hashmap_element_s *const e);
void TitlebarAppearsTransparent(struct Application* app);
@@ -107,7 +140,15 @@ void SetAppearance(struct Application* app, const char *);
void WebviewIsTransparent(struct Application* app);
void WindowBackgroundIsTranslucent(struct Application* app);
void SetTray(struct Application* app, const char *, const char *, const char *);
void SetContextMenus(struct Application* app, const char *);
//void SetContextMenus(struct Application* app, const char *);
void AddTrayMenu(struct Application* app, const char *);
void SetActivationPolicy(struct Application* app, int policy);
void* lookupStringConstant(id constantName);
void HasURLHandlers(struct Application* app);
id createImageFromBase64Data(const char *data, bool isTemplateImage);
#endif

View File

@@ -0,0 +1,78 @@
typedef struct {
} Application;
struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
}
void SetMinWindowSize(struct Application* app, int minWidth, int minHeight) {
}
void SetMaxWindowSize(struct Application* app, int maxWidth, int maxHeight) {
}
void Run(struct Application* app, int argc, char **argv) {
}
void DestroyApplication(struct Application* app) {
}
void SetDebug(struct Application* app, int flag) {
}
void SetBindings(struct Application* app, const char *bindings) {
}
void ExecJS(struct Application* app, const char *script) {
}
void Hide(struct Application* app) {
}
void Show(struct Application* app) {
}
void Center(struct Application* app) {
}
void Maximise(struct Application* app) {
}
void Unmaximise(struct Application* app) {
}
void ToggleMaximise(struct Application* app) {
}
void Minimise(struct Application* app) {
}
void Unminimise(struct Application* app) {
}
void ToggleMinimise(struct Application* app) {
}
void SetColour(struct Application* app, int red, int green, int blue, int alpha) {
}
void SetSize(struct Application* app, int width, int height) {
}
void SetPosition(struct Application* app, int x, int y) {
}
void Quit(struct Application* app) {
}
void SetTitle(struct Application* app, const char *title) {
}
void Fullscreen(struct Application* app) {
}
void UnFullscreen(struct Application* app) {
}
void ToggleFullscreen(struct Application* app) {
}
void DisableFrame(struct Application* app) {
}
void OpenDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
}
void SaveDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
}
void MessageDialog(struct Application* app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
}
void DarkModeEnabled(struct Application* app, char *callbackID) {
}
void SetApplicationMenu(struct Application* app, const char *applicationMenuJSON) {
}
void AddTrayMenu(struct Application* app, const char *menuTrayJSON) {
}
void SetTrayMenu(struct Application* app, const char *menuTrayJSON) {
}
void DeleteTrayMenuByID(struct Application* app, const char *id) {
}
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
}
void AddContextMenu(struct Application* app, char *contextMenuJSON) {
}
void UpdateContextMenu(struct Application* app, char *contextMenuJSON) {
}

View File

@@ -0,0 +1,14 @@
package ffenestri
/*
#include "ffenestri.h"
#include "ffenestri_windows.h"
*/
import "C"
func (a *Application) processPlatformSettings() error {
return nil
}

View File

@@ -0,0 +1,5 @@
#ifndef _FFENESTRI_WINDOWS_
#define _FFENESTRI_WINDOWS_
#endif

View File

@@ -5,6 +5,7 @@
#include "ffenestri_darwin.h"
#include "menu_darwin.h"
#include "contextmenus_darwin.h"
#include "common.h"
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
Menu* NewMenu(JsonNode *menuData) {
@@ -63,8 +64,6 @@ MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const
return result;
}
void DeleteMenu(Menu *menu) {
// Free menu item hashmap
@@ -91,7 +90,7 @@ void DeleteMenu(Menu *menu) {
// Free nsmenu if we have it
if ( menu->menu != NULL ) {
msg(menu->menu, s("release"));
msg_reg(menu->menu, s("release"));
}
free(menu);
@@ -99,7 +98,11 @@ void DeleteMenu(Menu *menu) {
// Creates a JSON message for the given menuItemID and data
const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID) {
JsonNode *jsonObject = json_mkobject();
if (menuItemID == NULL ) {
ABORT("Item ID NULL for menu!!\n");
}
json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID));
json_append_member(jsonObject, "menuType", json_mkstring(MenuTypeAsString[(int)menuType]));
if (data != NULL) {
@@ -117,17 +120,17 @@ const char* createMenuClickedMessage(const char *menuItemID, const char *data, e
// Callback for text menu items
void menuItemCallback(id self, SEL cmd, id sender) {
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg_reg(msg_reg(sender, s("representedObject")), s("pointerValue"));
const char *message;
// Update checkbox / radio item
if( callbackData->menuItemType == Checkbox) {
// Toggle state
bool state = msg(callbackData->menuItem, s("state"));
msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
bool state = msg_reg(callbackData->menuItem, s("state"));
msg_int(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
} else if( callbackData->menuItemType == Radio ) {
// Check the menu items' current state
bool selected = msg(callbackData->menuItem, s("state"));
bool selected = (bool)msg_reg(callbackData->menuItem, s("state"));
// If it's already selected, exit early
if (selected) return;
@@ -139,13 +142,13 @@ void menuItemCallback(id self, SEL cmd, id sender) {
id thisMember = members[0];
int count = 0;
while(thisMember != NULL) {
msg(thisMember, s("setState:"), NSControlStateValueOff);
msg_int(thisMember, s("setState:"), NSControlStateValueOff);
count = count + 1;
thisMember = members[count];
}
// check the selected menu item
msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
msg_int(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
}
const char *menuID = callbackData->menuID;
@@ -177,151 +180,154 @@ id processAcceleratorKey(const char *key) {
return str("");
}
if( STREQ(key, "Backspace") ) {
if( STREQ(key, "backspace") ) {
return strunicode(0x0008);
}
if( STREQ(key, "Tab") ) {
if( STREQ(key, "tab") ) {
return strunicode(0x0009);
}
if( STREQ(key, "Return") ) {
if( STREQ(key, "return") ) {
return strunicode(0x000d);
}
if( STREQ(key, "Escape") ) {
if( STREQ(key, "enter") ) {
return strunicode(0x000d);
}
if( STREQ(key, "escape") ) {
return strunicode(0x001b);
}
if( STREQ(key, "Left") ) {
if( STREQ(key, "left") ) {
return strunicode(0x001c);
}
if( STREQ(key, "Right") ) {
if( STREQ(key, "right") ) {
return strunicode(0x001d);
}
if( STREQ(key, "Up") ) {
if( STREQ(key, "up") ) {
return strunicode(0x001e);
}
if( STREQ(key, "Down") ) {
if( STREQ(key, "down") ) {
return strunicode(0x001f);
}
if( STREQ(key, "Space") ) {
if( STREQ(key, "space") ) {
return strunicode(0x0020);
}
if( STREQ(key, "Delete") ) {
if( STREQ(key, "delete") ) {
return strunicode(0x007f);
}
if( STREQ(key, "Home") ) {
if( STREQ(key, "home") ) {
return strunicode(0x2196);
}
if( STREQ(key, "End") ) {
if( STREQ(key, "end") ) {
return strunicode(0x2198);
}
if( STREQ(key, "Page Up") ) {
if( STREQ(key, "page up") ) {
return strunicode(0x21de);
}
if( STREQ(key, "Page Down") ) {
if( STREQ(key, "page down") ) {
return strunicode(0x21df);
}
if( STREQ(key, "F1") ) {
if( STREQ(key, "f1") ) {
return strunicode(0xf704);
}
if( STREQ(key, "F2") ) {
if( STREQ(key, "f2") ) {
return strunicode(0xf705);
}
if( STREQ(key, "F3") ) {
if( STREQ(key, "f3") ) {
return strunicode(0xf706);
}
if( STREQ(key, "F4") ) {
if( STREQ(key, "f4") ) {
return strunicode(0xf707);
}
if( STREQ(key, "F5") ) {
if( STREQ(key, "f5") ) {
return strunicode(0xf708);
}
if( STREQ(key, "F6") ) {
if( STREQ(key, "f6") ) {
return strunicode(0xf709);
}
if( STREQ(key, "F7") ) {
if( STREQ(key, "f7") ) {
return strunicode(0xf70a);
}
if( STREQ(key, "F8") ) {
if( STREQ(key, "f8") ) {
return strunicode(0xf70b);
}
if( STREQ(key, "F9") ) {
if( STREQ(key, "f9") ) {
return strunicode(0xf70c);
}
if( STREQ(key, "F10") ) {
if( STREQ(key, "f10") ) {
return strunicode(0xf70d);
}
if( STREQ(key, "F11") ) {
if( STREQ(key, "f11") ) {
return strunicode(0xf70e);
}
if( STREQ(key, "F12") ) {
if( STREQ(key, "f12") ) {
return strunicode(0xf70f);
}
if( STREQ(key, "F13") ) {
if( STREQ(key, "f13") ) {
return strunicode(0xf710);
}
if( STREQ(key, "F14") ) {
if( STREQ(key, "f14") ) {
return strunicode(0xf711);
}
if( STREQ(key, "F15") ) {
if( STREQ(key, "f15") ) {
return strunicode(0xf712);
}
if( STREQ(key, "F16") ) {
if( STREQ(key, "f16") ) {
return strunicode(0xf713);
}
if( STREQ(key, "F17") ) {
if( STREQ(key, "f17") ) {
return strunicode(0xf714);
}
if( STREQ(key, "F18") ) {
if( STREQ(key, "f18") ) {
return strunicode(0xf715);
}
if( STREQ(key, "F19") ) {
if( STREQ(key, "f19") ) {
return strunicode(0xf716);
}
if( STREQ(key, "F20") ) {
if( STREQ(key, "f20") ) {
return strunicode(0xf717);
}
if( STREQ(key, "F21") ) {
if( STREQ(key, "f21") ) {
return strunicode(0xf718);
}
if( STREQ(key, "F22") ) {
if( STREQ(key, "f22") ) {
return strunicode(0xf719);
}
if( STREQ(key, "F23") ) {
if( STREQ(key, "f23") ) {
return strunicode(0xf71a);
}
if( STREQ(key, "F24") ) {
if( STREQ(key, "f24") ) {
return strunicode(0xf71b);
}
if( STREQ(key, "F25") ) {
if( STREQ(key, "f25") ) {
return strunicode(0xf71c);
}
if( STREQ(key, "F26") ) {
if( STREQ(key, "f26") ) {
return strunicode(0xf71d);
}
if( STREQ(key, "F27") ) {
if( STREQ(key, "f27") ) {
return strunicode(0xf71e);
}
if( STREQ(key, "F28") ) {
if( STREQ(key, "f28") ) {
return strunicode(0xf71f);
}
if( STREQ(key, "F29") ) {
if( STREQ(key, "f29") ) {
return strunicode(0xf720);
}
if( STREQ(key, "F30") ) {
if( STREQ(key, "f30") ) {
return strunicode(0xf721);
}
if( STREQ(key, "F31") ) {
if( STREQ(key, "f31") ) {
return strunicode(0xf722);
}
if( STREQ(key, "F32") ) {
if( STREQ(key, "f32") ) {
return strunicode(0xf723);
}
if( STREQ(key, "F33") ) {
if( STREQ(key, "f33") ) {
return strunicode(0xf724);
}
if( STREQ(key, "F34") ) {
if( STREQ(key, "f34") ) {
return strunicode(0xf725);
}
if( STREQ(key, "F35") ) {
if( STREQ(key, "f35") ) {
return strunicode(0xf726);
}
// if( STREQ(key, "Insert") ) {
@@ -333,7 +339,7 @@ id processAcceleratorKey(const char *key) {
// if( STREQ(key, "ScrollLock") ) {
// return strunicode(0xf72f);
// }
if( STREQ(key, "NumLock") ) {
if( STREQ(key, "numLock") ) {
return strunicode(0xf739);
}
@@ -342,61 +348,61 @@ id processAcceleratorKey(const char *key) {
void addSeparator(id menu) {
id item = msg(c("NSMenuItem"), s("separatorItem"));
msg(menu, s("addItem:"), item);
id item = msg_reg(c("NSMenuItem"), s("separatorItem"));
msg_id(menu, s("addItem:"), item);
}
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
id item = ALLOC("NSMenuItem");
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
return item;
}
id createMenuItem(id title, const char *action, const char *key) {
id item = ALLOC("NSMenuItem");
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
msg(item, s("autorelease"));
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
msg_reg(item, s("autorelease"));
return item;
}
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
id item = createMenuItem(str(title), action, key);
msg(item, s("setEnabled:"), !disabled);
msg(menu, s("addItem:"), item);
msg_bool(item, s("setEnabled:"), !disabled);
msg_id(menu, s("addItem:"), item);
return item;
}
id createMenu(id title) {
id menu = ALLOC("NSMenu");
msg(menu, s("initWithTitle:"), title);
msg(menu, s("setAutoenablesItems:"), NO);
msg_id(menu, s("initWithTitle:"), title);
msg_bool(menu, s("setAutoenablesItems:"), NO);
// msg(menu, s("autorelease"));
return menu;
}
void createDefaultAppMenu(id parentMenu) {
// App Menu
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
id appName = msg_reg(msg_reg(c("NSProcessInfo"), s("processInfo")), s("processName"));
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
id appMenu = createMenu(appName);
msg(appMenuItem, s("setSubmenu:"), appMenu);
msg(parentMenu, s("addItem:"), appMenuItem);
msg_id(appMenuItem, s("setSubmenu:"), appMenu);
msg_id(parentMenu, s("addItem:"), appMenuItem);
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
id title = msg_id(str("Hide "), s("stringByAppendingString:"), appName);
id item = createMenuItem(title, "hide:", "h");
msg(appMenu, s("addItem:"), item);
msg_id(appMenu, s("addItem:"), item);
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
addSeparator(appMenu);
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
title = msg_id(str("Quit "), s("stringByAppendingString:"), appName);
item = createMenuItem(title, "terminate:", "q");
msg(appMenu, s("addItem:"), item);
msg_id(appMenu, s("addItem:"), item);
}
void createDefaultEditMenu(id parentMenu) {
@@ -404,8 +410,8 @@ void createDefaultEditMenu(id parentMenu) {
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
id editMenu = createMenu(str("Edit"));
msg(editMenuItem, s("setSubmenu:"), editMenu);
msg(parentMenu, s("addItem:"), editMenuItem);
msg_id(editMenuItem, s("setSubmenu:"), editMenu);
msg_id(parentMenu, s("addItem:"), editMenuItem);
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
@@ -433,7 +439,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
}
if ( STREQ(roleName, "hideothers")) {
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
return;
}
if ( STREQ(roleName, "unhide")) {
@@ -470,7 +476,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
}
if( STREQ(roleName, "pasteandmatchstyle")) {
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
msg_int(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
}
if ( STREQ(roleName, "selectall")) {
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
@@ -505,20 +511,21 @@ unsigned long parseModifiers(const char **modifiers) {
const char *thisModifier = modifiers[0];
int count = 0;
while( thisModifier != NULL ) {
// Determine flags
if( STREQ(thisModifier, "CmdOrCtrl") ) {
if( STREQ(thisModifier, "cmdorctrl") ) {
result |= NSEventModifierFlagCommand;
}
if( STREQ(thisModifier, "OptionOrAlt") ) {
if( STREQ(thisModifier, "optionoralt") ) {
result |= NSEventModifierFlagOption;
}
if( STREQ(thisModifier, "Shift") ) {
if( STREQ(thisModifier, "shift") ) {
result |= NSEventModifierFlagShift;
}
if( STREQ(thisModifier, "Super") ) {
if( STREQ(thisModifier, "super") ) {
result |= NSEventModifierFlagCommand;
}
if( STREQ(thisModifier, "Control") ) {
if( STREQ(thisModifier, "ctrl") ) {
result |= NSEventModifierFlagControl;
}
count++;
@@ -536,18 +543,18 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
msg_id(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg_bool(item, s("setEnabled:"), !disabled);
msg_reg(item, s("autorelease"));
msg_int(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg(parentmenu, s("addItem:"), item);
msg_id(parentmenu, s("addItem:"), item);
return item;
}
@@ -562,38 +569,214 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg(parentmenu, s("addItem:"), item);
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
msg_id(item, s("setRepresentedObject:"), wrappedId);
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
msg_bool(item, s("setEnabled:"), !disabled);
msg_reg(item, s("autorelease"));
msg_int(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg_id(parentmenu, s("addItem:"), item);
return item;
}
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers) {
// getColour returns the colour from a styledLabel based on the key
const char* getColour(JsonNode *styledLabelEntry, const char* key) {
JsonNode* colEntry = getJSONObject(styledLabelEntry, key);
if( colEntry == NULL ) {
return NULL;
}
return getJSONString(colEntry, "hex");
}
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize) {
// Create result
id attributedString = ALLOC_INIT("NSMutableAttributedString");
msg_reg(attributedString, s("autorelease"));
// Create new Dictionary
id dictionary = ALLOC_INIT("NSMutableDictionary");
msg_reg(dictionary, s("autorelease"));
// Use default font
CGFloat fontSizeFloat = (CGFloat)fontSize;
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
// Check user supplied font
if( STR_HAS_CHARS(fontName) ) {
id fontNameAsNSString = str(fontName);
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
if( userFont != NULL ) {
font = userFont;
}
}
id fan = lookupStringConstant(str("NSFontAttributeName"));
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
id NSBackgroundColorAttributeName = lookupStringConstant(str("NSBackgroundColorAttributeName"));
// Loop over styled text creating NSAttributedText and appending to result
JsonNode *styledLabelEntry;
json_foreach(styledLabelEntry, styledLabel) {
// Clear dictionary
msg_reg(dictionary, s("removeAllObjects"));
// Add font to dictionary
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
// Get Text
const char* thisLabel = mustJSONString(styledLabelEntry, "Label");
// Get foreground colour
const char *hexColour = getColour(styledLabelEntry, "FgCol");
if( hexColour != NULL) {
unsigned short r, g, b, a;
// white by default
r = g = b = a = 255;
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
if (count > 0) {
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(CGFloat)r / (CGFloat)255.0,
(CGFloat)g / (CGFloat)255.0,
(CGFloat)b / (CGFloat)255.0,
(CGFloat)a / (CGFloat)255.0);
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
}
}
// Get background colour
hexColour = getColour(styledLabelEntry, "BgCol");
if( hexColour != NULL) {
unsigned short r, g, b, a;
// white by default
r = g = b = a = 255;
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
if (count > 0) {
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(CGFloat)r / (CGFloat)255.0,
(CGFloat)g / (CGFloat)255.0,
(CGFloat)b / (CGFloat)255.0,
(CGFloat)a / (CGFloat)255.0);
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
}
}
// Create AttributedText
id thisString = ALLOC("NSMutableAttributedString");
msg_reg(thisString, s("autorelease"));
msg_id_id(thisString, s("initWithString:attributes:"), str(thisLabel), dictionary);
// Append text to result
msg_id(attributedString, s("appendAttributedString:"), thisString);
}
return attributedString;
}
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
// Create new Dictionary
id dictionary = ALLOC_INIT("NSMutableDictionary");
CGFloat fontSizeFloat = (CGFloat)fontSize;
// Use default font
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
// Check user supplied font
if( STR_HAS_CHARS(fontName) ) {
id fontNameAsNSString = str(fontName);
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
if( userFont != NULL ) {
font = userFont;
}
}
// Add font to dictionary
id fan = lookupStringConstant(str("NSFontAttributeName"));
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
// RGBA
if( RGBA != NULL && strlen(RGBA) > 0) {
unsigned short r, g, b, a;
// white by default
r = g = b = a = 255;
int count = sscanf(RGBA, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
if (count > 0) {
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(CGFloat)r / (CGFloat)255.0,
(CGFloat)g / (CGFloat)255.0,
(CGFloat)b / (CGFloat)255.0,
(CGFloat)a / (CGFloat)255.0);
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
}
}
id attributedString = ALLOC("NSMutableAttributedString");
msg_id_id(attributedString, s("initWithString:attributes:"), str(title), dictionary);
msg_reg(attributedString, s("autorelease"));
msg_reg(dictionary, s("autorelease"));
return attributedString;
}
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate, JsonNode* styledLabel) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
msg_id(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
if( !alternate ) {
id key = processAcceleratorKey(acceleratorkey);
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
} else {
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
}
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
if( tooltip != NULL ) {
msg_id(item, s("setToolTip:"), str(tooltip));
}
// Process image
if( image != NULL && strlen(image) > 0) {
id nsimage = createImageFromBase64Data(image, templateImage);
msg_id(item, s("setImage:"), nsimage);
}
id attributedString = NULL;
if( styledLabel != NULL) {
attributedString = createAttributedStringFromStyledLabel(styledLabel, fontName, fontSize);
} else {
attributedString = createAttributedString(title, fontName, fontSize, RGBA);
}
msg_id(item, s("setAttributedTitle:"), attributedString);
//msg_id(item, s("setTitle:"), str(title));
msg_bool(item, s("setEnabled:"), !disabled);
msg_reg(item, s("autorelease"));
// Process modifiers
if( modifiers != NULL ) {
if( modifiers != NULL && !alternate) {
unsigned long modifierFlags = parseModifiers(modifiers);
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
((id(*)(id, SEL, unsigned long))objc_msgSend)(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
}
msg(parentMenu, s("addItem:"), item);
// alternate
if( alternate ) {
msg_bool(item, s("setAlternate:"), true);
msg_int(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
}
msg_id(parentMenu, s("addItem:"), item);
return item;
}
@@ -614,38 +797,6 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
return;
}
// Check if this is a submenu
JsonNode *submenu = json_find_member(item, "SubMenu");
if( submenu != NULL ) {
// Get the label
JsonNode *menuNameNode = json_find_member(item, "Label");
const char *name = "";
if ( menuNameNode != NULL) {
name = menuNameNode->string_;
}
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
id thisMenu = createMenu(str(name));
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
msg(parentMenu, s("addItem:"), thisMenuItem);
JsonNode *submenuItems = json_find_member(submenu, "Items");
// If we have no items, just return
if ( submenuItems == NULL ) {
return;
}
// Loop over submenu items
JsonNode *item;
json_foreach(item, submenuItems) {
// Get item label
processMenuItem(menu, thisMenu, item);
}
return;
}
// This is a user menu. Get the common data
// Get the label
const char *label = getJSONString(item, "Label");
@@ -653,6 +804,13 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
label = "(empty)";
}
// Check for a styled label
JsonNode *styledLabel = getJSONObject(item, "StyledLabel");
// Is this an alternate menu item?
bool alternate = false;
getJSONBool(item, "MacAlternate", &alternate);
const char *menuid = getJSONString(item, "ID");
if ( menuid == NULL) {
menuid = "";
@@ -666,6 +824,16 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
const char *acceleratorkey = NULL;
const char **modifiers = NULL;
const char *tooltip = getJSONString(item, "Tooltip");
const char *image = getJSONString(item, "Image");
const char *fontName = getJSONString(item, "FontName");
const char *RGBA = getJSONString(item, "RGBA");
bool templateImage = false;
getJSONBool(item, "MacTemplateImage", &templateImage);
int fontSize = 0;
getJSONInt(item, "FontSize", &fontSize);
// If we have an accelerator
if( accelerator != NULL ) {
// Get the key
@@ -696,9 +864,36 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
// Get the Type
JsonNode *type = json_find_member(item, "Type");
if( type != NULL ) {
if( STREQ(type->string_, "Text") || STREQ(type->string_, "Submenu")) {
id thisMenuItem = processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate, styledLabel);
if( STREQ(type->string_, "Text")) {
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers);
// Check if this node has a submenu
JsonNode *submenu = json_find_member(item, "SubMenu");
if( submenu != NULL ) {
// Get the label
JsonNode *menuNameNode = json_find_member(item, "Label");
const char *name = "";
if ( menuNameNode != NULL) {
name = menuNameNode->string_;
}
id thisMenu = createMenu(str(name));
msg_id(thisMenuItem, s("setSubmenu:"), thisMenu);
JsonNode *submenuItems = json_find_member(submenu, "Items");
// If we have no items, just return
if ( submenuItems == NULL ) {
return;
}
// Loop over submenu items
JsonNode *item;
json_foreach(item, submenuItems) {
// Get item label
processMenuItem(menu, thisMenu, item);
}
}
}
else if ( STREQ(type->string_, "Separator")) {
addSeparator(parentMenu);
@@ -717,7 +912,6 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
}
}
if ( modifiers != NULL ) {

View File

@@ -14,6 +14,21 @@ static const char *MenuTypeAsString[] = {
"ApplicationMenu", "ContextMenu", "TrayMenu",
};
typedef struct _NSRange {
unsigned long location;
unsigned long length;
} NSRange;
#define NSFontWeightUltraLight -0.8
#define NSFontWeightThin -0.6
#define NSFontWeightLight -0.4
#define NSFontWeightRegular 0.0
#define NSFontWeightMedium 0.23
#define NSFontWeightSemibold 0.3
#define NSFontWeightBold 0.4
#define NSFontWeightHeavy 0.56
#define NSFontWeightBlack 0.62
extern void messageFromWindowCallback(const char *);
typedef struct {
@@ -90,11 +105,13 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate, JsonNode* styledLabel);
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
void processMenuData(Menu *menu, JsonNode *menuData);
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup);
id GetMenu(Menu *menu);
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA);
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize);
#endif //ASSETS_C_MENU_DARWIN_H

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,8 @@
#include "traymenu_darwin.h"
#include "trayicons.h"
extern Class trayMenuDelegateClass;
// A cache for all our tray menu icons
// Global because it's a singleton
struct hashmap_s trayIconCache;
@@ -29,12 +31,25 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
result->ID = mustJSONString(processedJSON, "ID");
result->label = mustJSONString(processedJSON, "Label");
result->icon = mustJSONString(processedJSON, "Icon");
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
result->icon = mustJSONString(processedJSON, "Image");
result->fontName = getJSONString(processedJSON, "FontName");
result->RGBA = getJSONString(processedJSON, "RGBA");
getJSONBool(processedJSON, "MacTemplateImage", &result->templateImage);
result->fontSize = 0;
getJSONInt(processedJSON, "FontSize", &result->fontSize);
result->tooltip = NULL;
result->tooltip = getJSONString(processedJSON, "Tooltip");
result->disabled = false;
getJSONBool(processedJSON, "Disabled", &result->disabled);
result->styledLabel = getJSONObject(processedJSON, "StyledLabel");
// Create the menu
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
result->menu = NewMenu(processedMenu);
result->delegate = NULL;
// Init tray status bar item
result->statusbaritem = NULL;
@@ -49,60 +64,89 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
printf(" ['%s':%p] = { label: '%s', icon: '%s', menu: %p, statusbar: %p }\n", trayMenu->ID, trayMenu, trayMenu->label, trayMenu->icon, trayMenu->menu, trayMenu->statusbaritem );
}
void ShowTrayMenu(TrayMenu* trayMenu) {
// Create a status bar item if we don't have one
if( trayMenu->statusbaritem == NULL ) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg(trayMenu->statusbaritem, s("retain"));
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
// Update the icon if needed
UpdateTrayMenuIcon(trayMenu);
// Update the label if needed
UpdateTrayMenuLabel(trayMenu);
// Update the menu
id menu = GetMenu(trayMenu->menu);
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
}
void UpdateTrayMenuLabel(TrayMenu *trayMenu) {
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled, JsonNode *styledLabel) {
// Exit early if NULL
if( trayMenu->label == NULL ) {
return;
}
// We don't check for a
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setTitle:"), str(trayMenu->label));
// Update button label
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
id attributedString = NULL;
if( styledLabel != NULL) {
attributedString = createAttributedStringFromStyledLabel(styledLabel, fontName, fontSize);
} else {
attributedString = createAttributedString(label, fontName, fontSize, RGBA);
}
if( tooltip != NULL ) {
msg_id(statusBarButton, s("setToolTip:"), str(tooltip));
}
msg_bool(statusBarButton, s("setEnabled:"), !disabled);
msg_id(statusBarButton, s("setAttributedTitle:"), attributedString);
}
void UpdateTrayMenuIcon(TrayMenu *trayMenu) {
void UpdateTrayIcon(TrayMenu *trayMenu) {
// Exit early if NULL
if( trayMenu->icon == NULL ) {
return;
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
// Empty icon means remove it
if( STREMPTY(trayMenu->icon) ) {
// Remove image
msg(statusBarButton, s("setImage:"), NULL);
msg_id(statusBarButton, s("setImage:"), NULL);
return;
}
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
msg(statusBarButton, s("setImage:"), trayImage);
// If we don't have the image in the icon cache then assume it's base64 encoded image data
if (trayImage == NULL) {
trayImage = createImageFromBase64Data(trayMenu->icon, trayMenu->templateImage);
}
msg_int(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
msg_id(statusBarButton, s("setImage:"), trayImage);
}
void ShowTrayMenu(TrayMenu* trayMenu) {
// Create a status bar item if we don't have one
if( trayMenu->statusbaritem == NULL ) {
id statusBar = msg_reg( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = ((id(*)(id, SEL, CGFloat))objc_msgSend)(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg_reg(trayMenu->statusbaritem, s("retain"));
}
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
msg_uint(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
// Update the icon if needed
UpdateTrayIcon(trayMenu);
// Update the label if needed
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled, trayMenu->styledLabel);
// Update the menu
id menu = GetMenu(trayMenu->menu);
objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN);
// Create delegate
id trayMenuDelegate = msg_reg((id)trayMenuDelegateClass, s("new"));
msg_id(menu, s("setDelegate:"), trayMenuDelegate);
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
// Create menu delegate
trayMenu->delegate = trayMenuDelegate;
msg_id(trayMenu->statusbaritem, s("setMenu:"), menu);
}
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
@@ -124,6 +168,7 @@ void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
// Copy the other data
currentMenu->ID = newMenu->ID;
currentMenu->label = newMenu->label;
currentMenu->styledLabel = newMenu->styledLabel;
currentMenu->trayIconPosition = newMenu->trayIconPosition;
currentMenu->icon = newMenu->icon;
@@ -144,12 +189,16 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
// Free the status item
if ( trayMenu->statusbaritem != NULL ) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
msg(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
msg(trayMenu->statusbaritem, s("release"));
id statusBar = msg_reg( c("NSStatusBar"), s("systemStatusBar") );
msg_id(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
msg_reg(trayMenu->statusbaritem, s("release"));
trayMenu->statusbaritem = NULL;
}
if ( trayMenu->delegate != NULL ) {
msg_reg(trayMenu->delegate, s("release"));
}
// Free the tray menu memory
MEMFREE(trayMenu);
}
@@ -179,9 +228,9 @@ void LoadTrayIcons() {
int length = atoi((const char *)lengthAsString);
// Create the icon and add to the hashmap
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
id imageData = ((id(*)(id, SEL, id, int))objc_msgSend)(c("NSData"), s("dataWithBytes:length:"), (id)data, length);
id trayImage = ALLOC("NSImage");
msg(trayImage, s("initWithData:"), imageData);
msg_id(trayImage, s("initWithData:"), imageData);
hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage);
}
}

View File

@@ -13,22 +13,34 @@ typedef struct {
const char *label;
const char *icon;
const char *ID;
const char *tooltip;
bool templateImage;
const char *fontName;
int fontSize;
const char *RGBA;
bool disabled;
Menu* menu;
id statusbaritem;
int trayIconPosition;
unsigned int trayIconPosition;
JsonNode* processedJSON;
JsonNode* styledLabel;
id delegate;
} TrayMenu;
TrayMenu* NewTrayMenu(const char *trayJSON);
void DumpTrayMenu(TrayMenu* trayMenu);
void ShowTrayMenu(TrayMenu* trayMenu);
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
void UpdateTrayMenuIcon(TrayMenu *trayMenu);
void UpdateTrayMenuLabel(TrayMenu *trayMenu);
void UpdateTrayIcon(TrayMenu *trayMenu);
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled, JsonNode *styledLabel);
void LoadTrayIcons();
void UnloadTrayIcons();

View File

@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
}
if (pthread_mutex_init(&result->lock, NULL) != 0) {
printf("\n mutex init has failed\n");
exit(1);
}
return result;
}
@@ -25,15 +30,19 @@ int dumpTrayMenu(void *const context, struct hashmap_element_s *const e) {
}
void DumpTrayMenuStore(TrayMenuStore* store) {
pthread_mutex_lock(&store->lock);
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
pthread_mutex_unlock(&store->lock);
}
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON);
pthread_mutex_lock(&store->lock);
//TODO: check if there is already an entry for this menu
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
pthread_mutex_unlock(&store->lock);
}
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
@@ -43,12 +52,13 @@ int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
}
void ShowTrayMenusInStore(TrayMenuStore* store) {
pthread_mutex_lock(&store->lock);
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
}
pthread_mutex_unlock(&store->lock);
}
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
DeleteTrayMenu(e->data);
return -1;
@@ -65,43 +75,103 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
// Destroy tray menu map
hashmap_destroy(&store->trayMenuMap);
pthread_mutex_destroy(&store->lock);
}
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
// Get the current menu
return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
pthread_mutex_lock(&store->lock);
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
pthread_mutex_unlock(&store->lock);
return result;
}
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
// Get the current menu
pthread_mutex_lock(&store->lock);
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
pthread_mutex_unlock(&store->lock);
if (result == NULL ) {
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
}
return result;
}
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* ID) {
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
pthread_mutex_lock(&store->lock);
hashmap_remove(&store->trayMenuMap, ID, strlen(ID));
pthread_mutex_unlock(&store->lock);
DeleteTrayMenu(menu);
}
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
// Parse the JSON
JsonNode *parsedUpdate = mustParseJSON(JSON);
// Get the data out
const char* ID = mustJSONString(parsedUpdate, "ID");
const char* Label = mustJSONString(parsedUpdate, "Label");
// Check we have this menu
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
const char *fontName = getJSONString(parsedUpdate, "FontName");
const char *RGBA = getJSONString(parsedUpdate, "RGBA");
int fontSize = 0;
getJSONInt(parsedUpdate, "FontSize", &fontSize);
const char *tooltip = getJSONString(parsedUpdate, "Tooltip");
bool disabled = false;
getJSONBool(parsedUpdate, "Disabled", &disabled);
JsonNode *styledLabel = getJSONObject(parsedUpdate, "StyledLabel");
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled, styledLabel);
}
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON);
// DumpTrayMenu(newMenu);
// Get the current menu
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
// If we don't have a menu, we create one
if ( currentMenu == NULL ) {
ABORT("Attempted to update unknown tray menu with ID '%s'.", newMenu->ID);
// Store the new menu
pthread_mutex_lock(&store->lock);
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
pthread_mutex_unlock(&store->lock);
// Show it
ShowTrayMenu(newMenu);
return;
}
// DumpTrayMenu(currentMenu);
// Save the status bar reference
newMenu->statusbaritem = currentMenu->statusbaritem;
pthread_mutex_lock(&store->lock);
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
pthread_mutex_unlock(&store->lock);
// Delete the current menu
DeleteMenu(currentMenu->menu);
currentMenu->menu = NULL;
// Free JSON
if (currentMenu->processedJSON != NULL ) {
json_delete(currentMenu->processedJSON);
currentMenu->processedJSON = NULL;
}
// Free the tray menu memory
MEMFREE(currentMenu);
pthread_mutex_lock(&store->lock);
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
pthread_mutex_unlock(&store->lock);
// Show the updated menu
ShowTrayMenu(newMenu);
}

View File

@@ -5,6 +5,10 @@
#ifndef TRAYMENUSTORE_DARWIN_H
#define TRAYMENUSTORE_DARWIN_H
#include "traymenu_darwin.h"
#include <pthread.h>
typedef struct {
int dummy;
@@ -13,6 +17,8 @@ typedef struct {
// It maps tray IDs to TrayMenu*
struct hashmap_s trayMenuMap;
pthread_mutex_t lock;
} TrayMenuStore;
TrayMenuStore* NewTrayMenuStore();
@@ -22,4 +28,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
void ShowTrayMenusInStore(TrayMenuStore* store);
void DeleteTrayMenuStore(TrayMenuStore* store);
TrayMenu* GetTrayMenuByID(TrayMenuStore* store, const char* menuID);
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id);
#endif //TRAYMENUSTORE_DARWIN_H

View File

@@ -40,7 +40,7 @@ func Mkdir(dirname string) error {
// Returns error on failure
func MkDirs(fullPath string, mode ...os.FileMode) error {
var perms os.FileMode
perms = 0700
perms = 0755
if len(mode) == 1 {
perms = mode[0]
}
@@ -243,7 +243,7 @@ func CopyDir(src string, dst string) (err error) {
return fmt.Errorf("destination already exists")
}
err = os.MkdirAll(dst, si.Mode())
err = MkDirs(dst)
if err != nil {
return
}

View File

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

View File

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

View File

@@ -112,6 +112,9 @@ func (a *AssetBundle) processHTML(htmldata string) error {
if attr.Key == "as" && attr.Val == "script" {
asset.Type = AssetTypes.JS
}
if attr.Key == "rel" && attr.Val == "modulepreload" {
asset.Type = AssetTypes.JS
}
}
// Ensure we don't include duplicates

View File

@@ -26,6 +26,9 @@ func NewMenuItemMap() *MenuItemMap {
}
func (m *MenuItemMap) AddMenu(menu *menu.Menu) {
if menu == nil {
return
}
for _, item := range menu.Items {
m.processMenuItem(item)
}

View File

@@ -2,6 +2,10 @@ package menumanager
import (
"encoding/json"
"strings"
"github.com/leaanthony/go-ansi-parser"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
)
@@ -9,44 +13,74 @@ import (
type ProcessedMenuItem struct {
ID string
// Label is what appears as the menu text
Label string
Label string `json:",omitempty"`
// Role is a predefined menu type
Role menu.Role `json:"Role,omitempty"`
Role menu.Role `json:",omitempty"`
// Accelerator holds a representation of a key binding
Accelerator *keys.Accelerator `json:"Accelerator,omitempty"`
Accelerator *keys.Accelerator `json:",omitempty"`
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
Type menu.Type
// Disabled makes the item unselectable
Disabled bool
Disabled bool `json:",omitempty"`
// Hidden ensures that the item is not shown in the menu
Hidden bool
Hidden bool `json:",omitempty"`
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
Checked bool
Checked bool `json:",omitempty"`
// Submenu contains a list of menu items that will be shown as a submenu
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
SubMenu *ProcessedMenu `json:"SubMenu,omitempty"`
SubMenu *ProcessedMenu `json:",omitempty"`
// Foreground colour in hex RGBA format EG: 0xFF0000FF = #FF0000FF = red
Foreground int
// Colour
RGBA string `json:",omitempty"`
// Background colour
Background int
// Font
FontSize int `json:",omitempty"`
FontName string `json:",omitempty"`
// Image - base64 image data
Image string `json:",omitempty"`
MacTemplateImage bool `json:", omitempty"`
MacAlternate bool `json:", omitempty"`
// Tooltip
Tooltip string `json:",omitempty"`
// Styled label
StyledLabel []*ansi.StyledText `json:",omitempty"`
}
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
ID := menuItemMap.menuItemToIDMap[menuItem]
// Parse ANSI text
var styledLabel []*ansi.StyledText
tempLabel := menuItem.Label
if strings.Contains(tempLabel, "\033[") {
parsedLabel, err := ansi.Parse(menuItem.Label)
if err == nil {
styledLabel = parsedLabel
}
}
result := &ProcessedMenuItem{
ID: ID,
Label: menuItem.Label,
Role: menuItem.Role,
Accelerator: menuItem.Accelerator,
Type: menuItem.Type,
Disabled: menuItem.Disabled,
Hidden: menuItem.Hidden,
Checked: menuItem.Checked,
Foreground: menuItem.Foreground,
Background: menuItem.Background,
ID: ID,
Label: menuItem.Label,
Role: menuItem.Role,
Accelerator: menuItem.Accelerator,
Type: menuItem.Type,
Disabled: menuItem.Disabled,
Hidden: menuItem.Hidden,
Checked: menuItem.Checked,
SubMenu: nil,
RGBA: menuItem.RGBA,
FontSize: menuItem.FontSize,
FontName: menuItem.FontName,
Image: menuItem.Image,
MacTemplateImage: menuItem.MacTemplateImage,
MacAlternate: menuItem.MacAlternate,
Tooltip: menuItem.Tooltip,
StyledLabel: styledLabel,
}
if menuItem.SubMenu != nil {
@@ -63,9 +97,11 @@ type ProcessedMenu struct {
func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu {
result := &ProcessedMenu{}
for _, item := range menu.Items {
processedMenuItem := NewProcessedMenuItem(menuItemMap, item)
result.Items = append(result.Items, processedMenuItem)
if menu != nil {
for _, item := range menu.Items {
processedMenuItem := NewProcessedMenuItem(menuItemMap, item)
result.Items = append(result.Items, processedMenuItem)
}
}
return result

View File

@@ -3,28 +3,43 @@ package menumanager
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/menu"
"strconv"
"strings"
"sync"
"github.com/leaanthony/go-ansi-parser"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/menu"
)
var trayMenuID int
var trayMenuIDMutex sync.Mutex
func generateTrayID() string {
var idStr string
trayMenuIDMutex.Lock()
result := fmt.Sprintf("%d", trayMenuID)
idStr = strconv.Itoa(trayMenuID)
trayMenuID++
trayMenuIDMutex.Unlock()
return result
return idStr
}
type TrayMenu struct {
ID string
Label string
Icon string
menuItemMap *MenuItemMap
menu *menu.Menu
ProcessedMenu *WailsMenu
ID string
Label string
FontSize int
FontName string
Disabled bool
Tooltip string `json:",omitempty"`
Image string
MacTemplateImage bool
RGBA string
menuItemMap *MenuItemMap
menu *menu.Menu
ProcessedMenu *WailsMenu
trayMenu *menu.TrayMenu
StyledLabel []*ansi.StyledText `json:",omitempty"`
}
func (t *TrayMenu) AsJSON() (string, error) {
@@ -37,11 +52,29 @@ func (t *TrayMenu) AsJSON() (string, error) {
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
// Parse ANSI text
var styledLabel []*ansi.StyledText
tempLabel := trayMenu.Label
if strings.Contains(tempLabel, "\033[") {
parsedLabel, err := ansi.Parse(tempLabel)
if err == nil {
styledLabel = parsedLabel
}
}
result := &TrayMenu{
Label: trayMenu.Label,
Icon: trayMenu.Icon,
menu: trayMenu.Menu,
menuItemMap: NewMenuItemMap(),
Label: trayMenu.Label,
FontName: trayMenu.FontName,
FontSize: trayMenu.FontSize,
Disabled: trayMenu.Disabled,
Tooltip: trayMenu.Tooltip,
Image: trayMenu.Image,
MacTemplateImage: trayMenu.MacTemplateImage,
menu: trayMenu.Menu,
RGBA: trayMenu.RGBA,
menuItemMap: NewMenuItemMap(),
trayMenu: trayMenu,
StyledLabel: styledLabel,
}
result.menuItemMap.AddMenu(trayMenu.Menu)
@@ -50,7 +83,29 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
return result
}
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) {
func (m *Manager) OnTrayMenuOpen(id string) {
trayMenu, ok := m.trayMenus[id]
if !ok {
return
}
if trayMenu.trayMenu.OnOpen == nil {
return
}
go trayMenu.trayMenu.OnOpen()
}
func (m *Manager) OnTrayMenuClose(id string) {
trayMenu, ok := m.trayMenus[id]
if !ok {
return
}
if trayMenu.trayMenu.OnClose == nil {
return
}
go trayMenu.trayMenu.OnClose()
}
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
newTrayMenu := NewTrayMenu(trayMenu)
// Hook up a new ID
@@ -60,12 +115,23 @@ func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) {
// Save the references
m.trayMenus[trayID] = newTrayMenu
m.trayMenuPointers[trayMenu] = trayID
return newTrayMenu.AsJSON()
}
func (m *Manager) UpdateTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
func (m *Manager) GetTrayID(trayMenu *menu.TrayMenu) (string, error) {
trayID, exists := m.trayMenuPointers[trayMenu]
if !exists {
return "", fmt.Errorf("Unable to find menu ID for tray menu!")
}
return trayID, nil
}
// SetTrayMenu updates or creates a menu
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
if !trayMenuKnown {
return "", fmt.Errorf("unknown Tray Menu '%s'. Please add the tray menu using AddTrayMenu()", trayMenu.Label)
return m.AddTrayMenu(trayMenu)
}
// Create the updated tray menu
@@ -91,6 +157,57 @@ func (m *Manager) GetTrayMenus() ([]string, error) {
return result, nil
}
func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
if !trayMenuKnown {
return "", fmt.Errorf("[UpdateTrayMenuLabel] unknown tray id for tray %s", trayMenu.Label)
}
type LabelUpdate struct {
ID string
Label string `json:",omitempty"`
FontName string `json:",omitempty"`
FontSize int
RGBA string `json:",omitempty"`
Disabled bool
Tooltip string `json:",omitempty"`
Image string `json:",omitempty"`
MacTemplateImage bool
StyledLabel []*ansi.StyledText `json:",omitempty"`
}
// Parse ANSI text
var styledLabel []*ansi.StyledText
tempLabel := trayMenu.Label
if strings.Contains(tempLabel, "\033[") {
parsedLabel, err := ansi.Parse(tempLabel)
if err == nil {
styledLabel = parsedLabel
}
}
update := &LabelUpdate{
ID: trayID,
Label: trayMenu.Label,
FontName: trayMenu.FontName,
FontSize: trayMenu.FontSize,
Disabled: trayMenu.Disabled,
Tooltip: trayMenu.Tooltip,
Image: trayMenu.Image,
MacTemplateImage: trayMenu.MacTemplateImage,
RGBA: trayMenu.RGBA,
StyledLabel: styledLabel,
}
data, err := json.Marshal(update)
if err != nil {
return "", errors.Wrap(err, "[UpdateTrayMenuLabel] ")
}
return string(data), nil
}
func (m *Manager) GetContextMenus() ([]string, error) {
result := []string{}
for _, contextMenu := range m.contextMenus {

View File

@@ -2,10 +2,11 @@ package messagedispatcher
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
)
// Client defines what a frontend client can do
@@ -13,9 +14,9 @@ type Client interface {
Quit()
NotifyEvent(message string)
CallResult(message string)
OpenDialog(dialogOptions *options.OpenDialog, callbackID string)
SaveDialog(dialogOptions *options.SaveDialog, callbackID string)
MessageDialog(dialogOptions *options.MessageDialog, callbackID string)
OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string)
SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string)
MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string)
WindowSetTitle(title string)
WindowShow()
WindowHide()
@@ -26,13 +27,17 @@ type Client interface {
WindowUnminimise()
WindowPosition(x int, y int)
WindowSize(width int, height int)
WindowSetMinSize(width int, height int)
WindowSetMaxSize(width int, height int)
WindowFullscreen()
WindowUnFullscreen()
WindowSetColour(colour int)
DarkModeEnabled(callbackID string)
SetApplicationMenu(menuJSON string)
UpdateTrayMenu(trayMenuJSON string)
SetTrayMenu(trayMenuJSON string)
UpdateTrayMenuLabel(JSON string)
UpdateContextMenu(contextMenuJSON string)
DeleteTrayMenuByID(id string)
}
// DispatchClient is what the frontends use to interface with the

View File

@@ -27,7 +27,7 @@ func dialogMessageParser(message string) (*parsedMessage, error) {
if idx < 0 {
return nil, fmt.Errorf("Invalid dialog response message format: %+v", message)
}
callbackID := message[:idx+1]
callbackID := message[:idx]
payloadData := message[idx+1:]
switch dialogType {

View File

@@ -32,6 +32,14 @@ func menuMessageParser(message string) (*parsedMessage, error) {
callbackid := message[2:]
topic = "menu:clicked"
data = callbackid
case 'o':
callbackid := message[2:]
topic = "menu:ontrayopen"
data = callbackid
case 'c':
callbackid := message[2:]
topic = "menu:ontrayclose"
data = callbackid
default:
return nil, fmt.Errorf("invalid menu message: %s", message)
}

View File

@@ -21,13 +21,14 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
'M': menuMessageParser,
'T': trayMessageParser,
'X': contextMenusMessageParser,
'U': urlMessageParser,
}
// Parse will attempt to parse the given message
func Parse(message string) (*parsedMessage, error) {
if len(message) == 0 {
return nil, fmt.Errorf("MessageParser received blank message");
return nil, fmt.Errorf("MessageParser received blank message")
}
parseMethod := messageParsers[message[0]]

View File

@@ -8,8 +8,8 @@ import (
// systemMessageParser does what it says on the tin!
func systemMessageParser(message string) (*parsedMessage, error) {
// Sanity check: system messages must be at least 4 bytes
if len(message) < 4 {
// Sanity check: system messages must be at least 2 bytes
if len(message) < 2 {
return nil, fmt.Errorf("system message was an invalid length")
}
@@ -23,6 +23,9 @@ func systemMessageParser(message string) (*parsedMessage, error) {
// Format of system response messages: S<command><callbackID>|<payload>
// DarkModeEnabled
case 'D':
if len(message) < 4 {
return nil, fmt.Errorf("system message was an invalid length")
}
message = message[1:]
idx := strings.IndexByte(message, '|')
if idx < 0 {
@@ -34,6 +37,11 @@ func systemMessageParser(message string) (*parsedMessage, error) {
topic := "systemresponse:" + callbackID
responseMessage = &parsedMessage{Topic: topic, Data: payloadData == "T"}
// This is our startup hook - the frontend is now ready
case 'S':
topic := "hooks:startup"
startupURL := message[1:]
responseMessage = &parsedMessage{Topic: topic, Data: startupURL}
default:
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
}

View File

@@ -0,0 +1,20 @@
package message
import "fmt"
// urlMessageParser does what it says on the tin!
func urlMessageParser(message string) (*parsedMessage, error) {
// Sanity check: URL messages must be at least 2 bytes
if len(message) < 2 {
return nil, fmt.Errorf("log message was an invalid length")
}
// Switch on the log type
switch message[1] {
case 'C':
return &parsedMessage{Topic: "url:handler", Data: message[2:]}, nil
default:
return nil, fmt.Errorf("url message type '%c' invalid", message[1])
}
}

View File

@@ -1,16 +1,18 @@
package messagedispatcher
import (
"context"
"encoding/json"
"strconv"
"strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/options"
)
// Dispatcher translates messages received from the frontend
@@ -23,7 +25,6 @@ type Dispatcher struct {
dialogChannel <-chan *servicebus.Message
systemChannel <-chan *servicebus.Message
menuChannel <-chan *servicebus.Message
running bool
servicebus *servicebus.ServiceBus
logger logger.CustomLogger
@@ -31,6 +32,13 @@ type Dispatcher struct {
// Clients
clients map[string]*DispatchClient
lock sync.RWMutex
// Context for cancellation
ctx context.Context
cancel context.CancelFunc
// internal wait group
wg sync.WaitGroup
}
// New dispatcher. Needs a service bus to send to.
@@ -75,6 +83,9 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
return nil, err
}
// Create context
ctx, cancel := context.WithCancel(context.Background())
result := &Dispatcher{
servicebus: servicebus,
eventChannel: eventChannel,
@@ -86,6 +97,8 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
dialogChannel: dialogChannel,
systemChannel: systemChannel,
menuChannel: menuChannel,
ctx: ctx,
cancel: cancel,
}
return result, nil
@@ -96,15 +109,18 @@ func (d *Dispatcher) Start() error {
d.logger.Trace("Starting")
d.running = true
d.wg.Add(1)
// Spin off a go routine
go func() {
for d.running {
defer d.logger.Trace("Shutdown")
for {
select {
case <-d.ctx.Done():
d.wg.Done()
return
case <-d.quitChannel:
d.processQuit()
d.running = false
case resultMessage := <-d.resultChannel:
d.processCallResult(resultMessage)
case eventMessage := <-d.eventChannel:
@@ -119,9 +135,6 @@ func (d *Dispatcher) Start() error {
d.processMenuMessage(menuMessage)
}
}
// Call shutdown
d.shutdown()
}()
return nil
@@ -135,10 +148,6 @@ func (d *Dispatcher) processQuit() {
}
}
func (d *Dispatcher) shutdown() {
d.logger.Trace("Shutdown")
}
// RegisterClient will register the given callback with the dispatcher
// and return a DispatchClient that the caller can use to send messages
func (d *Dispatcher) RegisterClient(client Client) *DispatchClient {
@@ -349,6 +358,38 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
for _, client := range d.clients {
client.frontend.WindowSize(w, h)
}
case "minsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:minsize' : %#v", result.Data())
return
}
w, err1 := strconv.Atoi(splitTopic[2])
h, err2 := strconv.Atoi(splitTopic[3])
if err1 != nil || err2 != nil {
d.logger.Error("Invalid integer parameters for 'window:minsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMinSize(w, h)
}
case "maxsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:maxsize' : %#v", result.Data())
return
}
w, err1 := strconv.Atoi(splitTopic[2])
h, err2 := strconv.Atoi(splitTopic[3])
if err1 != nil || err2 != nil {
d.logger.Error("Invalid integer parameters for 'window:maxsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMaxSize(w, h)
}
default:
d.logger.Error("Unknown window command: %s", command)
}
@@ -370,7 +411,7 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
dialogType := splitTopic[2]
switch dialogType {
case "open":
dialogOptions, ok := result.Data().(*options.OpenDialog)
dialogOptions, ok := result.Data().(*dialog.OpenDialog)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:open' : %#v", result.Data())
return
@@ -384,7 +425,7 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
client.frontend.OpenDialog(dialogOptions, callbackID)
}
case "save":
dialogOptions, ok := result.Data().(*options.SaveDialog)
dialogOptions, ok := result.Data().(*dialog.SaveDialog)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:save' : %#v", result.Data())
return
@@ -398,7 +439,7 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
client.frontend.SaveDialog(dialogOptions, callbackID)
}
case "message":
dialogOptions, ok := result.Data().(*options.MessageDialog)
dialogOptions, ok := result.Data().(*dialog.MessageDialog)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:message' : %#v", result.Data())
return
@@ -445,10 +486,10 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
client.frontend.SetApplicationMenu(updatedMenu)
}
case "updatetraymenu":
updatedTrayMenu, ok := result.Data().(string)
case "settraymenu":
trayMenuJSON, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:updatetraymenu' : %#v",
d.logger.Error("Invalid data for 'menufrontend:settraymenu' : %#v",
result.Data())
return
}
@@ -456,8 +497,9 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
for _, client := range d.clients {
client.frontend.UpdateTrayMenu(updatedTrayMenu)
client.frontend.SetTrayMenu(trayMenuJSON)
}
case "updatecontextmenu":
updatedContextMenu, ok := result.Data().(string)
if !ok {
@@ -472,7 +514,37 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
client.frontend.UpdateContextMenu(updatedContextMenu)
}
case "updatetraymenulabel":
updatedTrayMenuLabel, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
result.Data())
return
}
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
for _, client := range d.clients {
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
}
case "deletetraymenu":
traymenuid, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
result.Data())
return
}
for _, client := range d.clients {
client.frontend.DeleteTrayMenuByID(traymenuid)
}
default:
d.logger.Error("Unknown menufrontend command: %s", command)
}
}
func (d *Dispatcher) Close() {
d.cancel()
d.wg.Wait()
}

View File

@@ -10,8 +10,6 @@ import (
"golang.org/x/tools/go/packages"
)
var internalMethods = slicer.String([]string{"WailsInit", "Wails Shutdown"})
var structCache = make(map[string]*ParsedStruct)
var boundStructs = make(map[string]*ParsedStruct)
var boundMethods = []string{}
@@ -49,7 +47,7 @@ func ParseProject(projectPath string) (BoundStructs, error) {
cfg := &packages.Config{Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedTypesInfo}
pkgs, err := packages.Load(cfg, projectPath)
if err != nil {
fmt.Fprintf(os.Stderr, "load: %v\n", err)
_, _ = fmt.Fprintf(os.Stderr, "load: %v\n", err)
os.Exit(1)
}
if packages.PrintErrors(pkgs) > 0 {
@@ -203,10 +201,6 @@ func ParseProject(projectPath string) (BoundStructs, error) {
// This is a struct pointer method
i, ok := se.X.(*ast.Ident)
if ok {
// We want to ignore Internal functions
if internalMethods.Contains(x.Name.Name) {
continue
}
// If we haven't already found this struct,
// Create a placeholder in the cache
parsedStruct := structCache[i.Name]
@@ -437,4 +431,6 @@ func ParseProject(projectPath string) (BoundStructs, error) {
println()
println("}")
println()
return nil, nil
}

View File

@@ -1,6 +1,7 @@
package process
import (
"os"
"os/exec"
"github.com/wailsapp/wails/v2/pkg/clilogger"
@@ -16,11 +17,14 @@ type Process struct {
// NewProcess creates a new process struct
func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process {
return &Process{
result := &Process{
logger: logger,
cmd: exec.Command(cmd, args...),
exitChannel: make(chan bool, 1),
}
result.cmd.Stdout = os.Stdout
result.cmd.Stderr = os.Stderr
return result
}
// Start the process
@@ -35,7 +39,12 @@ func (p *Process) Start() error {
go func(cmd *exec.Cmd, running *bool, logger *clilogger.CLILogger, exitChannel chan bool) {
logger.Println("Starting process (PID: %d)", cmd.Process.Pid)
cmd.Wait()
err := cmd.Wait()
if err != nil {
if err.Error() != "signal: killed" {
logger.Fatal("Fatal error from app: " + err.Error())
}
}
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
*running = false
exitChannel <- true

File diff suppressed because one or more lines are too long

View File

@@ -5,14 +5,14 @@ import (
"github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/options"
dialogoptions "github.com/wailsapp/wails/v2/pkg/options/dialog"
)
// Dialog defines all Dialog related operations
type Dialog interface {
Open(dialogOptions *options.OpenDialog) []string
Save(dialogOptions *options.SaveDialog) string
Message(dialogOptions *options.MessageDialog) string
Open(dialogOptions *dialogoptions.OpenDialog) []string
Save(dialogOptions *dialogoptions.SaveDialog) string
Message(dialogOptions *dialogoptions.MessageDialog) string
}
// dialog exposes the Dialog interface
@@ -45,7 +45,7 @@ func (r *dialog) processTitleAndFilter(params ...string) (string, string) {
}
// Open prompts the user to select a file
func (r *dialog) Open(dialogOptions *options.OpenDialog) []string {
func (r *dialog) Open(dialogOptions *dialogoptions.OpenDialog) []string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
@@ -70,7 +70,7 @@ func (r *dialog) Open(dialogOptions *options.OpenDialog) []string {
}
// Save prompts the user to select a file
func (r *dialog) Save(dialogOptions *options.SaveDialog) string {
func (r *dialog) Save(dialogOptions *dialogoptions.SaveDialog) string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
@@ -95,7 +95,7 @@ func (r *dialog) Save(dialogOptions *options.SaveDialog) string {
}
// Message show a message to the user
func (r *dialog) Message(dialogOptions *options.MessageDialog) string {
func (r *dialog) Message(dialogOptions *dialogoptions.MessageDialog) string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()

View File

@@ -65,7 +65,7 @@ export function SetBindings(bindingsMap) {
window.backend[packageName][structName][methodName] = function () {
// No timeout by default
var timeout = 0;
let timeout = 0;
// Actual function
function dynamic() {
@@ -89,19 +89,3 @@ export function SetBindings(bindingsMap) {
});
});
}
// /**
// * Determines if the given identifier is valid Javascript
// *
// * @param {boolean} name
// * @returns
// */
// function isValidIdentifier(name) {
// // Don't xss yourself :-)
// try {
// new Function('var ' + name);
// return true;
// } catch (e) {
// return false;
// }
// }

View File

@@ -10,6 +10,7 @@ The lightweight framework for web-like apps
/* jshint esversion: 6 */
import { SetBindings } from './bindings';
import { Init } from './main';
import {RaiseError} from '../desktop/darwin';
// Setup global error handler
window.onerror = function (msg, url, lineNo, columnNo, error) {
@@ -21,7 +22,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
error: JSON.stringify(error),
stack: function() { return JSON.stringify(new Error().stack); }(),
};
window.wails.Log.Error(JSON.stringify(errorMessage));
RaiseError(errorMessage);
};
// Initialise the Runtime

View File

@@ -13,6 +13,7 @@ import { Error } from './log';
import { SendMessage } from 'ipc';
// Defines a single listener with a maximum number of times to callback
/**
* The Listener class defines a listener! :-)
*
@@ -43,7 +44,7 @@ class Listener {
}
}
var eventListeners = {};
let eventListeners = {};
/**
* Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
@@ -96,7 +97,7 @@ export function Once(eventName, callback) {
function notifyListeners(eventData) {
// Get the event name
var eventName = eventData.name;
let eventName = eventData.name;
// Check if we have any listeners for this event
if (eventListeners[eventName]) {
@@ -110,7 +111,7 @@ function notifyListeners(eventData) {
// Get next listener
const listener = eventListeners[eventName][count];
var data = eventData.data;
let data = eventData.data;
// Do the callback
const destroy = listener.Callback(data);
@@ -120,7 +121,7 @@ function notifyListeners(eventData) {
}
}
// Update callbacks with new list of listners
// Update callbacks with new list of listeners
eventListeners[eventName] = newEventListenerList;
}
}

View File

@@ -62,6 +62,4 @@ export function Init() {
// Do platform specific Init
Platform.Init();
window.wailsloader.runtime = true;
}

View File

@@ -25,6 +25,10 @@ export function SendMessage(message) {
window.webkit.messageHandlers.external.postMessage(message);
}
export function RaiseError(message) {
window.webkit.messageHandlers.error.postMessage(message);
}
export function Init() {
// Setup drag handler

View File

@@ -27,7 +27,7 @@ export function Init() {
// Setup drag handler
// Based on code from: https://github.com/patr0nus/DeskGap
window.addEventListener('mousedown', function (e) {
var currentElement = e.target;
let currentElement = e.target;
while (currentElement != null) {
if (currentElement.hasAttribute('data-wails-no-drag')) {
break;

View File

@@ -1 +1 @@
bridge.js
src

File diff suppressed because it is too large Load Diff

View File

@@ -9,13 +9,25 @@ The lightweight framework for web-like apps
*/
/* jshint esversion: 6 */
import bridge from './bridge';
/**
* Initialises the Wails runtime
* ready will execute the callback when Wails has loaded
* and initialised.
*
* @param {function} callback
*/
function Init(callback) {
window.wails._.Init(callback);
function ready(callback) {
// If window.wails exists, we are ready
if( window.wails ) {
return callback();
}
// If not we need to setup the bridge
bridge.InitBridge(callback);
}
module.exports = Init;
module.exports = {
ready: ready,
};

View File

@@ -23,7 +23,7 @@ module.exports = {
Browser: Browser,
Dialog: Dialog,
Events: Events,
Init: Init,
ready: Init.ready,
Log: Log,
System: System,
Store: Store,

View File

@@ -1,6 +1,6 @@
{
"name": "@wails/runtime",
"version": "1.2.13",
"version": "1.2.24",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@wails/runtime",
"version": "1.2.22",
"version": "1.3.12",
"description": "Wails V2 Javascript runtime library",
"main": "main.js",
"types": "runtime.d.ts",

View File

@@ -0,0 +1,56 @@
<script>
export let menu;
export let hidden = true;
</script>
{#if !hidden}
<div class="menu">
{#if menu.Menu }
{#each menu.Menu.Items as menuItem}
<div class="menuitem">
{#if menuItem.Image }
<div><img alt="" src="data:image/png;base64,{menuItem.Image}"/></div>
{/if}
<div class="menulabel">{menuItem.Label}</div>
</div>
{/each}
{/if}
</div>
{/if}
<style>
.menu {
padding: 3px;
background-color: #0008;
color: #EEF;
border-radius: 5px;
margin-top: 5px;
position: absolute;
backdrop-filter: blur(3px) saturate(160%) contrast(45%) brightness(140%);
border: 1px solid rgb(88,88,88);
box-shadow: 0 0 1px rgb(146,146,148) inset;
}
.menuitem {
display: flex;
align-items: center;
padding: 1px 5px;
}
.menuitem:hover {
display: flex;
align-items: center;
background-color: rgb(57,131,223);
padding: 1px 5px;
border-radius: 5px;
}
.menuitem img {
padding-right: 5px;
}
</style>

View File

@@ -0,0 +1,72 @@
<script>
import {menuVisible} from './store'
import {fade} from 'svelte/transition';
import {trays} from './store'
import TrayMenu from "./TrayMenu.svelte";
import {onMount} from "svelte";
let time = new Date();
$: day = time.toLocaleString("default", { weekday: "short" })
$: dom = time.getDate()
$: mon = time.toLocaleString("default", { month: "short" })
$: currentTime = time.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }).toLowerCase()
$: dateTimeString = `${day} ${dom} ${mon} ${currentTime}`
onMount(() => {
const interval = setInterval(() => {
time = new Date();
}, 1000);
return () => {
clearInterval(interval);
};
});
function handleKeydown(e) {
// Backtick toggle
if( e.keyCode == 192 ) {
menuVisible.update( (current) => {
return !current;
});
}
}
</script>
{#if $menuVisible }
<div class="wails-menubar" transition:fade>
<span class="tray-menus">
{#each $trays as tray}
<TrayMenu {tray}/>
{/each}
<span class="time">{dateTimeString}</span>
</span>
</div>
{/if}
<svelte:window on:keydown={handleKeydown}/>
<style>
.tray-menus {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.wails-menubar { position: relative;
display: block;
top: 0;
height: 2rem;
width: 100%;
border-bottom: 1px solid #b3b3b3;
box-shadow: 0 0 10px 0 #33333360;
}
.time {
padding-left: 0.5rem;
padding-right: 1.5rem;
overflow: visible;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,55 @@
<script>
import { overlayVisible } from './store'
import { fade, } from 'svelte/transition';
</script>
{#if $overlayVisible }
<div class="wails-reconnect-overlay" transition:fade="{{ duration: 200 }}">
<div class="wails-reconnect-overlay-content">
<div class="wails-reconnect-overlay-loadingspinner"></div>
</div>
</div>
{/if}
<style>
.wails-reconnect-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
backdrop-filter: blur(20px) saturate(160%) contrast(45%) brightness(140%);
z-index: 999999
}
.wails-reconnect-overlay-content {
position: relative;
top: 50%;
transform: translateY(-50%);
margin: 0;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAflBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAAAAABAQEEBAQAAAAAAAAEBAQAAAADAwMAAAABAQEAAAAAAAAAAAAAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWCC3waAAAAKXRSTlMALgUMIBk0+xEqJs70Xhb3lu3EjX2EZTlv5eHXvbarQj3cdmpXSqOeUDwaqNAAAAKCSURBVEjHjZTntqsgEIUPVVCwtxg1vfD+L3hHRe8K6snZf+KKn8OewvzsSSeXLruLnz+KHs0gr6DkT3xsRkU6VVn4Ha/UxLe1Z4y64i847sykPBh/AvQ7ry3eFN70oKrfcBJYvm/tQ1qxP4T3emXPeXAkvodPUvtdjbhk+Ft4c0hslTiXVOzxOJ15NWUblQhRsdu3E1AfCjj3Gdm18zSOsiH8Lk4TB480ksy62fiqNo4OpyU8O21l6+hyRtS6z8r1pHlmle5sR1/WXS6Mq2Nl+YeKt3vr+vdH/q4O68tzXuwkiZmngYb4R8Co1jh0+Ww2UTyWxBvtyxLO7QVjO3YOD/lWZpbXDGellFG2Mws58mMnjVZSn7p+XvZ6IF4nn02OJZV0aTO22arp/DgLPtrgpVoi6TPbZm4XQBjY159w02uO0BDdYsfrOEi0M2ulRXlCIPAOuN1NOVhi+riBR3dgwQplYsZRZJLXq23Mlo5njkbY0rZFu3oiNIYG2kqsbVz67OlNuZZIOlfxHDl0UpyRX86z/OYC/3qf1A1xTrMp/PWWM4ePzf8DDp1nesQRpcFk7BlwdzN08ZIALJpCaciQXO0f6k4dnuT/Ewg4l7qSTNzm2SykdHn6GJ12mWc6aCNj/g1cTXpB8YFfr0uVc96aFkkqiIiX4nO+salKwGtIkvfB+Ja8DxMeD3hIXP5mTOYPB4eVT0+32I5ykvPZjesnkGgIREgYnmLrPb0PdV3hoLup2TjcGBPM4mgsfF5BrawZR4/GpzYQzQfrUZCf0TCWYo2DqhdhTJBQ6j4xqmmLN5LjdRIY8LWExiFUsSrza/nmFBqw3I9tEZB9h0lIQSO9if8DkISDAj8CDawAAAAASUVORK5CYII=);
background-repeat: no-repeat;
background-position: center
}
.wails-reconnect-overlay-loadingspinner {
pointer-events: none;
width: 2.5em;
height: 2.5em;
border: .4em solid transparent;
border-color: #f00 #eee0 #f00 #eee0;
border-radius: 50%;
animation: loadingspin 1s linear infinite;
margin: auto;
padding: 2.5em
}
@keyframes loadingspin {
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -0,0 +1,65 @@
<script>
import Menu from "./Menu.svelte";
import { selectedMenu } from "./store";
export let tray = null;
$: hidden = $selectedMenu !== tray;
function closeMenu() {
selectedMenu.set(null);
}
function trayClicked() {
if ( $selectedMenu !== tray ) {
selectedMenu.set(tray);
} else {
selectedMenu.set(null);
}
}
// Source: https://svelte.dev/repl/0ace7a508bd843b798ae599940a91783?version=3.16.7
/** Dispatch event on click outside of node */
function clickOutside(node) {
const handleClick = event => {
if (node && !node.contains(event.target) && !event.defaultPrevented) {
node.dispatchEvent(
new CustomEvent('click_outside', node)
)
}
}
document.addEventListener('click', handleClick, true);
return {
destroy() {
document.removeEventListener('click', handleClick, true);
}
}
}
</script>
<span class="tray-menu" use:clickOutside on:click_outside={closeMenu}>
<!--{#if tray.Image && tray.Image.length > 0}-->
<!-- <img alt="" src="data:image/png;base64,{tray.Image}"/>-->
<!--{/if}-->
<span class="label" on:click={trayClicked}>{tray.Label}</span>
{#if tray.ProcessedMenu }
<Menu menu="{tray.ProcessedMenu}" {hidden}/>
{/if}
</span>
<style>
.tray-menu {
padding-left: 0.5rem;
padding-right: 0.5rem;
overflow: visible;
font-size: 14px;
}
.label {
text-align: right;
padding-right: 10px;
}
</style>

View File

@@ -0,0 +1,9 @@
export function log(message) {
// eslint-disable-next-line
console.log(
'%c wails bridge %c ' + message + ' ',
'background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem',
'background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem'
);
}

View File

@@ -0,0 +1,45 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
import Overlay from './Overlay.svelte';
import MenuBar from './Menubar.svelte';
import {showOverlay} from "./store";
import {StartWebsocket} from "./websocket";
let components = {};
function setupMenuBar() {
components.menubar = new MenuBar({
target: document.body,
});
}
// Sets up the overlay
function setupOverlay() {
components.overlay = new Overlay({
target: document.body,
anchor: document.querySelector('#wails-bridge'),
});
}
export function InitBridge(callback) {
setupMenuBar()
// Setup the overlay
setupOverlay();
// Start by showing the overlay...
showOverlay();
// ...and attempt to connect
StartWebsocket(callback);
}

View File

@@ -0,0 +1,525 @@
{
"name": "bridge",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
"integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
"dev": true,
"requires": {
"@babel/highlight": "^7.12.13"
}
},
"@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
"dev": true
},
"@babel/highlight": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz",
"integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.12.11",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@rollup/plugin-commonjs": {
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz",
"integrity": "sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
"commondir": "^1.0.1",
"estree-walker": "^2.0.1",
"glob": "^7.1.6",
"is-reference": "^1.2.1",
"magic-string": "^0.25.7",
"resolve": "^1.17.0"
},
"dependencies": {
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
}
}
},
"@rollup/plugin-node-resolve": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.1.1.tgz",
"integrity": "sha512-zlBXR4eRS+2m79TsUZWhsd0slrHUYdRx4JF+aVQm+MI0wsKdlpC2vlDVjmlGvtZY1vsefOT9w3JxvmWSBei+Lg==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
"@types/resolve": "1.17.1",
"builtin-modules": "^3.1.0",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0",
"resolve": "^1.19.0"
}
},
"@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"dev": true,
"requires": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
}
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"@types/node": {
"version": "14.14.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz",
"integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==",
"dev": true
},
"@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
"integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"builtin-modules": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
"integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"is-core-module": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
"integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
},
"is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
"dev": true
},
"is-reference": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
"dev": true,
"requires": {
"@types/estree": "*"
}
},
"jest-worker": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
"integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
"dev": true,
"requires": {
"@types/node": "*",
"merge-stream": "^2.0.0",
"supports-color": "^7.0.0"
},
"dependencies": {
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"magic-string": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
"dev": true,
"requires": {
"sourcemap-codec": "^1.4.4"
}
},
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
"dev": true
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"requires": {
"safe-buffer": "^5.1.0"
}
},
"require-relative": {
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
"dev": true
},
"resolve": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
"integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
"dev": true,
"requires": {
"is-core-module": "^2.1.0",
"path-parse": "^1.0.6"
}
},
"rollup": {
"version": "2.38.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.38.5.tgz",
"integrity": "sha512-VoWt8DysFGDVRGWuHTqZzT02J0ASgjVq/hPs9QcBOGMd7B+jfTr/iqMVEyOi901rE3xq+Deq66GzIT1yt7sGwQ==",
"dev": true,
"requires": {
"fsevents": "~2.3.1"
}
},
"rollup-plugin-svelte": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-7.1.0.tgz",
"integrity": "sha512-vopCUq3G+25sKjwF5VilIbiY6KCuMNHP1PFvx2Vr3REBNMDllKHFZN2B9jwwC+MqNc3UPKkjXnceLPEjTjXGXg==",
"dev": true,
"requires": {
"require-relative": "^0.8.7",
"rollup-pluginutils": "^2.8.2"
}
},
"rollup-plugin-terser": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
"integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
"jest-worker": "^26.2.1",
"serialize-javascript": "^4.0.0",
"terser": "^5.0.0"
}
},
"rollup-pluginutils": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
"dev": true,
"requires": {
"estree-walker": "^0.6.1"
},
"dependencies": {
"estree-walker": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
"dev": true
}
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
},
"serialize-javascript": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
}
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
},
"source-map-support": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"svelte": {
"version": "3.32.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.32.2.tgz",
"integrity": "sha512-Zxh1MQQl/+vnToKbU1Per+PoMN8Jb2MeKJcGxiOsCGR677hXw7jkMfbnNXq33+dxIzV/HfA4xtoSPJrqeB0VUg==",
"dev": true
},
"terser": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz",
"integrity": "sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.19"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
}
}

View File

@@ -0,0 +1,21 @@
{
"name": "bridge",
"version": "1.0.0",
"description": "Wails bridge",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "npx rollup -c",
"watch": "npx rollup -c -w"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.1.1",
"rollup": "^2.38.5",
"rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-terser": "^7.0.2",
"svelte": "^3.32.2"
}
}

View File

@@ -0,0 +1,37 @@
import resolve from '@rollup/plugin-node-resolve';
// import commonjs from '@rollup/plugin-commonjs';
import svelte from 'rollup-plugin-svelte';
import { terser } from "rollup-plugin-terser";
export default [
// browser-friendly UMD build
{
input: 'main.js',
output: {
name: 'bridge',
file: '../bridge.js',
format: 'umd',
exports: "named"
},
plugins: [
svelte({
// Optionally, preprocess components with svelte.preprocess:
// https://svelte.dev/docs#svelte_preprocess
// preprocess: {
// style: ({content}) => {
// return transformStyles(content);
// }
// },
// Emit CSS as "files" for other plugins to process. default is true
emitCss: false,
}),
resolve({browser: true}), // so Rollup can find `ms`
// commonjs() // so Rollup can convert `ms` to an ES module
// terser(),
]
},
];

View File

@@ -0,0 +1,64 @@
import { writable } from 'svelte/store';
import {log} from "./log";
/** Overlay */
export const overlayVisible = writable(false);
export function showOverlay() {
overlayVisible.set(true);
}
export function hideOverlay() {
overlayVisible.set(false);
}
/** Menubar **/
export const menuVisible = writable(false);
export function showMenuBar() {
menuVisible.set(true);
}
export function hideMenuBar() {
menuVisible.set(false);
}
/** Trays **/
export const trays = writable([]);
export function setTray(tray) {
trays.update((current) => {
// Remove existing if it exists, else add
const index = current.findIndex(item => item.ID === tray.ID);
if ( index === -1 ) {
current.push(tray);
} else {
current[index] = tray;
}
return current;
})
}
export function updateTrayLabel(tray) {
trays.update((current) => {
// Remove existing if it exists, else add
const index = current.findIndex(item => item.ID === tray.ID);
if ( index === -1 ) {
return log("ERROR: Attempted to update tray index ", tray.ID, "but it doesn't exist")
}
current[index].Label = tray.Label;
return current;
})
}
export function deleteTrayMenu(id) {
trays.update((current) => {
// Remove existing if it exists, else add
const index = current.findIndex(item => item.ID === id);
if ( index === -1 ) {
return log("ERROR: Attempted to delete tray index ", id, "but it doesn't exist")
}
current.splice(index, 1);
return current;
})
}
export let selectedMenu = writable(null);

View File

@@ -0,0 +1,170 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
import {setTray, hideOverlay, showOverlay, updateTrayLabel, deleteTrayMenu} from "./store";
import {log} from "./log";
let websocket = null;
let callback = null;
let connectTimer;
export function StartWebsocket(userCallback) {
callback = userCallback;
window.onbeforeunload = function() {
if( websocket ) {
websocket.onclose = function () { };
websocket.close();
websocket = null;
}
}
// ...and attempt to connect
connect();
}
function setupIPCBridge() {
// darwin
window.webkit = {
messageHandlers: {
external: {
postMessage: (message) => {
websocket.send(message);
}
},
windowDrag: {
postMessage: () => {
// Ignore window drag events
}
}
}
};
}
// Handles incoming websocket connections
function handleConnect() {
log('Connected to backend');
setupIPCBridge();
hideOverlay();
clearInterval(connectTimer);
websocket.onclose = handleDisconnect;
websocket.onmessage = handleMessage;
}
// Handles websocket disconnects
function handleDisconnect() {
log('Disconnected from backend');
websocket = null;
showOverlay();
connect();
}
// Try to connect to the backend every 1s (default value).
function connect() {
connectTimer = setInterval(function () {
if (websocket == null) {
websocket = new WebSocket('ws://' + window.location.hostname + ':34115/bridge');
websocket.onopen = handleConnect;
websocket.onerror = function (e) {
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
websocket = null;
return false;
};
}
}, 1000);
}
// Adds a script to the Dom.
// Removes it if second parameter is true.
function addScript(script, remove) {
const s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.textContent = script;
document.head.appendChild(s);
// Remove internal messages from the DOM
if (remove) {
s.parentNode.removeChild(s);
}
}
function handleMessage(message) {
// As a bridge we ignore js and css injections
switch (message.data[0]) {
// Wails library - inject!
case 'b':
message = message.data.slice(1)
addScript(message);
log('Loaded Wails Runtime');
// We need to now send a message to the backend telling it
// we have loaded (System Start)
window.webkit.messageHandlers.external.postMessage("SS");
// Now wails runtime is loaded, wails for the ready event
// and callback to the main app
// window.wails.Events.On('wails:loaded', function () {
if (callback) {
log('Notifying application');
callback(window.wails);
}
// });
break;
// // Notifications
// case 'n':
// addScript(message.data.slice(1), true);
// break;
// // Binding
// case 'b':
// const binding = message.data.slice(1);
// //log("Binding: " + binding)
// window.wails._.NewBinding(binding);
// break;
// // Call back
case 'c':
const callbackData = message.data.slice(1);
window.wails._.Callback(callbackData);
break;
// Tray
case 'T':
const trayMessage = message.data.slice(1);
switch (trayMessage[0]) {
case 'S':
// Set tray
const trayJSON = trayMessage.slice(1);
let tray = JSON.parse(trayJSON)
setTray(tray)
break
case 'U':
// Update label
const updateTrayLabelJSON = trayMessage.slice(1);
let trayLabelData = JSON.parse(updateTrayLabelJSON)
updateTrayLabel(trayLabelData)
break
case 'D':
// Delete Tray Menu
const id = trayMessage.slice(1);
deleteTrayMenu(id)
break
default:
log('Unknown tray message: ' + message.data);
}
break;
default:
log('Unknown message: ' + message.data);
}
}

View File

@@ -13,7 +13,7 @@ const Events = require('./events');
/**
* Registers an event listener that will be invoked when the user changes the
* desktop theme (light mode / dark mode). The callback receives a boolean which
* desktop theme (light mode / dark mode). The callback receives a booleanean which
* indicates if dark mode is enabled.
*
* @export
@@ -43,12 +43,12 @@ function DarkModeEnabled() {
* Mac Title Bar Config
* Check out https://github.com/lukakerr/NSWindowStyles for some examples of these settings
* @typedef {Object} MacTitleBar
* @param {bool} TitleBarAppearsTransparent - NSWindow.titleBarAppearsTransparent
* @param {bool} HideTitle - NSWindow.hideTitle
* @param {bool} HideTitleBar - NSWindow.hideTitleBar
* @param {bool} FullSizeContent - Makes the webview portion of the window the full size of the window, even over the titlebar
* @param {bool} UseToolbar - Set true to add a blank toolbar to the window (makes the title bar larger)
* @param {bool} HideToolbarSeparator - Set true to remove the separator between the toolbar and the main content area
* @param {boolean} TitleBarAppearsTransparent - NSWindow.titleBarAppearsTransparent
* @param {boolean} HideTitle - NSWindow.hideTitle
* @param {boolean} HideTitleBar - NSWindow.hideTitleBar
* @param {boolean} FullSizeContent - Makes the webview portion of the window the full size of the window, even over the titlebar
* @param {boolean} UseToolbar - Set true to add a blank toolbar to the window (makes the title bar larger)
* @param {boolean} HideToolbarSeparator - Set true to remove the separator between the toolbar and the main content area
*
*/
@@ -65,8 +65,8 @@ function DarkModeEnabled() {
* @param {number} MinHeight - Window Minimum Height
* @param {number} MaxWidth - Window Maximum Width
* @param {number} MaxHeight - Window Maximum Height
* @param {bool} StartHidden - Start with window hidden
* @param {bool} DevTools - Enables the window devtools
* @param {boolean} StartHidden - Start with window hidden
* @param {boolean} DevTools - Enables the window devtools
* @param {number} RBGA - The initial window colour. Convert to hex then it'll mean 0xRRGGBBAA
* @param {MacAppConfig} [Mac] - Configuration when running on Mac
* @param {LinuxAppConfig} [Linux] - Configuration when running on Linux
@@ -88,11 +88,23 @@ function AppConfig() {
return window.wails.System.AppConfig.get();
}
function LogLevel() {
return window.wails.System.LogLevel();
}
function Platform() {
return window.wails.System.Platform();
}
function AppType() {
return window.wails.System.AppType();
}
module.exports = {
OnThemeChange: OnThemeChange,
DarkModeEnabled: DarkModeEnabled,
LogLevel: window.wails.System.LogLevel,
Platform: window.wails.System.Platform,
AppType: window.wails.System.AppType,
LogLevel: LogLevel,
Platform: Platform,
AppType: AppType,
AppConfig: AppConfig,
};

View File

@@ -13,7 +13,7 @@ module.exports = {
mode: 'production',
output: {
path: path.resolve(__dirname, '..', 'assets'),
filename: 'desktop.js',
filename: 'desktop_'+platform+'.js',
library: 'Wails'
},
resolve: {

View File

@@ -9,7 +9,9 @@ import (
type Menu interface {
UpdateApplicationMenu()
UpdateContextMenu(contextMenu *menu.ContextMenu)
UpdateTrayMenu(trayMenu *menu.TrayMenu)
SetTrayMenu(trayMenu *menu.TrayMenu)
DeleteTrayMenu(trayMenu *menu.TrayMenu)
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
}
type menuRuntime struct {
@@ -31,6 +33,14 @@ func (m *menuRuntime) UpdateContextMenu(contextMenu *menu.ContextMenu) {
m.bus.Publish("menu:updatecontextmenu", contextMenu)
}
func (m *menuRuntime) UpdateTrayMenu(trayMenu *menu.TrayMenu) {
m.bus.Publish("menu:updatetraymenu", trayMenu)
func (m *menuRuntime) SetTrayMenu(trayMenu *menu.TrayMenu) {
m.bus.Publish("menu:settraymenu", trayMenu)
}
func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) {
m.bus.Publish("menu:updatetraymenulabel", trayMenu)
}
func (m *menuRuntime) DeleteTrayMenu(trayMenu *menu.TrayMenu) {
m.bus.Publish("menu:deletetraymenu", trayMenu)
}

View File

@@ -35,5 +35,6 @@ func New(serviceBus *servicebus.ServiceBus) *Runtime {
// Quit the application
func (r *Runtime) Quit() {
// Start shutdown of Wails
r.bus.Publish("quit", "runtime.Quit()")
}

View File

@@ -3,12 +3,13 @@ package main
import (
"bytes"
"fmt"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/shell"
"io/ioutil"
"log"
"os"
"strings"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/shell"
)
func main() {
@@ -49,12 +50,20 @@ func main() {
log.Fatal(err)
}
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop.js")
wailsJS := fs.RelativePath("../assets/desktop_" + platform + ".js")
runtimeData, err := ioutil.ReadFile(wailsJS)
if err != nil {
log.Fatal(err)
}
// Copy this file to bridge directory for embedding
bridgeDir := fs.RelativePath("../../bridge/" + platform + ".js")
println("Copying", wailsJS, "to", bridgeDir)
err = fs.CopyFile(wailsJS, bridgeDir)
if err != nil {
log.Fatal(err)
}
// Convert to C structure
runtimeC := `
// runtime.c (c) 2019-Present Lea Anthony.

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