Compare commits

...

237 Commits

Author SHA1 Message Date
Lea Anthony
695f78861d [v2] v2.0.0-alpha.67 2021-06-21 19:28:51 +10:00
Lea Anthony
46ad4f4d18 [windows] Support SetPosition 2021-06-21 19:21:25 +10:00
Lea Anthony
2fc2d63e2d [v2] Remove SetColour 2021-06-21 18:05:45 +10:00
Lea Anthony
d137859d12 [windows] Support fullscreen config + api & unfullscreen api 2021-06-21 16:54:33 +10:00
Lea Anthony
09cf223aa2 [windows] Workaround webview2 bug being blank. Reduced flashing at start. 2021-06-21 15:48:24 +10:00
Lea Anthony
909da72eb2 [windows] Support StartHidden flag 2021-06-21 15:39:26 +10:00
Lea Anthony
2a06e2e577 [windows] Change location of application data 2021-06-21 15:27:14 +10:00
Lea Anthony
7f841ab85b [windows] hideWindowOnClose partial solution 2021-06-21 15:08:52 +10:00
Lea Anthony
a2d95e1b99 [v2] Update default template 2021-06-21 14:49:08 +10:00
Lea Anthony
d06f563bfe [windows] Fix application shutdown 2021-06-21 14:44:07 +10:00
Lea Anthony
c53d44b3ec [windows] Temporarily use common-dialog fork 2021-06-21 14:24:42 +10:00
Lea Anthony
5e51f426fa [windows] Fix app size, disable devtools in production build 2021-06-21 14:12:05 +10:00
Lea Anthony
193d9e8ed8 [windows] Better handle closing dialogs 2021-06-21 14:11:30 +10:00
Lea Anthony
2d03d355c2 [v2] Vanilla template fix (no drag) 2021-06-21 14:10:13 +10:00
Lea Anthony
185b7fed63 [windows] Lint fixes 2021-06-21 09:58:10 +10:00
Lea Anthony
ee6ad0bb27 [windows] Support frameless 2021-06-21 09:44:45 +10:00
Lea Anthony
3644f4ae1e [windows] Support drag 2021-06-21 09:04:14 +10:00
Lea Anthony
28d87a8e58 [windows] Improve Save Dialog 2021-06-20 16:09:22 +10:00
Lea Anthony
cf7d31e432 [windows] Improve Dialog Message API 2021-06-20 16:09:22 +10:00
Lea Anthony
fd40faabe8 [v2] Update build directory template. Update vanilla template. 2021-06-20 15:09:03 +10:00
Lea Anthony
d521f80dcd [windows] Improve Dialog API. Major refactor. 2021-06-20 13:48:30 +10:00
Lea Anthony
102a8cc5a6 [windows] Support Dialog API 2021-06-19 16:29:49 +10:00
Lea Anthony
e90f5361be [windows] Support webview2 runtime installation strategies 2021-06-15 21:25:08 +10:00
Lea Anthony
afe677d39d [windows] Update SDK scripts/files 2021-06-15 20:08:54 +10:00
Lea Anthony
58e6ce10ad [windows] Support disabling window icon 2021-06-14 12:00:47 +10:00
Lea Anthony
b0c522a59a [windows] Support translucent windows 2021-06-14 11:36:41 +10:00
Lea Anthony
62fc489001 [windows] Remove default app title 2021-06-14 11:04:24 +10:00
Lea Anthony
a269fc9e8c [windows] Support transparent webview 2021-06-13 20:47:01 +10:00
Lea Anthony
7cd6d109d4 [windows] Add missing files 2021-06-12 07:32:03 +10:00
Lea Anthony
1f8a2bb9b1 [windows] Update WebView2 to 1.0.864.35 2021-06-12 07:31:47 +10:00
Lea Anthony
eb2ac99067 [mac] fix compilation bug 2021-06-12 06:56:15 +10:00
Lea Anthony
79147c612e [v2] Big tidy up! 2021-06-12 06:49:38 +10:00
Lea Anthony
3d75ba174b [v2] If using -compiler flag, add go version to filename. 2021-06-11 15:24:42 +10:00
Lea Anthony
3933c5ab02 [windows] Better error message 2021-06-09 12:13:27 +10:00
Lea Anthony
aa5ff6ed2e [v2] Update vanilla template with @wails/runtime v1.3.20 2021-06-06 12:29:49 +10:00
Lea Anthony
407269b0d5 [v2] Update bridge assets. @wails/runtime v1.3.20 2021-06-06 12:21:26 +10:00
Lea Anthony
67a72cc693 [v2] Update debme for CopyFile() fix 2021-06-06 12:08:09 +10:00
Lea Anthony
955fe1d583 [v2] Better app icon 2021-06-06 11:35:35 +10:00
Lea Anthony
82bce89086 [mac] Update branding 2021-06-05 16:02:03 +10:00
Lea Anthony
c13257b9e9 [v2] v2.0.0-alpha.66 2021-06-05 14:37:12 +10:00
Lea Anthony
3f773f80ac [linux] Fix header signatures 2021-06-05 14:22:37 +10:00
Lea Anthony
5b23122b35 [v2] Make user + email discovery optional 2021-06-05 14:18:42 +10:00
Lea Anthony
d5cbfa6749 [linux] Support wails build and wails dev 2021-06-04 20:36:56 +10:00
Lea Anthony
3ad92192a9 [linux] Add linux runtime to wails dev 2021-06-04 20:34:55 +10:00
Lea Anthony
466676d99f [linux] Create Linux runtime 2021-06-04 20:32:40 +10:00
Lea Anthony
a1d5412465 [v2] Remove redundant apps package 2021-06-03 21:32:19 +10:00
Lea Anthony
9fdd5148ca [mac] Update runtime to use window.wailsInvoke 2021-06-03 21:01:14 +10:00
Lea Anthony
ac957b27cb [v1] Upgrade vanilla template to use runtime v1.3.19 2021-06-03 21:00:13 +10:00
Lea Anthony
20bc332720 [v1] Fix vanilla template package-lock.json 2021-06-03 20:33:25 +10:00
Lea Anthony
d2507660c2 [linux] Get CLI working. yum->dnf. doctor improvements. 2021-06-02 20:02:45 +10:00
Lea Anthony
64264c6378 [v2] Add README to vanilla template 2021-06-01 21:40:35 +10:00
Lea Anthony
ddadcdc18f [v2] Rebuild runtime 2021-05-31 20:09:04 +10:00
Lea Anthony
4738a0e0a8 [v2] Remove common.js 2021-05-31 20:08:11 +10:00
Lea Anthony
d2e4a0b0d2 [windows] Support wails dev 2021-05-31 20:01:40 +10:00
Lea Anthony
9f365ed85e [windows] Support rollup in vanilla template 2021-05-30 20:05:07 +10:00
Lea Anthony
5e733657c2 [v2] Updated runtime 2021-05-30 20:03:28 +10:00
Lea Anthony
93d9b61366 [windows] Fix bridge payload for 'wails dev' 2021-05-30 20:03:01 +10:00
Lea Anthony
b5d289bda9 [v2] Published @wails/runtime v1.3.14 2021-05-30 11:24:15 +10:00
Lea Anthony
319b7a3755 [v2] Change import to require for bridge for compatibility reasons 2021-05-30 11:22:40 +10:00
Lea Anthony
410d23b4d9 [windows] wails doctor docker support Better output 2021-05-30 11:06:38 +10:00
Lea Anthony
003e505c4a [mac] detect upx 2021-05-30 10:19:27 +10:00
Lea Anthony
22f09772d3 [mac] better wails doctor 2021-05-30 10:18:36 +10:00
Lea Anthony
a300172adc [windows] Better wails doctor installation details 2021-05-30 09:41:47 +10:00
Lea Anthony
1eba408f64 [windows] Better wails doctor diagnostics 2021-05-30 09:34:07 +10:00
Lea Anthony
be39b293b5 [mac] better wails doctor 2021-05-26 21:18:52 +10:00
Travis McLane
31cf04a944 fix shadowed err (#718) 2021-05-19 05:55:05 +10:00
Lea Anthony
f5912d29b6 Updated README.md 2021-05-18 21:29:03 +10:00
Lea Anthony
caad3a6b00 [windows] add icon to main window 2021-05-18 21:25:34 +10:00
Lea Anthony
08f4476087 Support for upx and more go:embed. 2021-05-18 21:25:16 +10:00
Lea Anthony
46ea3e6074 Better handling of process in wails dev 2021-05-18 21:22:52 +10:00
Lea Anthony
8be2a39daf [windows] Generate syso file for windows builds 2021-05-17 19:51:43 +10:00
Lea Anthony
7591b45ffa [windows] fix for no windows config 2021-05-17 19:50:12 +10:00
Travis McLane
f5056e7232 hack: enable builds on non-Windows 2021-05-16 13:51:14 +10:00
Alexander Hudek
101d344303 Merge pull request #715
* Fixed multi-line tags being minified incorrectly. Fixed self closing …

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

fix #620

Co-authored-by: amaury-tobias <amaury.tobiasqr@gmai.com>
2021-03-20 15:51:57 +11:00
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
244 changed files with 22391 additions and 2686 deletions

4
.gitignore vendored
View File

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

View File

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

View File

@@ -1,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

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

View File

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

View File

@@ -3,8 +3,10 @@ package build
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"text/tabwriter"
"time"
"github.com/leaanthony/clir"
@@ -22,10 +24,6 @@ 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)
// Setup production flag
production := false
command.BoolFlag("production", "Build in production mode", &production)
@@ -37,28 +35,47 @@ 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("upx", "Compress final binary with UPX (if installed)", &compress)
compressFlags := ""
command.StringFlag("upxflags", "Flags to pass to upx", &compressFlags)
// Setup Platform flag
platform := runtime.GOOS
command.StringFlag("platform", "Platform to target", &platform)
// Quiet Build
quiet := false
command.BoolFlag("q", "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)
// Log to file
logFile := ""
command.StringFlag("l", "Log to file", &logFile)
// tags to pass to `go`
tags := ""
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &tags)
// Retain assets
keepAssets := false
command.BoolFlag("k", "Keep generated assets", &keepAssets)
// Retain assets
outputFilename := ""
command.StringFlag("o", "Output filename", &outputFilename)
// Clean build directory
cleanBuildDirectory := false
command.BoolFlag("clean", "Clean the build directory before building", &cleanBuildDirectory)
webview2 := "download"
command.StringFlag("webview2", "WebView2 installer strategy: download,embed,browser,error.", &webview2)
command.Action(func() error {
quiet := verbosity == 0
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
@@ -72,28 +89,115 @@ 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)))
// Setup mode
mode := build.Debug
if production {
mode = build.Production
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
"darwin/amd64",
"darwin/arm64",
"darwin/universal",
"linux",
//"linux/amd64",
//"linux/arm-7",
"windows",
"windows/amd64",
})
if !validPlatformArch.Contains(platform) {
return fmt.Errorf("platform %s is not supported", platform)
}
if compress && platform == "darwin/universal" {
println("Warning: compress flag unsupported for universal binaries. Ignoring.")
compress = false
}
// Tags
userTags := []string{}
for _, tag := range strings.Split(tags, " ") {
thisTag := strings.TrimSpace(tag)
if thisTag != "" {
userTags = append(userTags, thisTag)
}
}
// Webview2 installer strategy (download by default)
wv2rtstrategy := ""
webview2 = strings.ToLower(webview2)
if webview2 != "" {
validWV2Runtime := slicer.String([]string{"download", "embed", "browser", "error"})
if !validWV2Runtime.Contains(webview2) {
return fmt.Errorf("invalid option for flag 'webview2': %s", webview2)
}
// These are the build tags associated with the strategies
switch webview2 {
case "embed":
wv2rtstrategy = "wv2runtime.embed"
case "error":
wv2rtstrategy = "wv2runtime.error"
case "browser":
wv2rtstrategy = "wv2runtime.browser"
}
}
// 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,
Verbosity: verbosity,
Compress: compress,
CompressFlags: compressFlags,
UserTags: userTags,
WebView2Strategy: wv2rtstrategy,
}
// 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

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

View File

@@ -3,14 +3,16 @@ package dev
import (
"fmt"
"io"
"log"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir"
@@ -20,6 +22,21 @@ import (
"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 {
@@ -37,6 +54,17 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
extensions := "go"
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
// extensions to trigger rebuilds
showWarnings := false
command.BoolFlag("w", "Show warnings", &showWarnings)
// Verbosity
verbosity := 1
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
loglevel := ""
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
command.Action(func() error {
// Create logger
@@ -49,10 +77,14 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
if err != nil {
return err
}
defer watcher.Close()
defer func(watcher *fsnotify.Watcher) {
err := watcher.Close()
if err != nil {
log.Fatal(err)
}
}(watcher)
var debugBinaryProcess *process.Process = nil
var buildFrontend bool = false
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
// Setup signal handler
@@ -61,12 +93,22 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
debounceQuit := make(chan bool, 1)
var passthruArgs []string
//if len(os.Args) > 2 {
// passthruArgs = os.Args[2:]
//}
// Do initial build
logger.Println("Building application for development...")
debugBinaryProcess, err = restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess)
newProcess, appBinary, err := restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
if newProcess != nil {
debugBinaryProcess = newProcess
}
if err != nil {
return err
}
var newBinaryProcess *process.Process
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Println("event: %+v", event)
@@ -79,7 +121,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
@@ -88,33 +130,29 @@ 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
}
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, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess)
newBinaryProcess, _, err = restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
if err != nil {
fmt.Printf("Error during build: %s", err.Error())
return
@@ -128,23 +166,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())
@@ -156,7 +199,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
for quit == false {
select {
case <-quitChannel:
println("Caught quit")
LogGreen("\nCaught quit")
// Notify debouncer to quit
debounceQuit <- true
quit = true
@@ -171,7 +214,13 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
}
}
logger.Println("Development mode exited")
// Remove dev binary
err = os.Remove(appBinary)
if err != nil {
return err
}
LogGreen("Development mode exited")
return nil
})
@@ -198,15 +247,15 @@ exit:
}
}
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process) (*process.Process, error) {
func restartApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string, passthruArgs []string, verbosity int) (*process.Process, string, error) {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
appBinary, err := buildApp(logger, ldflags, compilerCommand, verbosity)
println()
if err != nil {
logger.Fatal(err.Error())
return nil, errors.Wrap(err, "Build Failed:")
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 {
@@ -221,8 +270,12 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
// TODO: Generate `backend.js`
// Start up new binary
newProcess := process.NewProcess(logger, appBinary)
// Start up new binary with correct args
var args = []string{"-loglevel", loglevel}
if len(passthruArgs) > 0 {
args = append(args, passthruArgs...)
}
newProcess := process.NewProcess(logger, appBinary, args...)
err = newProcess.Start()
if err != nil {
// Remove binary
@@ -233,18 +286,21 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
logger.Fatal("Unable to start application: %s", err.Error())
}
return newProcess, nil
return newProcess, appBinary, nil
}
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
func buildApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, verbosity int) (string, error) {
// Create random output file
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
outputFile := "wailsdev"
if runtime.GOOS == "windows" {
outputFile += ".exe"
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
OutputType: "dev",
Mode: build.Debug,
Pack: false,
Platform: runtime.GOOS,
@@ -252,6 +308,7 @@ func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, co
Compiler: compilerCommand,
OutputFile: outputFile,
IgnoreFrontend: true,
Verbosity: verbosity,
}
return build.Build(buildOptions)

View File

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

View File

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

View File

@@ -2,13 +2,15 @@ package initialise
import (
"fmt"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"io"
"strings"
"time"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise/templates"
"github.com/leaanthony/clir"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/internal/templates"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/git"
)
@@ -39,7 +41,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Quiet Init
quiet := false
command.BoolFlag("q", "Supress output to console", &quiet)
command.BoolFlag("q", "Suppress output to console", &quiet)
initGit := false
gitInstalled := git.IsInstalled()
@@ -105,10 +107,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
}
// Try to discover author details from git config
err := findAuthorDetails(options)
if err != nil {
return err
}
findAuthorDetails(options)
return initProject(options)
})
@@ -128,6 +127,12 @@ func initProject(options *templates.Options) error {
return err
}
// Install the default assets
err = buildassets.Install(options.TargetDir, options.ProjectName)
if err != nil {
return err
}
if options.InitGit {
err = initGit(options)
if err != nil {
@@ -163,20 +168,18 @@ func initGit(options *templates.Options) error {
return nil
}
func findAuthorDetails(options *templates.Options) error {
// findAuthorDetails tries to find the user's name and email
// from gitconfig. If it finds them, it stores them in the project options
func findAuthorDetails(options *templates.Options) {
if git.IsInstalled() {
name, err := git.Name()
if err != nil {
return err
if err == nil {
options.AuthorName = strings.TrimSpace(name)
}
options.AuthorName = strings.TrimSpace(name)
email, err := git.Email()
if err != nil {
return err
if err == nil {
options.AuthorEmail = strings.TrimSpace(email)
}
options.AuthorEmail = strings.TrimSpace(email)
}
return nil
}

View File

@@ -1,14 +1,16 @@
package templates
import (
"embed"
"encoding/json"
"fmt"
"io/ioutil"
gofs "io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/leaanthony/debme"
"github.com/leaanthony/gosod"
"github.com/leaanthony/slicer"
"github.com/olekukonko/tablewriter"
@@ -16,6 +18,12 @@ import (
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
//go:embed templates
var templates embed.FS
//go:embed ides/*
var ides embed.FS
// Cahce for the templates
// We use this because we need different views of the same data
var templateCache []Template = nil
@@ -59,20 +67,21 @@ type Template struct {
HelpURL string `json:"helpurl"`
// Other data
Directory string `json:"-"`
FS gofs.FS `json:"-"`
}
func parseTemplate(directory string) (Template, error) {
templateJSON := filepath.Join(directory, "template.json")
func parseTemplate(template gofs.FS) (Template, error) {
var result Template
data, err := ioutil.ReadFile(templateJSON)
data, err := gofs.ReadFile(template, "template.json")
if err != nil {
return result, err
}
result.Directory = directory
err = json.Unmarshal(data, &result)
return result, err
if err != nil {
return result, err
}
result.FS = template
return result, nil
}
// TemplateShortNames returns a slicer of short template names
@@ -134,11 +143,13 @@ func getTemplateByShortname(shortname string) (Template, error) {
// Loads the template cache
func loadTemplateCache() error {
// Get local template directory
templateDir := fs.RelativePath("templates")
templatesFS, err := debme.FS(templates, "templates")
if err != nil {
return err
}
// Get directories
files, err := ioutil.ReadDir(templateDir)
files, err := templatesFS.ReadDir(".")
if err != nil {
return err
}
@@ -148,8 +159,11 @@ func loadTemplateCache() error {
for _, file := range files {
if file.IsDir() {
templateDir := filepath.Join(templateDir, file.Name())
template, err := parseTemplate(templateDir)
templateFS, err := templatesFS.FS(file.Name())
if err != nil {
return err
}
template, err := parseTemplate(templateFS)
if err != nil {
// Cannot parse this template, continue
continue
@@ -163,7 +177,6 @@ func loadTemplateCache() error {
// Install the given template
func Install(options *Options) error {
// Get cwd
cwd, err := os.Getwd()
if err != nil {
@@ -211,19 +224,16 @@ func Install(options *Options) error {
}
// Use Gosod to install the template
installer, err := gosod.TemplateDir(template.Directory)
if err != nil {
return err
}
installer := gosod.New(template.FS)
// Ignore template.json files
installer.IgnoreFilename("template.json")
installer.IgnoreFile("template.json")
// Setup the data.
// We use the directory name for the binary name, like Go
BinaryName := filepath.Base(options.TargetDir)
NPMProjectName := strings.ToLower(strings.ReplaceAll(BinaryName, " ", ""))
localWailsDirectory := fs.RelativePath("../..")
localWailsDirectory := fs.RelativePath("../../../../../..")
templateData := &Data{
ProjectName: options.ProjectName,
BinaryName: filepath.Base(options.TargetDir),
@@ -295,14 +305,14 @@ func generateIDEFiles(options *Options) error {
func generateVSCodeFiles(options *Options) error {
targetDir := filepath.Join(options.TargetDir, ".vscode")
sourceDir := fs.RelativePath(filepath.Join("./ides/vscode"))
// Use Gosod to install the template
installer, err := gosod.TemplateDir(sourceDir)
source, err := debme.FS(ides, "ides/vscode")
if err != nil {
return err
}
// Use gosod to install the template
installer := gosod.New(source)
binaryName := filepath.Base(options.TargetDir)
if runtime.GOOS == "windows" {
// yay windows

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -4,10 +4,10 @@
<link rel="stylesheet" href="/main.css">
</head>
<body>
<body data-wails-drag>
<div id="logo"></div>
<div id="input">
<input id="name" type="text"></input>
<div id="input" data-wails-no-drag>
<input id="name" type="text">
<button onclick="greet()">Greet</button>
</div>
<div id="result"></div>

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,14 +18,14 @@ import (
func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error {
command := app.NewSubCommand("update", "Update the Wails CLI")
command.LongDescription(`This command allows you to update your version of Wails.`)
command.LongDescription(`This command allows you to update your version of the Wails CLI.`)
// Setup flags
var prereleaseRequired bool
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired)
command.BoolFlag("pre", "Update CLI to latest Prerelease", &prereleaseRequired)
var specificVersion string
command.StringFlag("version", "Install a specific version (Overrides other flags)", &specificVersion)
command.StringFlag("version", "Install a specific version (Overrides other flags) of the CLI", &specificVersion)
command.Action(func() error {
@@ -143,7 +143,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
}
fmt.Println()
logger.Print("Installing Wails " + desiredVersion + "...")
logger.Print("Installing Wails CLI " + desiredVersion + "...")
// Run command in non module directory
homeDir, err := os.UserHomeDir()
@@ -158,7 +158,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
return err
}
fmt.Println()
logger.Println("Wails updated to " + desiredVersion)
logger.Println("Wails CLI updated to " + desiredVersion)
return nil
}

View File

@@ -1,8 +1,11 @@
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"
@@ -19,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)
@@ -58,5 +67,6 @@ func main() {
err = app.Run()
if err != nil {
println("\n\nERROR: " + err.Error())
os.Exit(1)
}
}

View File

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

View File

@@ -4,15 +4,19 @@ go 1.16
require (
github.com/Masterminds/semver v1.5.0
github.com/davecgh/go-spew v1.1.1
github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gorilla/websocket v1.4.1
github.com/imdario/mergo v0.3.11
github.com/jackmordaunt/icns v1.0.0
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/gosod v0.0.4
github.com/leaanthony/debme v1.2.1
github.com/leaanthony/go-ansi-parser v1.0.1
github.com/leaanthony/go-common-file-dialog v1.0.2
github.com/leaanthony/gosod v1.0.1
github.com/leaanthony/slicer v1.5.0
github.com/leaanthony/webview2runtime v1.1.0
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0
github.com/matryer/is v1.4.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/olekukonko/tablewriter v0.0.4
@@ -20,10 +24,10 @@ require (
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/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
nhooyr.io/websocket v1.8.6
)

View File

@@ -1,7 +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=
@@ -11,6 +10,8 @@ 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-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-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=
@@ -30,6 +31,8 @@ github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgj
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
@@ -42,10 +45,21 @@ github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eT
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/debme v1.1.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.2 h1:uJ2zXCxP4GwbpR/k/y6unuNbSJn3WTjt4rvHolX3Eys=
github.com/leaanthony/go-common-file-dialog v1.0.2/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.1 h1:F+4c3DmEBfigi7oAswCV2RpQ+k4DcNbhuCZUGdBHacQ=
github.com/leaanthony/gosod v1.0.1/go.mod h1:W8RyeSFBXu7RpIxPGEJfW4moSyGGEjlJMLV25wEbAdU=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/webview2runtime v1.1.0 h1:N0pv55ift8XtqozIp4PNOtRCJ/Qdd/qzx80lUpalS4c=
github.com/leaanthony/webview2runtime v1.1.0/go.mod h1:hH9GnWCve3DYzNaPOcPbhHQ7fodXR1QJNsnwixid4Tk=
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0 h1:FPGYnfxuuxqCZhrGq8nKjthEcYHgHmFbyY953Xv9cNI=
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
@@ -78,12 +92,18 @@ 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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -92,15 +112,14 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 h1:faDu4veV+8pcThn4fewv6TVlNCezafGoC1gM/mxQLbQ=
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=

7
v2/init.go Normal file
View File

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

15
v2/init_windows.go Normal file
View File

@@ -0,0 +1,15 @@
package wails
import (
"fmt"
"syscall"
)
// Init is called at the start of the application
func Init() error {
status, r, err := syscall.NewLazyDLL("user32.dll").NewProc("SetProcessDPIAware").Call()
if status == 0 {
return fmt.Errorf("exit status %d: %v %v", status, r, err)
}
return nil
}

View File

@@ -4,8 +4,9 @@ package app
import (
"flag"
"github.com/wailsapp/wails/v2/pkg/logger"
"strings"
"github.com/wailsapp/wails/v2/pkg/logger"
)
// Init initialises the application for a debug environment
@@ -19,10 +20,10 @@ func (a *App) Init() error {
}
// Set log levels
greeting := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
flag.Parse()
if len(*greeting) > 0 {
switch strings.ToLower(*greeting) {
if len(*loglevel) > 0 {
switch strings.ToLower(*loglevel) {
case "trace":
a.logger.SetLogLevel(logger.TRACE)
case "info":

View File

@@ -35,6 +35,7 @@ type App struct {
//binding *subsystem.Binding
call *subsystem.Call
menu *subsystem.Menu
url *subsystem.URL
dispatcher *messagedispatcher.Dispatcher
menuManager *menumanager.Manager
@@ -102,8 +103,17 @@ func CreateApp(appoptions *options.App) (*App, error) {
// Initialise the app
err := result.Init()
if err != nil {
return nil, err
}
return result, err
// Preflight Checks
err = result.PreflightChecks(appoptions)
if err != nil {
return nil, err
}
return result, nil
}
@@ -117,14 +127,6 @@ func (a *App) Run() error {
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
// Start the service bus
a.servicebus.Debug()
err = a.servicebus.Start()
@@ -132,7 +134,7 @@ func (a *App) Run() error {
return err
}
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
if err != nil {
return err
}
@@ -168,6 +170,19 @@ 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
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil {
@@ -207,6 +222,14 @@ func (a *App) Run() error {
return err
}
// 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 {
@@ -231,5 +254,10 @@ func (a *App) Run() error {
return err
}
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil
}

View File

@@ -66,7 +66,6 @@ func CreateApp(appoptions *options.App) (*App, error) {
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
// Create the menu manager
menuManager := menumanager.NewManager()
@@ -119,14 +118,6 @@ func (a *App) Run() error {
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
// Start the service bus
a.servicebus.Debug()
err = a.servicebus.Start()
@@ -134,7 +125,7 @@ func (a *App) Run() error {
return err
}
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, nil)
if err != nil {
return err
}
@@ -212,6 +203,14 @@ func (a *App) Run() error {
// 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 {
@@ -236,6 +235,10 @@ func (a *App) Run() error {
return err
}
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil
}

View File

@@ -0,0 +1,9 @@
//+build !windows
package app
import "github.com/wailsapp/wails/v2/pkg/options"
func (a *App) PreflightChecks(options *options.App) error {
return nil
}

View File

@@ -0,0 +1,19 @@
//+build windows
package app
import (
"github.com/wailsapp/wails/v2/internal/ffenestri/windows/wv2runtime"
"github.com/wailsapp/wails/v2/pkg/options"
)
func (a *App) PreflightChecks(options *options.App) error {
// Process the webview2 runtime situation. We can pass a strategy in via the `webview2` flag for `wails build`.
// This will determine how wv2runtime.Process will handle a lack of valid runtime.
err := wv2runtime.Process()
if err != nil {
return err
}
return nil
}

View File

@@ -79,7 +79,7 @@ func (a *App) Run() error {
// Default app options
var port = 8080
var ip = "localhost"
var supressLogging = false
var SuppressLogging = false
var debugMode = false
// Create CLI
@@ -89,7 +89,7 @@ func (a *App) Run() error {
cli.IntFlag("p", "Port to serve on", &port)
cli.StringFlag("i", "IP to serve on", &ip)
cli.BoolFlag("d", "Debug mode", &debugMode)
cli.BoolFlag("q", "Supress logging", &supressLogging)
cli.BoolFlag("q", "Suppress logging", &SuppressLogging)
// Setup main action
cli.Action(func() error {
@@ -98,8 +98,8 @@ func (a *App) Run() error {
a.webserver.SetPort(port)
a.webserver.SetIP(ip)
a.webserver.SetBindings(a.bindings)
// Log information (if we aren't supressing it)
if !supressLogging {
// Log information (if we aren't Suppressing it)
if !SuppressLogging {
cli.PrintBanner()
a.logger.Info("Running server at %s", a.webserver.URL())
}

View File

@@ -14,30 +14,6 @@ import (
"github.com/leaanthony/slicer"
)
const _comment = `
const backend = {
main: {
"xbarApp": {
"GetCategories": () => {
window.backend.main.xbarApp.GetCategories.call(arguments);
},
/**
* @param {string} arg1
*/
"InstallPlugin": (arg1) => {
window.backend.main.xbarApp.InstallPlugin.call(arguments);
},
"GetPlugins": () => {
window.backend.main.xbarApp.GetPlugins.call(arguments);
}
}
}
}
export default backend;`
//go:embed assets/package.json
var packageJSON []byte
@@ -58,7 +34,7 @@ const backend = {`)
sortedPackageNames.Add(packageName)
}
sortedPackageNames.Sort()
for _, packageName := range sortedPackageNames.AsSlice() {
sortedPackageNames.Each(func(packageName string) {
packages := store[packageName]
output.WriteString(fmt.Sprintf(" \"%s\": {", packageName))
output.WriteString("\n")
@@ -67,7 +43,8 @@ const backend = {`)
sortedStructNames.Add(structName)
}
sortedStructNames.Sort()
for _, structName := range sortedStructNames.AsSlice() {
sortedStructNames.Each(func(structName string) {
structs := packages[structName]
output.WriteString(fmt.Sprintf(" \"%s\": {", structName))
output.WriteString("\n")
@@ -78,7 +55,7 @@ const backend = {`)
}
sortedMethodNames.Sort()
for _, methodName := range sortedMethodNames.AsSlice() {
sortedMethodNames.Each(func(methodName string) {
methodDetails := structs[methodName]
output.WriteString(" /**\n")
output.WriteString(" * " + methodName + "\n")
@@ -99,6 +76,8 @@ const backend = {`)
}
returnType += ">"
returnTypeDetails = " - Go Type: " + methodDetails.Outputs[0].TypeName
} else {
returnType = "Promise<void>"
}
output.WriteString(" * @returns {" + returnType + "} " + returnTypeDetails + "\n")
output.WriteString(" */\n")
@@ -109,25 +88,29 @@ const backend = {`)
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")
// TODO: Make this configurable in wails.json
dirname, err := fs.RelativeToCwd("frontend/src/backend")
if err != nil {
log.Fatal(err)
}
if !fs.DirExists(dirname) {
err := fs.Mkdir(dirname)
err := fs.MkDirs(dirname)
if err != nil {
log.Fatal(err)
}
@@ -161,6 +144,8 @@ func goTypeToJSDocType(input string) string {
return "number"
case input == "bool":
return "boolean"
case input == "[]byte":
return "string"
case strings.HasPrefix(input, "[]"):
arrayType := goTypeToJSDocType(input[2:])
return "Array.<" + arrayType + ">"

View File

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

View File

@@ -11,6 +11,10 @@ type BridgeClient struct {
messageCache chan string
}
func (b BridgeClient) DeleteTrayMenuByID(id string) {
b.session.sendMessage("TD" + id)
}
func NewBridgeClient() *BridgeClient {
return &BridgeClient{
messageCache: make(chan string, 100),
@@ -22,9 +26,8 @@ func (b BridgeClient) Quit() {
}
func (b BridgeClient) NotifyEvent(message string) {
//b.session.sendMessage("n" + message)
b.session.sendMessage("n" + message)
b.session.log.Info("NotifyEvent: %s", message)
b.session.log.Info("NotifyEvent unsupported in Bridge mode")
}
func (b BridgeClient) CallResult(message string) {

File diff suppressed because one or more lines are too long

View File

@@ -19,6 +19,9 @@ type DialogClient struct {
log *logger.Logger
}
func (d *DialogClient) DeleteTrayMenuByID(id string) {
}
func NewDialogClient(log *logger.Logger) *DialogClient {
return &DialogClient{
log: log,

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,17 +13,17 @@ import (
)
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -lobjc
#cgo windows CXXFLAGS: -std=c++11
#cgo windows,amd64 LDFLAGS: -L./windows/x64 -lWebView2Loader -lgdi32 -lole32 -lShlwapi -luser32 -loleaut32 -ldwmapi
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
@@ -65,14 +65,6 @@ func NewApplicationWithConfig(config *options.App, logger *logger.Logger, menuMa
}
}
// NewApplication creates a new Application with the default config
func NewApplication(logger *logger.Logger) *Application {
return &Application{
config: options.Default,
logger: logger.CustomLogger("Ffenestri"),
}
}
func (a *Application) freeMemory() {
for _, mem := range a.memory {
// fmt.Printf("Freeing memory: %+v\n", mem)
@@ -138,10 +130,9 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
// Set debug if needed
C.SetDebug(app, a.bool2Cint(debug))
// TODO: Move frameless to Linux options
// if a.config.Frameless {
// C.DisableFrame(a.app)
// }
if a.config.Frameless {
C.DisableFrame(a.app)
}
if a.config.RGBA != 0 {
r, g, b, alpha := intToColour(a.config.RGBA)
@@ -173,7 +164,7 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
// Oh no! We couldn't initialise the application
a.logger.Fatal("Cannot initialise Application.")
}
//println("\n\n\n\n\n\nhererererer\n\n\n\n")
a.freeMemory()
return nil
}

View File

@@ -1,6 +1,10 @@
#ifndef __FFENESTRI_H__
#define __FFENESTRI_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
struct Application;
@@ -37,8 +41,16 @@ extern void DarkModeEnabled(struct Application*, char *callbackID);
extern void SetApplicationMenu(struct Application*, const char *);
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
extern void DeleteTrayMenuByID(struct Application*, const char *id);
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
extern void WebviewIsTransparent(struct Application*);
extern void WindowBackgroundIsTranslucent(struct Application*);
extern void* GetWindowHandle(struct Application*);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,25 +1,23 @@
// +build !windows
package ffenestri
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
import (
"runtime"
"strconv"
"strings"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger"
)
// Client is our implentation of messageDispatcher.Client
// Client is our implementation of messageDispatcher.Client
type Client struct {
app *Application
logger logger.CustomLogger
@@ -128,17 +126,72 @@ func (c *Client) WindowSetColour(colour int) {
C.SetColour(c.app.app, r, g, b, a)
}
// OpenDialog will open a dialog with the given title and filter
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
// OpenFileDialog will open a dialog with the given title and filter
func (c *Client) OpenFileDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
filters := []string{}
if runtime.GOOS == "darwin" {
for _, filter := range dialogOptions.Filters {
filters = append(filters, strings.Split(filter.Pattern, ",")...)
}
}
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(strings.Join(filters, ";")),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.AllowFiles),
c.app.bool2Cint(dialogOptions.AllowDirectories),
c.app.bool2Cint(dialogOptions.AllowMultiple),
c.app.bool2Cint(false),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.ResolvesAliases),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// OpenDirectoryDialog will open a dialog with the given title and filter
func (c *Client) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
filters := []string{}
if runtime.GOOS == "darwin" {
for _, filter := range dialogOptions.Filters {
filters = append(filters, strings.Split(filter.Pattern, ",")...)
}
}
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(strings.Join(filters, ";")),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(false), // Files
c.app.bool2Cint(true), // Directories
c.app.bool2Cint(false), // Multiple
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.ResolvesAliases),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// OpenMultipleFilesDialog will open a dialog with the given title and filter
func (c *Client) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
filters := []string{}
if runtime.GOOS == "darwin" {
for _, filter := range dialogOptions.Filters {
filters = append(filters, strings.Split(filter.Pattern, ",")...)
}
}
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(strings.Join(filters, ";")),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.AllowFiles),
c.app.bool2Cint(dialogOptions.AllowDirectories),
c.app.bool2Cint(true),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.ResolvesAliases),
@@ -148,10 +201,16 @@ func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string)
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
filters := []string{}
if runtime.GOOS == "darwin" {
for _, filter := range dialogOptions.Filters {
filters = append(filters, strings.Split(filter.Pattern, ",")...)
}
}
C.SaveDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(strings.Join(filters, ";")),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
@@ -208,3 +267,7 @@ func (c *Client) UpdateTrayMenuLabel(JSON string) {
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))
}

View File

@@ -0,0 +1,319 @@
// +build windows
package ffenestri
/*
#include "ffenestri.h"
*/
import "C"
import (
"encoding/json"
"github.com/leaanthony/go-common-file-dialog/cfd"
"golang.org/x/sys/windows"
"log"
"strconv"
"syscall"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger"
)
// Client is our implementation of messageDispatcher.Client
type Client struct {
app *Application
logger logger.CustomLogger
}
func newClient(app *Application) *Client {
return &Client{
app: app,
logger: app.logger,
}
}
// Quit the application
func (c *Client) Quit() {
c.app.logger.Trace("Got shutdown message")
C.Quit(c.app.app)
}
// NotifyEvent will pass on the event message to the frontend
func (c *Client) NotifyEvent(message string) {
eventMessage := `window.wails._.Notify(` + strconv.Quote(message) + `);`
c.app.logger.Trace("eventMessage = %+v", eventMessage)
C.ExecJS(c.app.app, c.app.string2CString(eventMessage))
}
// CallResult contains the result of the call from JS
func (c *Client) CallResult(message string) {
callbackMessage := `window.wails._.Callback(` + strconv.Quote(message) + `);`
c.app.logger.Trace("callbackMessage = %+v", callbackMessage)
C.ExecJS(c.app.app, c.app.string2CString(callbackMessage))
}
// WindowSetTitle sets the window title to the given string
func (c *Client) WindowSetTitle(title string) {
C.SetTitle(c.app.app, c.app.string2CString(title))
}
// WindowFullscreen will set the window to be fullscreen
func (c *Client) WindowFullscreen() {
C.Fullscreen(c.app.app)
}
// WindowUnFullscreen will unfullscreen the window
func (c *Client) WindowUnFullscreen() {
C.UnFullscreen(c.app.app)
}
// WindowShow will show the window
func (c *Client) WindowShow() {
C.Show(c.app.app)
}
// WindowHide will hide the window
func (c *Client) WindowHide() {
C.Hide(c.app.app)
}
// WindowCenter will hide the window
func (c *Client) WindowCenter() {
C.Center(c.app.app)
}
// WindowMaximise will maximise the window
func (c *Client) WindowMaximise() {
C.Maximise(c.app.app)
}
// WindowMinimise will minimise the window
func (c *Client) WindowMinimise() {
C.Minimise(c.app.app)
}
// WindowUnmaximise will unmaximise the window
func (c *Client) WindowUnmaximise() {
C.Unmaximise(c.app.app)
}
// WindowUnminimise will unminimise the window
func (c *Client) WindowUnminimise() {
C.Unminimise(c.app.app)
}
// WindowPosition will position the window to x,y on the
// monitor that the window is mostly on
func (c *Client) WindowPosition(x int, y int) {
C.SetPosition(c.app.app, C.int(x), C.int(y))
}
// WindowSize will resize the window to the given
// width and height
func (c *Client) WindowSize(width int, height int) {
C.SetSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetMinSize sets the minimum window size
func (c *Client) WindowSetMinSize(width int, height int) {
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetMaxSize sets the maximum window size
func (c *Client) WindowSetMaxSize(width int, height int) {
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour int) {
r, g, b, a := intToColour(colour)
C.SetColour(c.app.app, r, g, b, a)
}
func convertFilters(filters []dialog.FileFilter) []cfd.FileFilter {
var result []cfd.FileFilter
for _, filter := range filters {
result = append(result, cfd.FileFilter(filter))
}
return result
}
func userCancelled(err error) bool {
return err.Error() == "cancelled by user"
}
// OpenFileDialog will open a dialog with the given title and filter
func (c *Client) OpenFileDialog(options *dialog.OpenDialog, callbackID string) {
config := cfd.DialogConfig{
Folder: options.DefaultDirectory,
FileFilters: convertFilters(options.Filters),
FileName: options.DefaultFilename,
}
thisdialog, err := cfd.NewOpenFileDialog(config)
if err != nil {
log.Fatal(err)
}
thisdialog.SetParentWindowHandle(uintptr(C.GetWindowHandle(c.app.app)))
defer func(thisdialog cfd.OpenFileDialog) {
err := thisdialog.Release()
if err != nil {
log.Fatal(err)
}
}(thisdialog)
result, err := thisdialog.ShowAndGetResult()
if err != nil && !userCancelled(err) {
log.Fatal(err)
}
resultJSON, err := json.Marshal([]string{result})
if err != nil {
log.Fatal(err)
}
dispatcher.DispatchMessage("DO" + callbackID + "|" + string(resultJSON))
}
// OpenDirectoryDialog will open a dialog with the given title and filter
func (c *Client) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
config := cfd.DialogConfig{
Title: dialogOptions.Title,
Role: "PickFolder",
Folder: dialogOptions.DefaultDirectory,
}
thisDialog, err := cfd.NewSelectFolderDialog(config)
if err != nil {
log.Fatal()
}
thisDialog.SetParentWindowHandle(uintptr(C.GetWindowHandle(c.app.app)))
defer func(thisDialog cfd.SelectFolderDialog) {
err := thisDialog.Release()
if err != nil {
log.Fatal(err)
}
}(thisDialog)
result, err := thisDialog.ShowAndGetResult()
if err != nil && !userCancelled(err) {
log.Fatal(err)
}
resultJSON, err := json.Marshal(result)
dispatcher.DispatchMessage("DD" + callbackID + "|" + string(resultJSON))
}
// OpenMultipleFilesDialog will open a dialog with the given title and filter
func (c *Client) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
config := cfd.DialogConfig{
Title: dialogOptions.Title,
Role: "OpenMultipleFiles",
FileFilters: convertFilters(dialogOptions.Filters),
FileName: dialogOptions.DefaultFilename,
Folder: dialogOptions.DefaultDirectory,
}
thisdialog, err := cfd.NewOpenMultipleFilesDialog(config)
if err != nil {
log.Fatal(err)
}
thisdialog.SetParentWindowHandle(uintptr(C.GetWindowHandle(c.app.app)))
defer func(thisdialog cfd.OpenMultipleFilesDialog) {
err := thisdialog.Release()
if err != nil {
log.Fatal(err)
}
}(thisdialog)
result, err := thisdialog.ShowAndGetResults()
if err != nil && !userCancelled(err) {
log.Fatal(err)
}
resultJSON, err := json.Marshal(result)
if err != nil {
log.Fatal(err)
}
dispatcher.DispatchMessage("D*" + callbackID + "|" + string(resultJSON))
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
saveDialog, err := cfd.NewSaveFileDialog(cfd.DialogConfig{
Title: dialogOptions.Title,
Role: "SaveFile",
FileFilters: convertFilters(dialogOptions.Filters),
FileName: dialogOptions.DefaultFilename,
Folder: dialogOptions.DefaultDirectory,
})
if err != nil {
log.Fatal(err)
}
saveDialog.SetParentWindowHandle(uintptr(C.GetWindowHandle(c.app.app)))
err = saveDialog.Show()
if err != nil {
log.Fatal(err)
}
result, err := saveDialog.GetResult()
if err != nil && !userCancelled(err) {
log.Fatal(err)
}
dispatcher.DispatchMessage("DS" + callbackID + "|" + result)
}
// MessageDialog will open a message dialog with the given options
func (c *Client) MessageDialog(options *dialog.MessageDialog, callbackID string) {
title, err := syscall.UTF16PtrFromString(options.Title)
if err != nil {
log.Fatal(err)
}
message, err := syscall.UTF16PtrFromString(options.Message)
if err != nil {
log.Fatal(err)
}
var flags uint32
switch options.Type {
case dialog.InfoDialog:
flags = windows.MB_OK | windows.MB_ICONINFORMATION
case dialog.ErrorDialog:
flags = windows.MB_ICONERROR | windows.MB_OK
case dialog.QuestionDialog:
flags = windows.MB_YESNO
case dialog.WarningDialog:
flags = windows.MB_OK | windows.MB_ICONWARNING
}
button, _ := windows.MessageBox(windows.HWND(C.GetWindowHandle(c.app.app)), message, title, flags|windows.MB_SYSTEMMODAL)
// This maps MessageBox return values to strings
responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"}
result := "Error"
if int(button) < len(responses) {
result = responses[button]
}
dispatcher.DispatchMessage("DM" + callbackID + "|" + result)
}
// DarkModeEnabled sets the application to use dark mode
func (c *Client) DarkModeEnabled(callbackID string) {
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
}
// SetApplicationMenu sets the application menu
func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
}
// SetTrayMenu sets the tray menu
func (c *Client) SetTrayMenu(trayMenuJSON string) {
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
}
// UpdateTrayMenuLabel updates a tray menu label
func (c *Client) UpdateTrayMenuLabel(JSON string) {
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
}
// UpdateContextMenu will update the current context menu
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
}
// DeleteTrayMenuByID will remove a tray menu based on the given id
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

@@ -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

@@ -11,24 +11,51 @@
#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 ALLOC(classname) msg(c(classname), s("alloc"))
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
#define str(input) ((id(*)(id, SEL, const char *))objc_msgSend)(c("NSString"), s("stringWithUTF8String:"), input)
#define strunicode(input) ((id(*)(id, SEL, id, unsigned short))objc_msgSend)(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
#define cstr(input) (const char *)msg_reg(input, s("UTF8String"))
#define url(input) msg_id(c("NSURL"), s("fileURLWithPath:"), str(input))
#define ALLOC(classname) msg_reg(c(classname), s("alloc"))
#define ALLOC_INIT(classname) msg_reg(msg_reg(c(classname), s("alloc")), s("init"))
#if defined (__aarch64__)
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("frame"))
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("bounds"))
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend)(processInfo, s("operatingSystemVersion"));
#endif
#if defined (__x86_64__)
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))msg)(receiver, s("backingScaleFactor"))
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend_stret)(processInfo, s("operatingSystemVersion"));
#endif
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))objc_msgSend)(receiver, s("backingScaleFactor"))
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
#define MAIN_WINDOW_CALL(str) msg_reg(app->mainWindow, s((str)))
#define NSBackingStoreBuffered 2
@@ -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);
@@ -110,6 +143,12 @@ void SetTray(struct Application* app, const char *, const char *, const char *);
//void SetContextMenus(struct Application* app, const char *);
void AddTrayMenu(struct Application* app, const char *);
void 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

@@ -2,6 +2,7 @@
#ifndef __FFENESTRI_LINUX_H__
#define __FFENESTRI_LINUX_H__
#include "common.h"
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
#include <time.h>
@@ -11,9 +12,10 @@
#include <stdarg.h>
// References to assets
extern const unsigned char *assets[];
extern const unsigned char runtime;
extern const char *icon[];
#include "icon.h"
#include "assets.h"
// Constants
#define PRIMARY_MOUSE_BUTTON 1
@@ -23,17 +25,6 @@ extern const char *icon[];
// MAIN DEBUG FLAG
int debug;
// Credit: https://stackoverflow.com/a/8465083
char *concat(const char *s1, const char *s2)
{
const size_t len1 = strlen(s1);
const size_t len2 = strlen(s2);
char *result = malloc(len1 + len2 + 1);
memcpy(result, s1, len1);
memcpy(result + len1, s2, len2 + 1);
return result;
}
// Debug works like sprintf but mutes if the global debug flag is true
// Credit: https://stackoverflow.com/a/20639708
void Debug(char *message, ...)
@@ -312,17 +303,17 @@ void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
}
// SetColour sets the colour of the webview to the given colour string
int SetColour(struct Application *app, const char *colourString)
void SetColour(struct Application *app, int red, int green, int blue, int alpha)
{
GdkRGBA rgba;
gboolean result = gdk_rgba_parse(&rgba, colourString);
if (result == FALSE)
{
return 0;
}
// Debug("Setting webview colour to: %s", colourString);
webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
return 1;
// GdkRGBA rgba;
// rgba.
// gboolean result = gdk_rgba_parse(&rgba, colourString);
// if (result == FALSE)
// {
// return 0;
// }
// // Debug("Setting webview colour to: %s", colourString);
// webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
}
// DisableFrame disables the window frame
@@ -523,6 +514,11 @@ static void load_finished_cb(WebKitWebView *webView,
Debug("Binding Methods");
syncEval(app, app->bindings);
// Setup IPC commands
Debug("Setting up IPC methods");
const char *invoke = "window.wailsInvoke=function(message){window.webkit.messageHandlers.external.postMessage(message);};window.wailsDrag=function(message){window.webkit.messageHandlers.windowDrag.postMessage(message);};window.wailsContextMenuMessage=function(message){window.webkit.messageHandlers.contextMenu.postMessage(message);};";
syncEval(app, invoke);
// Runtime
Debug("Setting up Wails runtime");
syncEval(app, &runtime);
@@ -824,7 +820,7 @@ void Show(struct Application *app) {
// maximiseInternal maximises the main window
void maximiseInternal(struct Application *app) {
gtk_window_maximize(GTK_WIDGET(app->mainWindow));
gtk_window_maximize(GTK_WINDOW(app->mainWindow));
}
// Maximise places the maximiseInternal method onto the main thread for execution
@@ -841,7 +837,7 @@ void Maximise(struct Application *app) {
// unmaximiseInternal unmaximises the main window
void unmaximiseInternal(struct Application *app) {
gtk_window_unmaximize(GTK_WIDGET(app->mainWindow));
gtk_window_unmaximize(GTK_WINDOW(app->mainWindow));
}
// Unmaximise places the unmaximiseInternal method onto the main thread for execution
@@ -857,6 +853,21 @@ void Unmaximise(struct Application *app) {
}
void DarkModeEnabled(struct Application* app, char *callbackID) {}
void SetApplicationMenu(struct Application* app, const char *menuJSON) {}
void AddTrayMenu(struct Application* app, const char *menuTrayJSON) {}
void SetTrayMenu(struct Application* app, const char *menuTrayJSON) {}
void DeleteTrayMenuByID(struct Application* app, const char *id) {}
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {}
void AddContextMenu(struct Application* app, char *contextMenuJSON) {}
void UpdateContextMenu(struct Application* app, char *contextMenuJSON) {}
void WebviewIsTransparent(struct Application* app) {}
void WindowBackgroundIsTranslucent(struct Application* app) {}
void OpenDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {}
void SaveDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {}
void MessageDialog(struct Application* app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {}
// minimiseInternal minimises the main window
void minimiseInternal(struct Application *app) {
gtk_window_iconify(app->mainWindow);

View File

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

View File

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

View File

@@ -0,0 +1,845 @@
// Some code may be inspired by or directly used from Webview (c) zserge.
// License included in README.md
#include "ffenestri_windows.h"
#include "wv2ComHandler_windows.h"
#include <functional>
#include <atomic>
#include <Shlwapi.h>
#include <locale>
#include <codecvt>
#include "windows/WebView2.h"
#include <winuser.h>
#include "effectstructs_windows.h"
#include <Shlobj.h>
int debug = 0;
DWORD mainThread;
#define WS_EX_NOREDIRECTIONBITMAP 0x00200000L
// --- Assets
extern const unsigned char runtime;
extern const unsigned char *defaultDialogIcons[];
// dispatch will execute the given `func` pointer
void dispatch(dispatchFunction func) {
PostThreadMessage(mainThread, WM_APP, 0, (LPARAM) new dispatchFunction(func));
}
LPWSTR cstrToLPWSTR(const char *cstr) {
int wchars_num = MultiByteToWideChar( CP_UTF8 , 0 , cstr , -1, NULL , 0 );
wchar_t* wstr = new wchar_t[wchars_num+1];
MultiByteToWideChar( CP_UTF8 , 0 , cstr , -1, wstr , wchars_num );
return wstr;
}
// Credit: https://stackoverflow.com/a/9842450
char* LPWSTRToCstr(LPWSTR input) {
int length = WideCharToMultiByte(CP_UTF8, 0, input, -1, 0, 0, NULL, NULL);
char* output = new char[length];
WideCharToMultiByte(CP_UTF8, 0, input, -1, output , length, NULL, NULL);
return output;
}
struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
// Create application
struct Application *result = (struct Application*)malloc(sizeof(struct Application));
result->window = nullptr;
result->webview = nullptr;
result->webviewController = nullptr;
result->title = title;
result->width = width;
result->height = height;
result->resizable = resizable;
result->devtools = devtools;
result->fullscreen = fullscreen;
result->startHidden = startHidden;
result->logLevel = logLevel;
result->hideWindowOnClose = hideWindowOnClose;
result->webviewIsTranparent = false;
result->windowBackgroundIsTranslucent = false;
result->disableWindowIcon = false;
// Min/Max Width/Height
result->minWidth = 0;
result->minHeight = 0;
result->maxWidth = 0;
result->maxHeight = 0;
// Default colour
result->backgroundColour.R = 255;
result->backgroundColour.G = 255;
result->backgroundColour.B = 255;
result->backgroundColour.A = 255;
// Have a frame by default
result->frame = 1;
// Capture Main Thread
mainThread = GetCurrentThreadId();
// Startup url
result->startupURL = nullptr;
// Used to remember the window location when going fullscreen
result->previousPlacement = { sizeof(result->previousPlacement) };
return result;
}
void* GetWindowHandle(struct Application *app) {
return (void*)app->window;
}
void SetMinWindowSize(struct Application* app, int minWidth, int minHeight) {
app->minWidth = (LONG)minWidth;
app->minHeight = (LONG)minHeight;
}
void SetMaxWindowSize(struct Application* app, int maxWidth, int maxHeight) {
app->maxWidth = (LONG)maxWidth;
app->maxHeight = (LONG)maxHeight;
}
void SetBindings(struct Application *app, const char *bindings) {
std::string temp = std::string("window.wailsbindings = \"") + std::string(bindings) + std::string("\";");
app->bindings = new char[temp.length()+1];
memcpy(app->bindings, temp.c_str(), temp.length()+1);
}
void performShutdown(struct Application *app) {
if( app->startupURL != nullptr ) {
delete[] app->startupURL;
}
messageFromWindowCallback("WC");
}
// Credit: https://gist.github.com/ysc3839/b08d2bff1c7dacde529bed1d37e85ccf
void enableTranslucentBackground(struct Application *app) {
HMODULE hUser = GetModuleHandleA("user32.dll");
if (hUser)
{
pfnSetWindowCompositionAttribute setWindowCompositionAttribute = (pfnSetWindowCompositionAttribute)GetProcAddress(hUser, "SetWindowCompositionAttribute");
if (setWindowCompositionAttribute)
{
ACCENT_POLICY accent = { ACCENT_ENABLE_BLURBEHIND, 0, 0, 0 };
WINDOWCOMPOSITIONATTRIBDATA data;
data.Attrib = WCA_ACCENT_POLICY;
data.pvData = &accent;
data.cbData = sizeof(accent);
setWindowCompositionAttribute(app->window, &data);
}
}
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
struct Application *app = (struct Application *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch(msg) {
case WM_CLOSE: {
DestroyWindow( app->window );
break;
}
case WM_DESTROY: {
if( app->hideWindowOnClose ) {
Hide(app);
} else {
PostQuitMessage(0);
}
break;
}
case WM_SIZE: {
if ( app == NULL ) {
return 0;
}
if( app->webviewController != nullptr) {
RECT bounds;
GetClientRect(app->window, &bounds);
app->webviewController->put_Bounds(bounds);
}
break;
}
case WM_GETMINMAXINFO: {
// Exit early if this is called before the window is created.
if ( app == NULL ) {
return 0;
}
// get pixel density
HDC hDC = GetDC(NULL);
double DPIScaleX = GetDeviceCaps(hDC, 88)/96.0;
double DPIScaleY = GetDeviceCaps(hDC, 90)/96.0;
ReleaseDC(NULL, hDC);
RECT rcClient, rcWind;
POINT ptDiff;
GetClientRect(hwnd, &rcClient);
GetWindowRect(hwnd, &rcWind);
int widthExtra = (rcWind.right - rcWind.left) - rcClient.right;
int heightExtra = (rcWind.bottom - rcWind.top) - rcClient.bottom;
LPMINMAXINFO mmi = (LPMINMAXINFO) lParam;
if (app->minWidth > 0 && app->minHeight > 0) {
mmi->ptMinTrackSize.x = app->minWidth * DPIScaleX + widthExtra;
mmi->ptMinTrackSize.y = app->minHeight * DPIScaleY + heightExtra;
}
if (app->maxWidth > 0 && app->maxHeight > 0) {
mmi->ptMaxSize.x = app->maxWidth * DPIScaleX + widthExtra;
mmi->ptMaxSize.y = app->maxHeight * DPIScaleY + heightExtra;
mmi->ptMaxTrackSize.x = app->maxWidth * DPIScaleX + widthExtra;
mmi->ptMaxTrackSize.y = app->maxHeight * DPIScaleY + heightExtra;
}
return 0;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
void init(struct Application *app, const char* js) {
LPCWSTR wjs = cstrToLPWSTR(js);
app->webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr);
delete[] wjs;
}
void execJS(struct Application* app, const char *script) {
LPWSTR s = cstrToLPWSTR(script);
app->webview->ExecuteScript(s, nullptr);
delete[] s;
}
void loadAssets(struct Application* app) {
// setup window.wailsInvoke
std::string initialCode = std::string("window.wailsInvoke=function(m){window.chrome.webview.postMessage(m)};");
// Load bindings
initialCode += std::string(app->bindings);
delete[] app->bindings;
// Load runtime
initialCode += std::string((const char*)&runtime);
int index = 1;
while(1) {
// Get next asset pointer
const unsigned char *asset = assets[index];
// If we have no more assets, break
if (asset == 0x00) {
break;
}
initialCode += std::string((const char*)asset);
index++;
};
// Disable context menu if not in debug mode
if( debug != 1 ) {
initialCode += std::string("wails._.DisableDefaultContextMenu();");
}
initialCode += std::string("window.wailsInvoke('completed');");
// Keep a copy of the code
app->initialCode = new char[initialCode.length()+1];
memcpy(app->initialCode, initialCode.c_str(), initialCode.length()+1);
execJS(app, app->initialCode);
// Show app if we need to
if( app->startHidden == false ) {
Show(app);
}
}
// This is called when all our assets are loaded into the DOM
void completed(struct Application* app) {
delete[] app->initialCode;
app->initialCode = nullptr;
// Process whether window should show by default
int startVisibility = SW_SHOWNORMAL;
if ( app->startHidden == 1 ) {
startVisibility = SW_HIDE;
}
// Fix for webview2 bug: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077
// Will be fixed in next stable release
app->webviewController->put_IsVisible(false);
app->webviewController->put_IsVisible(true);
// Private setTitle as we're on the main thread
if( app->frame == 1) {
setTitle(app, app->title);
}
ShowWindow(app->window, startVisibility);
UpdateWindow(app->window);
SetFocus(app->window);
if( app->startupURL == nullptr ) {
messageFromWindowCallback("SS");
return;
}
std::string readyMessage = std::string("SS") + std::string(app->startupURL);
messageFromWindowCallback(readyMessage.c_str());
}
//
bool initWebView2(struct Application *app, int debugEnabled, messageCallback cb) {
debug = debugEnabled;
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
std::atomic_flag flag = ATOMIC_FLAG_INIT;
flag.test_and_set();
char currentExePath[MAX_PATH];
GetModuleFileNameA(NULL, currentExePath, MAX_PATH);
char *currentExeName = PathFindFileNameA(currentExePath);
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
std::wstring userDataFolder =
wideCharConverter.from_bytes(std::getenv("APPDATA"));
std::wstring currentExeNameW = wideCharConverter.from_bytes(currentExeName);
ICoreWebView2Controller *controller;
ICoreWebView2* webview;
HRESULT res = CreateCoreWebView2EnvironmentWithOptions(
nullptr, (userDataFolder + L"/" + currentExeNameW).c_str(), nullptr,
new wv2ComHandler(app, app->window, cb,
[&](ICoreWebView2Controller *webviewController) {
controller = webviewController;
controller->get_CoreWebView2(&webview);
webview->AddRef();
ICoreWebView2Settings* settings;
webview->get_Settings(&settings);
if ( debugEnabled == 0 ) {
settings->put_AreDefaultContextMenusEnabled(FALSE);
}
// Fix for invisible webview
if( app->startHidden ) {}
flag.clear();
}));
if (!SUCCEEDED(res))
{
switch (res)
{
case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
{
MessageBox(
app->window,
L"Couldn't find Edge installation. "
"Do you have a version installed that's compatible with this "
"WebView2 SDK version?",
nullptr, MB_OK);
}
break;
case HRESULT_FROM_WIN32(ERROR_FILE_EXISTS):
{
MessageBox(
app->window, L"User data folder cannot be created because a file with the same name already exists.", nullptr, MB_OK);
}
break;
case E_ACCESSDENIED:
{
MessageBox(
app->window, L"Unable to create user data folder, Access Denied.", nullptr, MB_OK);
}
break;
case E_FAIL:
{
MessageBox(
app->window, L"Edge runtime unable to start", nullptr, MB_OK);
}
break;
default:
{
MessageBox(app->window, L"Failed to create WebView2 environment", nullptr, MB_OK);
}
}
}
if (res != S_OK) {
CoUninitialize();
return false;
}
MSG msg = {};
while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
app->webviewController = controller;
app->webview = webview;
// Resize WebView to fit the bounds of the parent window
RECT bounds;
GetClientRect(app->window, &bounds);
app->webviewController->put_Bounds(bounds);
// Let the backend know we have initialised
app->webview->AddScriptToExecuteOnDocumentCreated(L"window.chrome.webview.postMessage('initialised');", nullptr);
// Load the HTML
LPCWSTR html = (LPCWSTR) cstrToLPWSTR((char*)assets[0]);
app->webview->Navigate(html);
messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}");
return true;
}
void initialCallback(std::string message) {
printf("MESSAGE=%s\n", message);
}
void Run(struct Application* app, int argc, char **argv) {
// Register the window class.
const wchar_t CLASS_NAME[] = L"Ffenestri";
WNDCLASSEX wc = { };
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;
if( app->disableWindowIcon == false ) {
wc.hIcon = LoadIcon(wc.hInstance, MAKEINTRESOURCE(100));
wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(100));
}
// Configure translucency
DWORD dwExStyle = 0;
if ( app->windowBackgroundIsTranslucent) {
dwExStyle = WS_EX_NOREDIRECTIONBITMAP;
wc.hbrBackground = CreateSolidBrush(RGB(255,255,255));
}
RegisterClassEx(&wc);
// Process window style
DWORD windowStyle = WS_OVERLAPPEDWINDOW | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
if (app->resizable == 0) {
windowStyle &= ~WS_MAXIMIZEBOX;
windowStyle &= ~WS_THICKFRAME;
}
if ( app->frame == 0 ) {
windowStyle &= ~WS_OVERLAPPEDWINDOW;
windowStyle &= ~WS_CAPTION;
windowStyle |= WS_POPUP;
}
// Create the window.
app->window = CreateWindowEx(
dwExStyle, // Optional window styles.
CLASS_NAME, // Window class
L"", // Window text
windowStyle, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, app->width, app->height,
NULL, // Parent window
NULL, // Menu
wc.hInstance, // Instance handle
NULL // Additional application data
);
if (app->window == NULL)
{
return;
}
if ( app->fullscreen ) {
fullscreen(app);
}
// Credit: https://stackoverflow.com/a/35482689
if( app->disableWindowIcon && app->frame == 1 ) {
int extendedStyle = GetWindowLong(app->window, GWL_EXSTYLE);
SetWindowLong(app->window, GWL_EXSTYLE, extendedStyle | WS_EX_DLGMODALFRAME);
SetWindowPos(nullptr, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
}
if ( app->windowBackgroundIsTranslucent ) {
// Enable the translucent background effect
enableTranslucentBackground(app);
// Setup transparency of main window. This allows the blur to show through.
SetLayeredWindowAttributes(app->window,RGB(255,255,255),0,LWA_COLORKEY);
}
// Store application pointer in window handle
SetWindowLongPtr(app->window, GWLP_USERDATA, (LONG_PTR)app);
// private center() as we are on main thread
center(app);
// Add webview2
initWebView2(app, debug, initialCallback);
if( app->webviewIsTranparent ) {
wchar_t szBuff[64];
ICoreWebView2Controller2 *wc2;
wc2 = nullptr;
app->webviewController->QueryInterface(IID_ICoreWebView2Controller2, (void**)&wc2);
COREWEBVIEW2_COLOR wvColor;
wvColor.R = app->backgroundColour.R;
wvColor.G = app->backgroundColour.G;
wvColor.B = app->backgroundColour.B;
wvColor.A = app->backgroundColour.A == 0 ? 0 : 255;
if( app->windowBackgroundIsTranslucent ) {
wvColor.A = 0;
}
HRESULT result = wc2->put_DefaultBackgroundColor(wvColor);
if (!SUCCEEDED(result))
{
switch (result)
{
case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
{
MessageBox(
app->window,
L"Couldn't find Edge installation. "
"Do you have a version installed that's compatible with this "
"WebView2 SDK version?",
nullptr, MB_OK);
}
break;
case HRESULT_FROM_WIN32(ERROR_FILE_EXISTS):
{
MessageBox(
app->window, L"User data folder cannot be created because a file with the same name already exists.", nullptr, MB_OK);
}
break;
case E_ACCESSDENIED:
{
MessageBox(
app->window, L"Unable to create user data folder, Access Denied.", nullptr, MB_OK);
}
break;
case E_FAIL:
{
MessageBox(
app->window, L"Edge runtime unable to start", nullptr, MB_OK);
}
break;
default:
{
MessageBox(app->window, L"Failed to create WebView2 environment", nullptr, MB_OK);
}
}
}
}
// Main event loop
MSG msg;
BOOL res;
while ((res = GetMessage(&msg, NULL, 0, 0)) != -1) {
if (msg.hwnd) {
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
}
if (msg.message == WM_APP) {
dispatchFunction *f = (dispatchFunction*) msg.lParam;
(*f)();
delete(f);
} else if (msg.message == WM_QUIT) {
performShutdown(app);
return;
}
}
}
void SetDebug(struct Application* app, int flag) {
debug = flag;
}
void ExecJS(struct Application* app, const char *script) {
ON_MAIN_THREAD(
execJS(app, script);
);
}
void hide(struct Application* app) {
ShowWindow(app->window, SW_HIDE);
}
void Hide(struct Application* app) {
ON_MAIN_THREAD(
hide(app);
);
}
void show(struct Application* app) {
ShowWindow(app->window, SW_SHOW);
}
void Show(struct Application* app) {
ON_MAIN_THREAD(
show(app);
);
}
void DisableWindowIcon(struct Application* app) {
app->disableWindowIcon = true;
}
void center(struct Application* app) {
HMONITOR currentMonitor = MonitorFromWindow(app->window, MONITOR_DEFAULTTONEAREST);
MONITORINFO info = {0};
info.cbSize = sizeof(info);
GetMonitorInfoA(currentMonitor, &info);
RECT workRect = info.rcWork;
LONG screenMiddleW = (workRect.right - workRect.left) / 2;
LONG screenMiddleH = (workRect.bottom - workRect.top) / 2;
RECT winRect;
if (app->frame == 1) {
GetWindowRect(app->window, &winRect);
} else {
GetClientRect(app->window, &winRect);
}
LONG winWidth = winRect.right - winRect.left;
LONG winHeight = winRect.bottom - winRect.top;
LONG windowX = screenMiddleW - (winWidth / 2);
LONG windowY = screenMiddleH - (winHeight / 2);
SetWindowPos(app->window, HWND_TOP, windowX, windowY, winWidth, winHeight, SWP_NOSIZE);
}
void Center(struct Application* app) {
ON_MAIN_THREAD(
center(app);
);
}
UINT getWindowPlacement(struct Application* app) {
WINDOWPLACEMENT lpwndpl;
lpwndpl.length = sizeof(WINDOWPLACEMENT);
BOOL result = GetWindowPlacement(app->window, &lpwndpl);
if( result == 0 ) {
// TODO: Work out what this call failing means
return -1;
}
return lpwndpl.showCmd;
}
int isMaximised(struct Application* app) {
return getWindowPlacement(app) == SW_SHOWMAXIMIZED;
}
void maximise(struct Application* app) {
ShowWindow(app->window, SW_MAXIMIZE);
}
void Maximise(struct Application* app) {
ON_MAIN_THREAD(
maximise(app);
);
}
void unmaximise(struct Application* app) {
ShowWindow(app->window, SW_RESTORE);
}
void Unmaximise(struct Application* app) {
ON_MAIN_THREAD(
unmaximise(app);
);
}
void ToggleMaximise(struct Application* app) {
if(isMaximised(app)) {
return Unmaximise(app);
}
return Maximise(app);
}
int isMinimised(struct Application* app) {
return getWindowPlacement(app) == SW_SHOWMINIMIZED;
}
void minimise(struct Application* app) {
ShowWindow(app->window, SW_MINIMIZE);
}
void Minimise(struct Application* app) {
ON_MAIN_THREAD(
minimise(app);
);
}
void unminimise(struct Application* app) {
ShowWindow(app->window, SW_RESTORE);
}
void Unminimise(struct Application* app) {
ON_MAIN_THREAD(
unminimise(app);
);
}
void ToggleMinimise(struct Application* app) {
if(isMinimised(app)) {
return Unminimise(app);
}
return Minimise(app);
}
void SetColour(struct Application* app, int red, int green, int blue, int alpha) {
app->backgroundColour.R = red;
app->backgroundColour.G = green;
app->backgroundColour.B = blue;
app->backgroundColour.A = alpha;
}
void SetSize(struct Application* app, int width, int height) {
if( app->maxWidth > 0 && width > app->maxWidth ) {
width = app->maxWidth;
}
if ( app->maxHeight > 0 && height > app->maxHeight ) {
height = app->maxHeight;
}
SetWindowPos(app->window, nullptr, 0, 0, width, height, SWP_NOMOVE);
}
void setPosition(struct Application* app, int x, int y) {
HMONITOR currentMonitor = MonitorFromWindow(app->window, MONITOR_DEFAULTTONEAREST);
MONITORINFO info = {0};
info.cbSize = sizeof(info);
GetMonitorInfoA(currentMonitor, &info);
RECT workRect = info.rcWork;
LONG newX = workRect.left + x;
LONG newY = workRect.top + y;
SetWindowPos(app->window, HWND_TOP, newX, newY, 0, 0, SWP_NOSIZE);
}
void SetPosition(struct Application* app, int x, int y) {
ON_MAIN_THREAD(
setPosition(app, x, y);
);
}
void Quit(struct Application* app) {
// Override the hide window on close flag
app->hideWindowOnClose = 0;
ON_MAIN_THREAD(
DestroyWindow(app->window);
);
}
// Credit: https://stackoverflow.com/a/6693107
void setTitle(struct Application* app, const char *title) {
LPCTSTR text = cstrToLPWSTR(title);
SetWindowText(app->window, text);
delete[] text;
}
void SetTitle(struct Application* app, const char *title) {
ON_MAIN_THREAD(
setTitle(app, title);
);
}
void fullscreen(struct Application* app) {
// Ensure we aren't in fullscreen
if (app->isFullscreen) return;
app->isFullscreen = true;
app->previousWindowStyle = GetWindowLong(app->window, GWL_STYLE);
MONITORINFO mi = { sizeof(mi) };
if (GetWindowPlacement(app->window, &(app->previousPlacement)) && GetMonitorInfo(MonitorFromWindow(app->window, MONITOR_DEFAULTTOPRIMARY), &mi)) {
SetWindowLong(app->window, GWL_STYLE, app->previousWindowStyle & ~WS_OVERLAPPEDWINDOW);
SetWindowPos(app->window, HWND_TOP,
mi.rcMonitor.left,
mi.rcMonitor.top,
mi.rcMonitor.right - mi.rcMonitor.left,
mi.rcMonitor.bottom - mi.rcMonitor.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
}
}
void Fullscreen(struct Application* app) {
ON_MAIN_THREAD(
fullscreen(app);
show(app);
);
}
void unfullscreen(struct Application* app) {
if (app->isFullscreen) {
SetWindowLong(app->window, GWL_STYLE, app->previousWindowStyle);
SetWindowPlacement(app->window, &(app->previousPlacement));
SetWindowPos(app->window, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
app->isFullscreen = false;
}
}
void UnFullscreen(struct Application* app) {
ON_MAIN_THREAD(
unfullscreen(app);
);
}
void DisableFrame(struct Application* app) {
app->frame = 0;
}
// WebviewIsTransparent will make the webview transparent
// revealing the window underneath
void WebviewIsTransparent(struct Application *app) {
app->webviewIsTranparent = true;
}
void WindowBackgroundIsTranslucent(struct Application *app) {
app->windowBackgroundIsTranslucent = true;
}
void OpenDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
}
void SaveDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
}
void MessageDialog(struct Application* app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
}
void DarkModeEnabled(struct Application* app, char *callbackID) {
}
void SetApplicationMenu(struct Application* app, const char *applicationMenuJSON) {
}
void AddTrayMenu(struct Application* app, const char *menuTrayJSON) {
}
void SetTrayMenu(struct Application* app, const char *menuTrayJSON) {
}
void DeleteTrayMenuByID(struct Application* app, const char *id) {
}
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
}
void AddContextMenu(struct Application* app, char *contextMenuJSON) {
}
void UpdateContextMenu(struct Application* app, char *contextMenuJSON) {
}

View File

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

View File

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

View File

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

View File

@@ -90,7 +90,7 @@ void DeleteMenu(Menu *menu) {
// Free nsmenu if we have it
if ( menu->menu != NULL ) {
msg(menu->menu, s("release"));
msg_reg(menu->menu, s("release"));
}
free(menu);
@@ -120,17 +120,17 @@ const char* createMenuClickedMessage(const char *menuItemID, const char *data, e
// Callback for text menu items
void menuItemCallback(id self, SEL cmd, id sender) {
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg_reg(msg_reg(sender, s("representedObject")), s("pointerValue"));
const char *message;
// Update checkbox / radio item
if( callbackData->menuItemType == Checkbox) {
// Toggle state
bool state = msg(callbackData->menuItem, s("state"));
msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
bool state = msg_reg(callbackData->menuItem, s("state"));
msg_int(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
} else if( callbackData->menuItemType == Radio ) {
// Check the menu items' current state
bool selected = msg(callbackData->menuItem, s("state"));
bool selected = (bool)msg_reg(callbackData->menuItem, s("state"));
// If it's already selected, exit early
if (selected) return;
@@ -142,13 +142,13 @@ void menuItemCallback(id self, SEL cmd, id sender) {
id thisMember = members[0];
int count = 0;
while(thisMember != NULL) {
msg(thisMember, s("setState:"), NSControlStateValueOff);
msg_int(thisMember, s("setState:"), NSControlStateValueOff);
count = count + 1;
thisMember = members[count];
}
// check the selected menu item
msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
msg_int(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
}
const char *menuID = callbackData->menuID;
@@ -180,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") ) {
@@ -336,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);
}
@@ -345,61 +348,61 @@ id processAcceleratorKey(const char *key) {
void addSeparator(id menu) {
id item = msg(c("NSMenuItem"), s("separatorItem"));
msg(menu, s("addItem:"), item);
id item = msg_reg(c("NSMenuItem"), s("separatorItem"));
msg_id(menu, s("addItem:"), item);
}
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
id item = ALLOC("NSMenuItem");
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
return item;
}
id createMenuItem(id title, const char *action, const char *key) {
id item = ALLOC("NSMenuItem");
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
msg(item, s("autorelease"));
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
msg_reg(item, s("autorelease"));
return item;
}
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
id item = createMenuItem(str(title), action, key);
msg(item, s("setEnabled:"), !disabled);
msg(menu, s("addItem:"), item);
msg_bool(item, s("setEnabled:"), !disabled);
msg_id(menu, s("addItem:"), item);
return item;
}
id createMenu(id title) {
id menu = ALLOC("NSMenu");
msg(menu, s("initWithTitle:"), title);
msg(menu, s("setAutoenablesItems:"), NO);
msg_id(menu, s("initWithTitle:"), title);
msg_bool(menu, s("setAutoenablesItems:"), NO);
// msg(menu, s("autorelease"));
return menu;
}
void createDefaultAppMenu(id parentMenu) {
// App Menu
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
id appName = msg_reg(msg_reg(c("NSProcessInfo"), s("processInfo")), s("processName"));
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
id appMenu = createMenu(appName);
msg(appMenuItem, s("setSubmenu:"), appMenu);
msg(parentMenu, s("addItem:"), appMenuItem);
msg_id(appMenuItem, s("setSubmenu:"), appMenu);
msg_id(parentMenu, s("addItem:"), appMenuItem);
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
id title = msg_id(str("Hide "), s("stringByAppendingString:"), appName);
id item = createMenuItem(title, "hide:", "h");
msg(appMenu, s("addItem:"), item);
msg_id(appMenu, s("addItem:"), item);
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
addSeparator(appMenu);
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
title = msg_id(str("Quit "), s("stringByAppendingString:"), appName);
item = createMenuItem(title, "terminate:", "q");
msg(appMenu, s("addItem:"), item);
msg_id(appMenu, s("addItem:"), item);
}
void createDefaultEditMenu(id parentMenu) {
@@ -407,8 +410,8 @@ void createDefaultEditMenu(id parentMenu) {
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
id editMenu = createMenu(str("Edit"));
msg(editMenuItem, s("setSubmenu:"), editMenu);
msg(parentMenu, s("addItem:"), editMenuItem);
msg_id(editMenuItem, s("setSubmenu:"), editMenu);
msg_id(parentMenu, s("addItem:"), editMenuItem);
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
@@ -436,7 +439,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
}
if ( STREQ(roleName, "hideothers")) {
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
return;
}
if ( STREQ(roleName, "unhide")) {
@@ -473,7 +476,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
}
if( STREQ(roleName, "pasteandmatchstyle")) {
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
msg_int(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
}
if ( STREQ(roleName, "selectall")) {
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
@@ -508,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++;
@@ -539,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;
}
@@ -565,73 +569,136 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg(parentmenu, s("addItem:"), item);
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
msg_id(item, s("setRepresentedObject:"), wrappedId);
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
msg_bool(item, s("setEnabled:"), !disabled);
msg_reg(item, s("autorelease"));
msg_int(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
msg_id(parentmenu, s("addItem:"), item);
return item;
}
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) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
// 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");
}
// Process image
if( image != NULL && strlen(image) > 0) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
if( templateImage ) {
msg(nsimage, s("template"), YES);
}
msg(item, s("setImage:"), nsimage);
}
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize) {
// Process Menu Item attributes
// 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"));
// Process font
id font;
// 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;
// Check if valid
id fontNameAsNSString = str(fontName);
id fontsOnSystem = msg(msg(c("NSFontManager"), s("sharedFontManager")), s("availableFonts"));
bool valid = msg(fontsOnSystem, s("containsObject:"), fontNameAsNSString);
if( valid ) {
font = msg(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
} else {
bool supportsMonospacedDigitSystemFont = (bool) msg(c("NSFont"), s("respondsToSelector:"), s("monospacedDigitSystemFontOfSize:weight:"));
if( supportsMonospacedDigitSystemFont ) {
font = msg(c("NSFont"), s("monospacedDigitSystemFontOfSize:weight:"), fontSizeFloat, NSFontWeightRegular);
} else {
font = msg(c("NSFont"), s("menuFontOfSize:"), fontSizeFloat);
// Use default font
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
// Check user supplied font
if( STR_HAS_CHARS(fontName) ) {
id fontNameAsNSString = str(fontName);
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
if( userFont != NULL ) {
font = userFont;
}
}
// Add font to dictionary
msg(dictionary, s("setObject:forKey:"), font, lookupStringConstant(str("NSFontAttributeName")));
// Add offset to dictionary
id offset = msg(c("NSNumber"), s("numberWithFloat:"), 0.0);
msg(dictionary, s("setObject:forKey:"), offset, lookupStringConstant(str("NSBaselineOffsetAttributeName")));
id fan = lookupStringConstant(str("NSFontAttributeName"));
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
// RGBA
if( RGBA != NULL && strlen(RGBA) > 0) {
@@ -641,32 +708,75 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
r = g = b = a = 255;
int count = sscanf(RGBA, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
if (count > 0) {
id colour = msg(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(float)r / 255.0,
(float)g / 255.0,
(float)b / 255.0,
(float)a / 255.0);
msg(dictionary, s("setObject:forKey:"), colour, lookupStringConstant(str("NSForegroundColorAttributeName")));
msg(colour, s("release"));
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
(CGFloat)r / (CGFloat)255.0,
(CGFloat)g / (CGFloat)255.0,
(CGFloat)b / (CGFloat)255.0,
(CGFloat)a / (CGFloat)255.0);
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
}
}
id attributedString = ALLOC("NSMutableAttributedString");
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
msg(dictionary, s("release"));
msg_id_id(attributedString, s("initWithString:attributes:"), str(title), dictionary);
msg_reg(attributedString, s("autorelease"));
msg_reg(dictionary, s("autorelease"));
return attributedString;
}
msg(item, s("setAttributedTitle:"), attributedString);
msg(attributedString, s("autorelease"));
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");
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
msg_id(item, s("setRepresentedObject:"), wrappedId);
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(""));
}
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;
}
@@ -687,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");
@@ -726,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 = "";
@@ -746,7 +831,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
bool templateImage = false;
getJSONBool(item, "MacTemplateImage", &templateImage);
int fontSize = 12;
int fontSize = 0;
getJSONInt(item, "FontSize", &fontSize);
// If we have an accelerator
@@ -779,9 +864,36 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
// Get the Type
JsonNode *type = json_find_member(item, "Type");
if( type != NULL ) {
if( STREQ(type->string_, "Text") || STREQ(type->string_, "Submenu")) {
id thisMenuItem = processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate, styledLabel);
if( STREQ(type->string_, "Text")) {
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage);
// 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);
@@ -800,7 +912,6 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
}
}
if ( modifiers != NULL ) {

View File

@@ -105,10 +105,13 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage);
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

File diff suppressed because one or more lines are too long

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;
@@ -50,15 +65,28 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
}
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled, JsonNode *styledLabel) {
// Exit early if NULL
if( trayMenu->label == NULL ) {
return;
}
// Update button label
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setTitle:"), str(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 UpdateTrayIcon(TrayMenu *trayMenu) {
@@ -68,44 +96,57 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
return;
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
// Empty icon means remove it
if( STREMPTY(trayMenu->icon) ) {
// Remove image
msg(statusBarButton, s("setImage:"), NULL);
msg_id(statusBarButton, s("setImage:"), NULL);
return;
}
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( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg(trayMenu->statusbaritem, s("retain"));
id statusBar = msg_reg( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = ((id(*)(id, SEL, CGFloat))objc_msgSend)(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg_reg(trayMenu->statusbaritem, s("retain"));
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
msg_uint(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
// Update the icon if needed
UpdateTrayIcon(trayMenu);
// Update the label if needed
UpdateTrayLabel(trayMenu, trayMenu->label);
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled, trayMenu->styledLabel);
// Update the menu
id menu = GetMenu(trayMenu->menu);
msg(trayMenu->statusbaritem, s("setMenu:"), 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
@@ -127,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;
@@ -147,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);
}
@@ -182,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,14 +13,26 @@ 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);
@@ -28,7 +40,7 @@ void DumpTrayMenu(TrayMenu* trayMenu);
void ShowTrayMenu(TrayMenu* trayMenu);
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
void UpdateTrayIcon(TrayMenu *trayMenu);
void UpdateTrayLabel(TrayMenu *trayMenu, const char*);
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,22 +75,39 @@ 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);
@@ -91,7 +118,19 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
// Check we have this menu
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
UpdateTrayLabel(menu, Label);
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);
}
@@ -105,7 +144,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
// If we don't have a menu, we create one
if ( currentMenu == NULL ) {
// 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);
@@ -116,7 +157,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
// 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);
@@ -125,9 +168,10 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
// 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,6 +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

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,144 @@
// Copyright (C) Microsoft Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef __core_webview2_environment_options_h__
#define __core_webview2_environment_options_h__
#include <objbase.h>
#include <wrl/implements.h>
#include "webview2.h"
#define CORE_WEBVIEW_TARGET_PRODUCT_VERSION L"91.0.864.35"
#define COREWEBVIEW2ENVIRONMENTOPTIONS_STRING_PROPERTY(p) \
public: \
HRESULT STDMETHODCALLTYPE get_##p(LPWSTR* value) override { \
if (!value) \
return E_POINTER; \
*value = m_##p.Copy(); \
if ((*value == nullptr) && (m_##p.Get() != nullptr)) \
return HRESULT_FROM_WIN32(GetLastError()); \
return S_OK; \
} \
HRESULT STDMETHODCALLTYPE put_##p(LPCWSTR value) override { \
LPCWSTR result = m_##p.Set(value); \
if ((result == nullptr) && (value != nullptr)) \
return HRESULT_FROM_WIN32(GetLastError()); \
return S_OK; \
} \
\
protected: \
AutoCoMemString m_##p;
#define COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY(p) \
public: \
HRESULT STDMETHODCALLTYPE get_##p(BOOL* value) override { \
if (!value) \
return E_POINTER; \
*value = m_##p; \
return S_OK; \
} \
HRESULT STDMETHODCALLTYPE put_##p(BOOL value) override { \
m_##p = value; \
return S_OK; \
} \
\
protected: \
BOOL m_##p = FALSE;
// This is a base COM class that implements ICoreWebView2EnvironmentOptions.
template <typename allocate_fn_t,
allocate_fn_t allocate_fn,
typename deallocate_fn_t,
deallocate_fn_t deallocate_fn>
class CoreWebView2EnvironmentOptionsBase
: public Microsoft::WRL::Implements<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
ICoreWebView2EnvironmentOptions> {
public:
CoreWebView2EnvironmentOptionsBase() {
// Initialize the target compatible browser version value to the version of
// the browser binaries corresponding to this version of the SDK.
m_TargetCompatibleBrowserVersion.Set(CORE_WEBVIEW_TARGET_PRODUCT_VERSION);
}
protected:
~CoreWebView2EnvironmentOptionsBase(){};
class AutoCoMemString {
public:
AutoCoMemString() {}
~AutoCoMemString() { Release(); }
void Release() {
if (m_string) {
deallocate_fn(m_string);
m_string = nullptr;
}
}
LPCWSTR Set(LPCWSTR str) {
Release();
if (str) {
m_string = MakeCoMemString(str);
}
return m_string;
}
LPCWSTR Get() { return m_string; }
LPWSTR Copy() {
if (m_string)
return MakeCoMemString(m_string);
return nullptr;
}
protected:
LPWSTR MakeCoMemString(LPCWSTR source) {
const size_t length = wcslen(source);
const size_t bytes = (length + 1) * sizeof(*source);
// Ensure we didn't overflow during our size calculation.
if (bytes <= length) {
return nullptr;
}
wchar_t* result = reinterpret_cast<wchar_t*>(allocate_fn(bytes));
if (result)
memcpy(result, source, bytes);
return result;
}
LPWSTR m_string = nullptr;
};
COREWEBVIEW2ENVIRONMENTOPTIONS_STRING_PROPERTY(AdditionalBrowserArguments)
COREWEBVIEW2ENVIRONMENTOPTIONS_STRING_PROPERTY(Language)
COREWEBVIEW2ENVIRONMENTOPTIONS_STRING_PROPERTY(TargetCompatibleBrowserVersion)
COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY(
AllowSingleSignOnUsingOSPrimaryAccount)
};
template <typename allocate_fn_t,
allocate_fn_t allocate_fn,
typename deallocate_fn_t,
deallocate_fn_t deallocate_fn>
class CoreWebView2EnvironmentOptionsBaseClass
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
CoreWebView2EnvironmentOptionsBase<allocate_fn_t,
allocate_fn,
deallocate_fn_t,
deallocate_fn>> {
public:
CoreWebView2EnvironmentOptionsBaseClass() {}
protected:
~CoreWebView2EnvironmentOptionsBaseClass() override{};
};
typedef CoreWebView2EnvironmentOptionsBaseClass<decltype(&::CoTaskMemAlloc),
::CoTaskMemAlloc,
decltype(&::CoTaskMemFree),
::CoTaskMemFree>
CoreWebView2EnvironmentOptions;
#endif // __core_webview2_environment_options_h__

View File

@@ -0,0 +1,11 @@
# Build
This script will download the given webview2 sdk version and copy out the files necessary for building Wails apps.
## Prerequistes
- nuget
## Usage
`updatesdk.bat <version>`

View File

@@ -0,0 +1 @@
The version of WebView2 used: 1.0.864.35

View File

@@ -0,0 +1,18 @@
@echo off
IF %1.==. GOTO NoVersion
nuget install microsoft.web.webview2 -Version %1 -OutputDirectory . >NUL || goto :eof
echo Downloaded microsoft.web.webview2.%1
set sdk_version=%1
set native_dir="%~dp0\microsoft.web.webview2.%sdk_version%\build\native"
copy "%native_dir%\include\*.h" .. >NUL
copy "%native_dir%\x64\WebView2Loader.dll" "..\x64" >NUL
@rd /S /Q "microsoft.web.webview2.%sdk_version%"
del /s version.txt >nul 2>&1
echo The version of WebView2 SDK used: %sdk_version% > sdkversion.txt
echo SDK updated to %sdk_version%
goto :eof
:NoVersion
echo Please provide a version number, EG: 1.0.664.37
goto :eof

View File

@@ -0,0 +1,23 @@
// +build wv2runtime.browser
package wv2runtime
import (
"fmt"
"github.com/leaanthony/webview2runtime"
)
func doInstallationStrategy(installStatus installationStatus) error {
confirmed, err := webview2runtime.Confirm("This application requires the WebView2 runtime. Press OK to open the download page. Minimum version required: "+minimumRuntimeVersion, "Missing Requirements")
if err != nil {
return err
}
if confirmed {
err = webview2runtime.OpenInstallerDownloadWebpage()
if err != nil {
return err
}
}
return fmt.Errorf("webview2 runtime not installed")
}

View File

@@ -0,0 +1,35 @@
// +build !wv2runtime.error
// +build !wv2runtime.browser
// +build !wv2runtime.embed
package wv2runtime
import (
"fmt"
"github.com/leaanthony/webview2runtime"
)
func doInstallationStrategy(installStatus installationStatus) error {
message := "The WebView2 runtime is required. "
if installStatus == needsUpdating {
message = "The Webview2 runtime needs updating. "
}
message += "Press Ok to download and install. Note: The installer will download silently so please wait."
confirmed, err := webview2runtime.Confirm(message, "Missing Requirements")
if err != nil {
return err
}
if !confirmed {
return fmt.Errorf("webview2 runtime not installed")
}
installedCorrectly, err := webview2runtime.InstallUsingBootstrapper()
if err != nil {
_ = webview2runtime.Error(err.Error(), "Error")
return err
}
if !installedCorrectly {
err = webview2runtime.Error("The runtime failed to install correctly. Please try again.", "Error")
return err
}
return nil
}

View File

@@ -0,0 +1,33 @@
// +build wv2runtime.embed
package wv2runtime
import (
"fmt"
"github.com/leaanthony/webview2runtime"
)
func doInstallationStrategy(installStatus installationStatus) error {
message := "The WebView2 runtime is required. "
if installStatus == needsUpdating {
message = "The Webview2 runtime needs updating. "
}
message += "Press Ok to install."
confirmed, err := webview2runtime.Confirm(message, "Missing Requirements")
if err != nil {
return err
}
if !confirmed {
return fmt.Errorf("webview2 runtime not installed")
}
installedCorrectly, err := webview2runtime.InstallUsingEmbeddedBootstrapper()
if err != nil {
_ = webview2runtime.Error(err.Error(), "Error")
return err
}
if !installedCorrectly {
err = webview2runtime.Error("The runtime failed to install correctly. Please try again.", "Error")
return err
}
return nil
}

View File

@@ -0,0 +1,13 @@
// +build wv2runtime.error
package wv2runtime
import (
"fmt"
"github.com/leaanthony/webview2runtime"
)
func doInstallationStrategy(installStatus installationStatus) error {
_ = webview2runtime.Error("The WebView2 runtime is required to run this application. Please contact your system administrator.", "Error")
return fmt.Errorf("webview2 runtime not installed")
}

View File

@@ -0,0 +1,34 @@
package wv2runtime
import (
"github.com/leaanthony/webview2runtime"
)
const minimumRuntimeVersion string = "91.0.864.48"
type installationStatus int
const (
needsInstalling installationStatus = iota
needsUpdating
installed
)
func Process() error {
installStatus := needsInstalling
installedVersion := webview2runtime.GetInstalledVersion()
if installedVersion != nil {
installStatus = installed
updateRequired, err := installedVersion.IsOlderThan(minimumRuntimeVersion)
if err != nil {
_ = webview2runtime.Error(err.Error(), "Error")
return err
}
// Installed and does not require updating
if !updateRequired {
return nil
}
installStatus = needsUpdating
}
return doInstallationStrategy(installStatus)
}

Binary file not shown.

View File

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

View File

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

View File

@@ -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]
}
@@ -201,10 +201,6 @@ func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
func DirIsEmpty(dir string) (bool, error) {
if !DirExists(dir) {
return false, fmt.Errorf("DirIsEmpty called with a non-existant directory: %s", dir)
}
// CREDIT: https://stackoverflow.com/a/30708914/8325411
f, err := os.Open(dir)
if err != nil {
@@ -243,7 +239,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

@@ -52,21 +52,13 @@ func (a *Asset) AsString() string {
return a.Data
}
// AsCHexData processes the asset data so it may be used by C
func (a *Asset) AsCHexData() string {
// This will be our final string to hexify
dataString := a.Data
func (a *Asset) minifiedData() (string, error) {
switch a.Type {
case AssetTypes.HTML:
// Escape HTML
var re = regexp.MustCompile(`\s{2,}`)
result := re.ReplaceAllString(a.Data, ``)
result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\r\n", "")
result = strings.ReplaceAll(result, "\n", "")
var re = regexp.MustCompile(`[\s]+`)
result := re.ReplaceAllString(a.Data, ` `)
// Inject wailsloader code
result = strings.Replace(result, `</body>`, `<script id='wailsloader'>window.wailsloader = { html: true, runtime: false, userjs: false, usercss: false };var self=document.querySelector('#wailsloader');self.parentNode.removeChild(self);</script></body>`, 1)
@@ -75,7 +67,7 @@ func (a *Asset) AsCHexData() string {
urlString := strings.ReplaceAll(url.String(), "/", "%2f")
// Save Data uRI string
dataString = "data:text/html;charset=utf-8," + urlString
return "data:text/html;charset=utf-8," + urlString, nil
case AssetTypes.CSS:
@@ -91,19 +83,28 @@ func (a *Asset) AsCHexData() string {
result = strings.ReplaceAll(result, `'`, `\'`)
result = strings.ReplaceAll(result, ` {`, `{`)
result = strings.ReplaceAll(result, `: `, `:`)
dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result)
return fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result), nil
case AssetTypes.JS:
m := minify.New()
m.AddFunc("application/javascript", js.Minify)
var err error
dataString, err = m.String("application/javascript", a.Data+";")
result, err := m.String("application/javascript", a.Data+";")
if err != nil {
log.Fatal(err)
return "", err
}
a.Data = dataString
return result, nil
default:
return "", fmt.Errorf("minification for asset type %s not implemented", a.Type)
}
}
// AsCHexData processes the asset data so it may be used by C
func (a *Asset) AsCHexData() string {
dataString, err := a.minifiedData()
if err != nil {
log.Fatal(err)
}
// Get byte data of the string
bytes := *(*[]byte)(unsafe.Pointer(&dataString))

View File

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

View File

@@ -84,7 +84,7 @@ func (a *AssetBundle) processHTML(htmldata string) error {
}
//process the token according to the token type...
if tokenType == html.StartTagToken {
if tokenType == html.StartTagToken || tokenType == html.SelfClosingTagToken {
//get the token
token := tokenizer.Token()
@@ -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

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

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

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

View File

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

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