Compare commits

..

312 Commits

Author SHA1 Message Date
Lea Anthony
9c4221c10b improved 'generate' command for module 2020-11-14 13:52:01 +11:00
Lea Anthony
9b516adb32 Support TS declaration files 2020-11-11 21:27:40 +11:00
Lea Anthony
450775a582 add ts-check to templates 2020-11-11 20:25:05 +11:00
Lea Anthony
d4f0663924 Better global.d.ts 2020-11-11 20:22:35 +11:00
Lea Anthony
c30b003369 Support vscode file generation 2020-11-11 17:32:38 +11:00
Lea Anthony
fbc7d9bccd Updated mod files 2020-11-11 17:32:19 +11:00
Lea Anthony
75d1fa51a2 Support json tags in module generation 2020-11-11 17:31:26 +11:00
Lea Anthony
6d3f4c06f1 JS Object generation. Linting. 2020-11-11 05:58:41 +11:00
Lea Anthony
c3313c90ef Support local functions in bind() 2020-11-10 17:41:32 +11:00
Lea Anthony
6f33b6ede9 Better project generation logic 2020-11-10 11:59:29 +11:00
Lea Anthony
c181e4a9a6 Better Ts support 2020-11-10 07:33:10 +11:00
Lea Anthony
5345c8dd0d remove the spew 2020-11-09 22:06:55 +11:00
Lea Anthony
db38d13693 Refactor of parser 2020-11-09 21:55:35 +11:00
Lea Anthony
4f5d333d74 remove debug info 2020-11-07 07:06:05 +11:00
Lea Anthony
6863a5bf58 Reworked project parser *working* 2020-11-07 07:03:04 +11:00
Lea Anthony
58f96c9da8 Struct comments. External struct literal binding 2020-11-05 16:50:11 +11:00
Lea Anthony
9cbb0b75e2 Slight refactor to improve output 2020-11-05 11:48:42 +11:00
Lea Anthony
8a854f000f Final touches 2020-11-04 16:55:27 +11:00
Lea Anthony
f4e6fd387f Rewritten module generator (TS Only) 2020-11-04 16:11:15 +11:00
Lea Anthony
30527545ca Wrap parsing in Parser 2020-11-02 21:55:00 +11:00
Lea Anthony
6093c513f4 Reimagined parser 2020-11-02 21:38:36 +11:00
Lea Anthony
ae1f771879 Support Array types 2020-11-01 07:29:45 +11:00
Lea Anthony
f046c0c2ee Now parsing actual code 2020-10-31 15:31:44 +11:00
Lea Anthony
608663fd87 Revert "Simplified output"
This reverts commit 15cdf7382b.
2020-10-30 15:55:39 +11:00
Lea Anthony
a2a50a1c78 Tidy up 2020-10-30 15:55:15 +11:00
Lea Anthony
fc4d17b967 Rename file 2020-10-30 15:50:17 +11:00
Lea Anthony
15cdf7382b Simplified output 2020-10-30 15:49:22 +11:00
Lea Anthony
9ced48fcc9 Improved declaration files 2020-10-30 15:44:00 +11:00
Lea Anthony
a6f924b0a6 Improved @returns jsdoc 2020-10-30 15:27:38 +11:00
Lea Anthony
765ae8cd2b Typescript improvements 2020-10-30 15:23:26 +11:00
Lea Anthony
662b14fffb Initial generation of typescript declarations 2020-10-30 15:06:25 +11:00
Lea Anthony
cc50f9f062 Fix typo 2020-10-30 14:38:05 +11:00
Lea Anthony
ee0b17caed Convert to ES6 syntax 2020-10-30 14:28:45 +11:00
Lea Anthony
b4b16f86bf Add JSDoc comments 2020-10-30 14:11:51 +11:00
Lea Anthony
0f41c45de2 Add RelativeToCwd 2020-10-30 11:57:09 +11:00
Lea Anthony
3ea069d312 Generation of index.js 2020-10-30 11:56:58 +11:00
Lea Anthony
bea8aa477f WIP 2020-10-30 10:32:30 +11:00
Lea Anthony
ddb875f788 Dialog WIP 2020-10-24 14:06:57 +11:00
Lea Anthony
9a32852119 Add JS runtime Dialog 2020-10-23 23:56:33 +11:00
Lea Anthony
e795283482 Huge improvement to calls: Now handles objects 2020-10-23 22:24:30 +11:00
Lea Anthony
e6036d31cf Add System to kitchensink 2020-10-23 11:46:12 +11:00
Lea Anthony
5a85a6e4f9 Expose System methods in Go runtime 2020-10-23 11:24:52 +11:00
Lea Anthony
0113fbff4f Update runtime.System to make all methods 2020-10-23 11:19:38 +11:00
Lea Anthony
145656bc43 Improved runtime.System 2020-10-23 10:42:00 +11:00
Lea Anthony
6153c48c86 Update title 2020-10-21 06:52:52 +11:00
Lea Anthony
912c0125e5 Add Browser examples 2020-10-21 06:40:17 +11:00
Lea Anthony
dc2ad3cd3e Remove debug line 2020-10-21 06:02:07 +11:00
Lea Anthony
e3783c5480 Fix browser runtime export 2020-10-20 09:24:03 +11:00
Lea Anthony
440abbe3b6 JS Runtime v1.0.8 2020-10-20 09:10:54 +11:00
Lea Anthony
95369d7c3d Unify Browser runtime 2020-10-20 09:10:09 +11:00
Lea Anthony
9e0023961b Update Browser runtime API 2020-10-20 07:16:56 +11:00
Lea Anthony
307e07b4c8 Finish events page 2020-10-20 06:45:46 +11:00
Lea Anthony
fb88eadb58 Tidy up events runtime 2020-10-20 06:45:21 +11:00
Lea Anthony
6fdf088531 Revert Fatal on JS Error 2020-10-20 06:45:02 +11:00
Lea Anthony
07b6fc0c52 Update runtime in kitchensink 2020-10-18 16:49:08 +11:00
Lea Anthony
1b466090e8 Remove old Event methods 2020-10-18 16:47:48 +11:00
Lea Anthony
c990760f22 Remove old event methods 2020-10-18 16:44:32 +11:00
Lea Anthony
aeb7d857ee Support Emit & Once. Improved On. 2020-10-18 11:49:27 +11:00
Lea Anthony
78f99c2697 update runtime definitions 2020-10-18 11:48:56 +11:00
Lea Anthony
7885718d42 Disable annoying smart quotes 2020-10-18 11:48:38 +11:00
Lea Anthony
288da8c147 Improved error handling? 2020-10-18 11:48:20 +11:00
Lea Anthony
7e31db809a Add Events.On 2020-10-17 21:07:00 +11:00
Lea Anthony
fb0ccfc8e6 WIP Events.On 2020-10-17 15:10:13 +11:00
Lea Anthony
c9bf4e3d48 refactor clilogger 2020-10-17 13:47:13 +11:00
Lea Anthony
2a59272b86 hook in windowWillClose 2020-10-16 14:02:49 +11:00
Lea Anthony
ff5e2862b8 Fix change in logging levels 2020-10-13 07:49:47 +11:00
Lea Anthony
51678afdc7 @wails/runtime v1.0.4 2020-10-13 07:35:36 +11:00
Lea Anthony
3b6a3df03d Update logger constants to fix default values 2020-10-13 07:34:36 +11:00
Lea Anthony
1c97559151 logging: slight refactor 2020-10-13 06:43:38 +11:00
Lea Anthony
082e695c83 Fix log level reactivity.
Misc fixes and tweaks
2020-10-12 23:36:37 +11:00
Lea Anthony
4a5e4d3a5e Move preview for SetLogLevel 2020-10-12 20:55:47 +11:00
Lea Anthony
8988f29cea More Logging updates 2020-10-12 20:54:33 +11:00
Lea Anthony
6150010d17 Runtime defs update.
Slight System refactor
2020-10-12 20:01:37 +11:00
Lea Anthony
c165b97d57 SetLogLevel fully supported 2020-10-12 06:56:51 +11:00
Lea Anthony
39c599d2de Link component 2020-10-12 06:56:32 +11:00
Lea Anthony
be952ba2da Terminal output component 2020-10-12 06:56:22 +11:00
Lea Anthony
5a141d343e Better looking scrollbar 2020-10-12 06:55:42 +11:00
Lea Anthony
7e4ad307aa Fully refactored logging 2020-10-11 15:10:25 +11:00
Lea Anthony
d8bb418851 Runtime refactor 2020-10-11 13:33:55 +11:00
Lea Anthony
f1cd84d0c8 Support dynamic loglevel 2020-10-10 15:06:27 +11:00
Lea Anthony
ba6538da7c Made go runtime package public.
Using loglevel store to keep loglevel in sync
2020-10-10 13:57:32 +11:00
Lea Anthony
afea1cbb4c Support SetLogLevel() at runtime
Refactor of loglevel
2020-10-10 07:31:23 +11:00
Lea Anthony
19fbc884ae Fix number of methods in Log 2020-10-09 15:44:08 +11:00
Lea Anthony
67861e4f70 Updated Logger interface 2020-10-09 15:39:11 +11:00
Lea Anthony
228285f693 Support Print logging 2020-10-09 15:13:45 +11:00
Lea Anthony
9f62a08cd2 Move Info message to Trace 2020-10-09 14:59:12 +11:00
Lea Anthony
d97cd1b75f Runtime v1.0.1 2020-10-09 14:52:07 +11:00
Lea Anthony
5fd8312f63 Support Print in JS runtime 2020-10-09 14:51:26 +11:00
Lea Anthony
90b7d5f519 Support log level 2020-10-09 14:36:42 +11:00
Lea Anthony
93f4549efa Support trace in go runtime 2020-10-09 14:36:28 +11:00
Lea Anthony
b5c8dfac97 Support Trace in kitchensink 2020-10-09 14:36:15 +11:00
Lea Anthony
ee01b7759e Migrate runtime to @wails 2020-10-09 14:35:43 +11:00
Lea Anthony
8dba591cda Add trace to JS runtime 2020-10-09 14:35:30 +11:00
Lea Anthony
302db87bec Debug refactor 2020-10-09 14:01:35 +11:00
Lea Anthony
161ff3b32a Make Ffenestri use logging subsystem. 2020-10-09 12:15:25 +11:00
Lea Anthony
ffdfbb8ae5 Major logging refactor 2020-10-09 11:50:45 +11:00
Lea Anthony
53b54a8e52 Revert logger package 2020-10-09 10:33:11 +11:00
Lea Anthony
0403e0a783 Make logger a public package 2020-10-09 10:30:01 +11:00
Lea Anthony
8a2acacc37 Start Events. List styling moved to global scope. 2020-10-08 21:03:45 +11:00
Lea Anthony
256e84c4d4 Improved CodeBlock. Dark mode to store. 2020-10-08 20:51:14 +11:00
Lea Anthony
c10e8788d8 Add logging to kitchen sink 2020-10-08 07:39:15 +11:00
Lea Anthony
6eb89f61b3 Add Log to Go runtime 2020-10-08 07:39:01 +11:00
Lea Anthony
e3d2ff9ea1 Remove log package 2020-10-08 07:38:47 +11:00
Lea Anthony
665dfa6aee WIP 2020-10-07 21:09:05 +11:00
Lea Anthony
4f7e2128d1 Better drag support 2020-10-07 07:11:00 +11:00
Lea Anthony
8112facb4e Fix right click crash 2020-10-07 06:44:58 +11:00
Lea Anthony
944261b5e4 initial kitchen sink 2020-10-07 00:52:00 +11:00
Lea Anthony
ba528d0534 Debugging 2020-10-07 00:51:22 +11:00
Lea Anthony
a28afe86ce Remove unused event messages 2020-10-04 13:37:29 +11:00
Lea Anthony
5e0026e124 Update runtime to v1.0.3 2020-10-04 12:07:56 +11:00
Lea Anthony
f23ed3c319 Add Store to go runtime 2020-10-04 12:07:42 +11:00
Lea Anthony
6aae2eb1df Support setting app state at startup 2020-10-04 12:07:10 +11:00
Lea Anthony
f4943bc26c Remove generated files 2020-10-04 11:54:05 +11:00
Lea Anthony
1c6578e6ef Use IsDarkMode state store 2020-10-04 11:50:05 +11:00
Lea Anthony
5ef200f21c Refactor system. Add IsDarkMode state store 2020-10-04 11:42:42 +11:00
Lea Anthony
b3822137f7 Refactor store. Add get(). 2020-10-04 11:41:39 +11:00
Lea Anthony
858789f442 Port Sync Store 2020-10-04 07:22:00 +11:00
Lea Anthony
3209b39488 Support System calls in Go Runtime 2020-10-03 20:46:18 +10:00
Lea Anthony
0c9f6edeb6 Add system calls to js runtime 2020-10-03 20:45:37 +10:00
Lea Anthony
aabcef2958 Add System calls to runtime 2020-10-03 20:45:04 +10:00
Lea Anthony
10d68b2676 Small fixes to frontend events 2020-10-03 15:29:34 +10:00
Lea Anthony
e960afe8f6 Support OnMultiple 2020-10-03 15:10:38 +10:00
Lea Anthony
ae677ce9db Misc fixes for events 2020-10-03 15:03:14 +10:00
Lea Anthony
7f9c59a021 Support theme mode change event 2020-10-02 15:12:56 +10:00
Lea Anthony
d8fdc96899 Add IsDarkMode
Updated runtime test
2020-10-02 11:41:46 +10:00
Lea Anthony
c3280e8b60 Update runtime test 2020-10-02 07:45:41 +10:00
Lea Anthony
26a1f78d56 Support Translucent Window Background 2020-10-02 07:45:22 +10:00
Lea Anthony
ee9c98c515 Refactor Part 2 2020-10-02 07:11:24 +10:00
Lea Anthony
29ec06fb0a Refactor part 1 2020-10-02 06:52:08 +10:00
Lea Anthony
c1155e255b Add default appearance 2020-09-30 20:03:08 +10:00
Lea Anthony
4e39566118 Rename vibrancy to appearance 2020-09-30 19:54:41 +10:00
Lea Anthony
3f3094f0aa Support vibrancy and transparency for webview
Options Colour -> RGBA
2020-09-30 07:25:15 +10:00
Lea Anthony
84730d2f4d Tidy up 2020-09-29 20:36:38 +10:00
Lea Anthony
b8bb891275 Frameless is calculated for Mac 2020-09-29 20:34:54 +10:00
Lea Anthony
7bcb5be1a5 Update tests 2020-09-29 07:44:02 +10:00
Lea Anthony
081c842149 Support window dragging 2020-09-29 07:33:16 +10:00
Lea Anthony
6bdcec8105 WIP: Basics of window drag 2020-09-28 22:08:53 +10:00
Lea Anthony
3c7937bff9 Tidy Up 2020-09-28 21:22:11 +10:00
Lea Anthony
d7f832c00e Support SaveDialog 2020-09-28 21:13:57 +10:00
Lea Anthony
8cd39f6a9a Support default directory 2020-09-27 20:40:36 +10:00
Lea Anthony
ac27137e5a Filter support 2020-09-27 20:26:16 +10:00
Lea Anthony
762632d55a Add comments 2020-09-27 15:15:25 +10:00
Lea Anthony
48c17dac87 Support all dialog properties 2020-09-27 15:05:55 +10:00
Lea Anthony
8666935caf Support multiple files in dialog 2020-09-27 14:49:38 +10:00
Lea Anthony
0ec6707263 Support selecting files+dirs 2020-09-27 14:46:30 +10:00
Lea Anthony
d4224772b4 Initial support for OpenDialog 2020-09-27 14:32:08 +10:00
Lea Anthony
cd99376da9 Use options struct for dialogs 2020-09-27 10:17:30 +10:00
Lea Anthony
02fd4ec477 Revert changes to v1 2020-09-27 07:58:30 +10:00
Lea Anthony
3b851e9a22 change service bus topics for dialogs 2020-09-26 16:21:18 +10:00
Lea Anthony
5ce5e129cf Remove old dialog code 2020-09-26 16:16:25 +10:00
Lea Anthony
9b0f58ddf5 Support OpenDialog 2020-09-26 16:08:55 +10:00
Lea Anthony
bed5619d4e WIP: Support multiple value return 2020-09-25 07:09:55 +10:00
Lea Anthony
69c4e6ea28 Min/Max size supported 2020-09-24 21:43:37 +10:00
Lea Anthony
ea7b593693 Fix minmax app 2020-09-24 21:29:26 +10:00
Lea Anthony
7ac833e396 Sample titlebars 2020-09-24 06:45:34 +10:00
Lea Anthony
a5e909337e Support TitleBar Default
Fixed merging defaults
2020-09-24 06:39:08 +10:00
Lea Anthony
0c120eccc9 Support HiddenInset Titlebar 2020-09-24 06:22:49 +10:00
Lea Anthony
e6addafcdd Support hidden titlebar 2020-09-24 06:19:00 +10:00
Lea Anthony
ef11f45df8 Refactored mac titlebar options 2020-09-24 05:57:32 +10:00
Lea Anthony
72fc2204b4 Moved options to own package 2020-09-24 05:46:39 +10:00
Lea Anthony
15c08ef425 Support runtime colour change 2020-09-23 08:48:11 +10:00
Lea Anthony
52bb397105 Support colour 2020-09-22 20:46:36 +10:00
Lea Anthony
5572fccaf6 Add Toolbar support for Mac 2020-09-22 17:48:39 +10:00
Lea Anthony
65d591e2a6 Support more mac window options 2020-09-22 17:19:35 +10:00
Lea Anthony
4bf59301e5 Add HideTitleBar 2020-09-21 17:13:23 +10:00
Lea Anthony
629e8f73f4 Add Mac Application Options 2020-09-21 17:07:10 +10:00
Lea Anthony
eb68ba5120 fix setsize and setposition 2020-09-19 23:19:34 +10:00
Lea Anthony
a8bff7868b Got get frame working 2020-09-19 07:40:45 +10:00
Lea Anthony
ae04b4fcc0 WIP [bugged] 2020-09-18 21:04:23 +10:00
Lea Anthony
5eb91dd3fa More refactoring 2020-09-16 21:23:35 +10:00
Lea Anthony
3ad537fdbb Tidy up 2020-09-16 21:14:18 +10:00
Lea Anthony
2c570bb4f6 Support tons of stuff! 2020-09-16 21:11:17 +10:00
Lea Anthony
461f3aec0a Get it compiling again! 2020-09-16 20:06:32 +10:00
Lea Anthony
fd47122e39 Only show window when rendered 2020-09-15 19:57:34 -05:00
Lea Anthony
8de013f192 vscode stuff 2020-09-15 19:57:34 -05:00
Lea Anthony
1dd3a602d7 Support Un/Fullscreen 2020-09-15 19:57:34 -05:00
Lea Anthony
a84a49a13f ignore test builds 2020-09-15 19:57:34 -05:00
Lea Anthony
64a6a69bbd Update packages 2020-09-15 19:57:33 -05:00
Lea Anthony
d75b9f26f1 Callback hooks for MOAE 2020-09-15 19:57:33 -05:00
Lea Anthony
10cb7f830f Fix logger 2020-09-15 19:57:33 -05:00
Lea Anthony
dd3e6de9b2 Add Center + refactor prefs 2020-09-15 19:57:33 -05:00
Lea Anthony
d42b84abc1 Callbacks working 2020-09-15 19:57:33 -05:00
Lea Anthony
b6c649041b Support sending messages to the backend 2020-09-15 19:57:33 -05:00
Lea Anthony
93ec65be6a More feature flag removal 2020-09-15 19:57:30 -05:00
Lea Anthony
bfa8929c47 Remove feature flag code 2020-09-15 19:55:52 -05:00
Lea Anthony
360713c803 lint 2020-09-15 19:55:51 -05:00
Lea Anthony
26ce682824 Ignore favicon for desktop 2020-09-15 19:55:51 -05:00
Lea Anthony
65b546c0f9 Evaluation working correctly. Still WIP 2020-09-15 19:55:51 -05:00
Travis McLane
31494bba22 add darwin platform for server 2020-09-15 19:55:51 -05:00
Lea Anthony
f25abb0b26 Semi runs 2020-09-15 19:55:51 -05:00
Lea Anthony
e831bc75c6 Stubs in place to compile 2020-09-15 19:55:51 -05:00
Lea Anthony
852bbd148c Get it compiling 2020-09-15 19:55:50 -05:00
Travis McLane
c158fd369a Merge commit 'a213e8bcd1d8e4e5c764978879d875d2d55dd400' as 'v2' 2020-09-15 19:52:54 -05:00
Travis McLane
a213e8bcd1 Squashed 'v2/' content from commit 72ef153
git-subtree-dir: v2
git-subtree-split: 72ef15359e36e42b18d9407f74c762f83eb9a099
2020-09-15 19:52:54 -05:00
Lea Anthony
8a6c44963d Upgrade packages for js runtime 2020-09-02 08:51:21 +10:00
Lea Anthony
d31c5522e9 fix: revert location of wails config 2020-09-02 07:07:12 +10:00
Lea Anthony
6e5b2ce871 Update Contributors 2020-09-01 20:44:50 +10:00
Tim Kipp
c2c9c331cd refactor: attempt to refactor spacing issues detected by houndci 2020-09-01 20:43:20 +10:00
Tim Kipp
9d5280d4d6 feat(templates): updated react template to latest CRA implementation 2020-09-01 20:43:20 +10:00
Lea Anthony
01a1288364 Merge branch 'develop' of github.com:wailsapp/wails into develop 2020-08-23 09:50:58 +10:00
Lea Anthony
5cd34e07b9 Release v1.7.2-pre3 2020-08-23 09:50:16 +10:00
Lea Anthony
2290f36939 Allow customising of info.plist 2020-08-23 09:49:54 +10:00
Lea Anthony
670986df5b Lintastic update 2020-08-21 23:44:22 +10:00
Lea Anthony
086cd02cd4 Lint fix 2020-08-21 23:44:22 +10:00
Lea Anthony
d8c8dd57fa Add artem to Contributors 2020-08-21 23:44:22 +10:00
Lea Anthony
2d158d449a Use double encoding for notify data 2020-08-21 23:44:22 +10:00
Lea Anthony
04577c242b Updated contributors 2020-08-04 05:06:50 +10:00
Zámbó Levente
ac0d4493d3 fix(bridge): fixed long message emit issue #469 2020-08-04 05:06:43 +10:00
Lea Anthony
b123156331 v1.7.2-pre2 2020-07-27 21:13:46 +10:00
Lea Anthony
23468ce7c7 Don't clean up windows files 2020-07-27 21:10:32 +10:00
Lea Anthony
7b6bb5e259 Update Contributors 2020-07-16 19:41:41 +10:00
konez2k
77bd34d601 refactor: use proper user config directory
replace os.UserHomeDir with os.UserConfigDir which manages properly the config folder based on platform.
2020-07-13 20:00:59 +10:00
konez2k
d750077a90 Update minimum go version to 1.13 2020-07-13 20:00:59 +10:00
konez2k
71df64087b refactor homedir to stdlib os.UserHomeDir (go1.12)
- remove mitchellh/go-homedir dependency
2020-07-13 20:00:59 +10:00
Lea Anthony
5565f8ba94 v1.7.1 2020-07-03 20:05:01 +10:00
Lea Anthony
3bc91f20c5 Fix contributors 2020-07-03 13:54:02 +10:00
Lea Anthony
0a15cbaa1d v1.7.0-pre2 2020-07-03 13:52:51 +10:00
Lea Anthony
c650671265 fix: vanilla template for windows 2020-07-03 13:48:27 +10:00
Lea Anthony
7106c338df fix: windows icon name 2020-07-03 06:05:47 +10:00
Lea Anthony
be2cef4a63 free filter memory 2020-06-29 06:41:24 +10:00
Lea Anthony
ee93f3c1e6 v1.7.0-pre1 2020-06-22 20:14:10 +10:00
Lea Anthony
b9ffe53732 Add ldflags option to build 2020-06-22 20:13:15 +10:00
Lea Anthony
bbc16fe03a Lint fixes 2020-06-19 09:38:01 +10:00
Lea Anthony
bdfc3ca631 Release v1.6.0 2020-06-19 09:26:19 +10:00
Lea Anthony
490d66cf77 Update Contributors 2020-06-19 09:11:34 +10:00
Lea Anthony
bce686d779 lint fixes 2020-06-19 08:52:44 +10:00
Lea Anthony
c0b0ef0200 Initial implementation of vanilla template 2020-06-19 08:52:44 +10:00
Florian
8e869baed7 fix: trim whitespaces from filters 2020-06-18 20:25:40 +10:00
Florian Didron
7522428d5c feat: add dialog filter for Cocoa 2020-06-18 20:25:40 +10:00
Florian Didron
bc570999e8 feat: file dialog filters for win32 2020-06-18 20:25:40 +10:00
Florian
393a4fceb2 feat: adds file filtering to gtk select/save dialogs 2020-06-16 18:57:25 +10:00
Lea Anthony
da20bcc8d2 v1.6.0-pre6 2020-06-06 14:42:59 +10:00
Lea Anthony
b091baa16f fix: Project options getting overwritten 2020-06-06 14:42:59 +10:00
Lea Anthony
ecaaafa9d9 v1.6.0-pre5 2020-05-27 12:49:14 +10:00
Lea Anthony
0009da9585 fix: ignore case of linux distro id 2020-05-27 12:48:28 +10:00
Lea Anthony
6235e83677 v1.6.0-pre4 2020-05-27 10:38:40 +10:00
Lea Anthony
9f93e7d979 fix default libs identifier for solus 2020-05-27 10:35:42 +10:00
Lea Anthony
949bc40317 Fix package detection for solus 2020-05-27 10:35:42 +10:00
Lea Anthony
81777f29d8 update package names 2020-05-27 10:35:42 +10:00
Lea Anthony
5d35dd3105 Initial support for solus 2020-05-27 10:35:42 +10:00
Lea Anthony
ad034d3950 fix: popos ID 2020-05-27 08:28:23 +10:00
Lea Anthony
98337df92d Support Pop!_OS
Added link to guide in request process
2020-05-27 08:28:23 +10:00
Dario Emerson
9cc417cf04 Add myself as contributor 2020-05-25 20:48:48 +10:00
Dario Emerson
030b954971 Correctly add icon to window on Windows 2020-05-25 20:48:48 +10:00
Dario Emerson
7a3ab27977 Changed icon scaling algorithm to CatmullRom 2020-05-25 17:32:49 +10:00
Dario Emerson
0e6265a9d7 Moved ico file writing later 2020-05-25 17:32:49 +10:00
Dario Emerson
b003a080b0 Improved Windows .ico generation 2020-05-25 17:32:49 +10:00
Lea Anthony
376ba743f4 Release v1.6.0-pre3 2020-05-22 07:10:58 +10:00
Lea Anthony
aa8ffff68d fix! 2020-05-21 22:05:17 +01:00
Lea Anthony
613a44af5e fix: icon size 2020-05-21 22:05:17 +01:00
Lea Anthony
2e15c4e045 intial support for .ico generation 2020-05-21 22:05:17 +01:00
Lea Anthony
421c13805d Merge branch 'dedo1911-408-default-serve-platform' into develop 2020-05-22 07:02:55 +10:00
Dario Emerson
cc204ab1f7 Fix missing import 2020-05-21 01:35:34 +02:00
Dario Emerson
a94a1a9fcb Merge branch 'develop' into 408-default-serve-platform 2020-05-21 01:33:22 +02:00
Dario Emerson
e860bd06ec Default Platform for serve command 2020-05-20 14:36:52 +02:00
Lea Anthony
6da02e6b44 v1.6.0-pre2 2020-05-17 20:17:36 +10:00
Lea Anthony
7cd78df1cd Support ArchLabs 2020-05-17 20:17:00 +10:00
Lea Anthony
bcecd854bc v1.6.0-pre1 2020-05-16 07:21:26 +10:00
Lea Anthony
6339f230f9 Fix CWE-126 2020-05-15 22:18:50 +01:00
Lea Anthony
131fd973cd lint fix 2020-05-15 22:18:50 +01:00
Lea Anthony
f1d16a03ec Disable F5 key on windows 2020-05-15 22:18:50 +01:00
Lea Anthony
cc99dcce80 Update contributors 2020-05-07 06:30:53 +10:00
Lea Anthony
a8ecc1e872 Merge branch 'develop' 2020-04-26 16:10:56 +10:00
Lea Anthony
6fc419ab48 Version v1.5.0 2020-04-26 16:00:39 +10:00
Lea Anthony
400bb37c03 Updated contributors 2020-04-26 14:39:11 +10:00
Lea Anthony
10fc7d762b fixes to OSX cross compile 2020-04-26 14:29:46 +10:00
Lea Anthony
61b9e50b8e Rapply -o suffix handling 2020-04-26 14:29:46 +10:00
Travis McLane
47916216de fixup 2020-04-26 14:29:46 +10:00
Travis McLane
a2b066decb wip: attempting to remove '.exe' from binary name 2020-04-26 14:29:46 +10:00
Travis McLane
8d1238289f correct issues with exe naming on windows 2020-04-26 14:29:46 +10:00
Travis McLane
b232b608fa remove extraneous println 2020-04-26 14:29:46 +10:00
Travis McLane
3b034ceadb correct issue with wails serve 2020-04-26 14:29:46 +10:00
Travis McLane
75be8f698a handle windows resources + cleanup when done
Moved the file cleanup to a CleanWindows function which simply deletes
all the files a Windows compilation generates for the icon resources.
Windows resource files MUST be in the same directory as the `go` source or
it will not be included in the resulting binary.
package.PackageWindows should be renamed to `GenerateWindowsResources`
since it doesn't actually package anything but rather generates the
various resources that are used during compilation.
2020-04-26 14:29:46 +10:00
Travis McLane
a78acbb247 continueOnError for RemoveFiles
Allow the purge to continue even if one of the files fails to be removed.
2020-04-26 14:29:46 +10:00
Travis McLane
6e29d1b087 docker process updates
- inform user we are starting to pull image and it may take a while
- inform user the target platform being built using docker image
2020-04-26 14:29:46 +10:00
Travis McLane
8171b644ca use 'build' dir for windows resources + cleanup 2020-04-26 14:29:46 +10:00
Travis McLane
a691ef0580 ensure OS X App is built inside 'build' dir 2020-04-26 14:29:46 +10:00
Travis McLane
151b4bff06 generate mewn files + cleanup 2020-04-26 14:29:46 +10:00
Travis McLane
fb093c58d2 cleanup: build native vs docker
- BuildDocker: uses wailsapp/xgo:latest docker container to build
- BuildNative: builds on the host for the host platform
2020-04-26 14:29:46 +10:00
Travis McLane
b68c69f4eb run tests first to fail fast 2020-04-26 14:29:46 +10:00
Travis McLane
8405ce37f9 remove "'" around ldflags
The single quote is needed for running the command in the shell directly.
When run inside the Go command 'os/exec' it is not removed and is
preventing the flags from being processed correctly inside the Docker image.
2020-04-26 14:29:46 +10:00
Travis McLane
76edc7976e use userid=1000 if user.Current() is not an integer 2020-04-26 14:29:46 +10:00
Travis McLane
2ab8fc37ab remove linux/i386 (buggy) 2020-04-26 14:29:46 +10:00
Travis McLane
c66e9cba5a cross-platform builds w/ wailsapp/xgo:latest
- use docker directly instead of `xgo` to call docker.
- use custom xgo docker image
2020-04-26 14:29:46 +10:00
Travis McLane
b33794c883 use wailsapp/xgo:latest for windres call 2020-04-26 14:29:46 +10:00
Travis McLane
5d719685d3 cross compilation command + help 2020-04-26 14:29:46 +10:00
Travis McLane
5b2e5b1d85 chore: correct build tag on app_other 2020-04-26 14:29:46 +10:00
Travis McLane
773bad45f6 build.sh improvements
- run `go test ./...` and STOP on error
- run `go build` and STOP on error
- run `go install` and STOP on error
2020-04-26 14:29:46 +10:00
Travis McLane
404cd7d14e linux packaging (noop) + list platforms 2020-04-26 14:29:46 +10:00
Travis McLane
ea703acfed generated files 2020-04-26 14:29:46 +10:00
konez2k
4750ee5d22 update image version 2020-04-26 14:29:46 +10:00
konez2k
1bd9408e9e add docker version pinning 2020-04-26 14:29:46 +10:00
konez2k
34202f1a2f add docker webkit/gtk support 2020-04-26 14:29:46 +10:00
konez2k
9d6d77f768 initial docker support 2020-04-26 14:29:46 +10:00
konez2k
fbff822ea9 check if xgo and docker are installed 2020-04-26 14:29:46 +10:00
konez2k
da28683ddc create build directory if not exists 2020-04-26 14:29:46 +10:00
konez2k
b7f0155c3b add cross-platform build and packaging 2020-04-26 14:29:46 +10:00
konez2k
f220e0e5bd add FindFile to FSHelper 2020-04-26 14:29:46 +10:00
konez2k
b5bad14124 add arch to project options 2020-04-26 14:29:46 +10:00
konez2k
0f36ee7030 add cross-compile flag to build command 2020-04-26 14:29:46 +10:00
konez2k
58761ccff4 embed assets internally using mewn 2020-04-26 14:29:46 +10:00
Lea Anthony
059cbbfd9a doc: updated contributors 2020-04-26 07:00:05 +10:00
Jarek Cora
339b0ecbaf feat: added openSUSE support 2020-04-26 06:56:53 +10:00
Travis McLane
615cc55b31 bump channel capacity to 100 2020-03-26 05:21:35 +11:00
Travis McLane
577b59aa89 writer channel to protect websocket 2020-03-26 05:21:35 +11:00
Travis McLane
45a507673e custom logger for Bridge.session 2020-03-26 05:21:35 +11:00
Lea Anthony
eb8a1f2303 Add Reuben to Contributors 2020-03-24 22:04:48 +11:00
Reuben
566914a9c2 fix RunCommandArray bug where if exec.LookPath returns an error program is an empty string in the log. 2020-03-24 22:02:56 +11:00
Lea Anthony
0866a633a3 fix optional callback data in definition 2020-03-21 14:32:17 +11:00
konez2k
0e7981cf87 Macos notarize fix (#362)
* Develop (#343)

* Support Distribution 'ArcoLinux' #310 (#312)

* Support Distribution 'ArcoLinux' #310

* Vuetify2 support (resurrected from git@github.com:MichaelHipp/wails.git) (#315)

* Initial create of vuetify2-basic folder

* Change template descr of vuetify-basic to say Vuetify 1.5

* Get vuetify2 template installing vuetify v2.0 (but with styling probs)

* Update App.vue, HelloWorld.vue for Vuetify v2

* Remove babel-polyfill, add mdi/font

* fix: codacy corrections

* fix: babel -> core-js, regenerator-runtime

Co-authored-by: Michael Hipp <michael@redmule.com>
Co-authored-by: Lea Anthony <lea.anthony@gmail.com>

* Update Contributors

* v1.0.2-pre1

* [313-remote-conn] allow remote connections to the websocket bridge (#314)

* [313-remote-conn] feat: compute wsURL based on window.location

* [313-remote-conn] feat: allow any host to connect to vue server

removing the 'host: "localhost"' specification causes the development
server to listen on all interfaces.

* [313-remote-conn] feat: allow any host to connect to angular dev server

* test: reinject tabs

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>

* fix: disable host check for vuetify 2 template

* v1.0.2-pre2

* fix: shutdown ipcmanager

* use channel to trigger shutdown

* load linuxdb from relative path

* Feat manjaro arm & deepin (#324)

* feat: new distros: manjaroARM & Deepin

* v1.0.2-pre3

* [326-platform-raspbian] feat: implement raspbian support (#327)

* fix: emit arguments (#306)

* v1.0.2-pre4 Raspbarian support

* Initial support for Typescript decl file (#330)

* v1.0.2-pre5

* revert to Go 1.12

* New CI (#331)

* prepare

* new CI/github actions

* Rename later-pre.yml to latest-pre.yml

* Update latest-pre.yml

* Update README.md

* Ensure version in go.mod is up to date (#339)

* release v1.0.2-pre6

* Fix typescript generation

* Release v1.0.2-pre7

* 316-multi-bridge-conn (#317)

* [316-multi-bridge-conn] feat: use callback func for bridge response

* [316-multi-bridge-conn] feat: implement multiple session support

* split client handling portion into 'session'
* keep track of sessions by remote address (ip & port)
* notify each of the sessions anytime an event comes across the bus

* [316-multi-bridge-conn] chore: move bridge files to package

* [316-multi-bridge-conn] chore: remove deprecated Callback function

The Callback function is no longer needed for the operation of
the frontend callback since the ipc.Dispatch function now requires
a callback function to be provided as an argument.
This function can be a private function since it is passed by reference.

* [316-multi-bridge-conn] chore: make webview.Callback private

* [316-multi-bridge-conn] chore: remove unused injectCSS function

I believe a slightly better method of doing this might need to be devised
if it is needed in the future. I presume it should collect the values
into a cache and then inject it into each sesssion as it appears.

* [316-multi-bridge-conn] ensure wails:ready event is emitted

Event is only emitted for the first session created from the Bridge.

* [316-multi-bridge-conn] emit events for session lifecycle

Emit an event for each session started and ended.

* [316-multi-bridge-conn] fix: session handling fixes

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>

* Release v1.0.2-pre8

* Release v1.0.2

Co-authored-by: Byron <ktc@protonmail.com>
Co-authored-by: Travis McLane <tmclane@gmail.com>
Co-authored-by: Michael Hipp <michael@redmule.com>

* Macos notarize fix

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
Co-authored-by: Byron <ktc@protonmail.com>
Co-authored-by: Travis McLane <tmclane@gmail.com>
Co-authored-by: Michael Hipp <michael@redmule.com>
2020-03-17 23:29:43 +11:00
Lea Anthony
ce3bd8f56f Update dependencies (#354)
* update vuetify2 dependencies

* update eslint

* update vuetify1 deps

* update vuebasic deps

* update react deps

* bump version
2020-03-04 06:39:34 +10:00
Lea Anthony
79188c503f Develop (#343)
* Support Distribution 'ArcoLinux' #310 (#312)

* Support Distribution 'ArcoLinux' #310

* Vuetify2 support (resurrected from git@github.com:MichaelHipp/wails.git) (#315)

* Initial create of vuetify2-basic folder

* Change template descr of vuetify-basic to say Vuetify 1.5

* Get vuetify2 template installing vuetify v2.0 (but with styling probs)

* Update App.vue, HelloWorld.vue for Vuetify v2

* Remove babel-polyfill, add mdi/font

* fix: codacy corrections

* fix: babel -> core-js, regenerator-runtime

Co-authored-by: Michael Hipp <michael@redmule.com>
Co-authored-by: Lea Anthony <lea.anthony@gmail.com>

* Update Contributors

* v1.0.2-pre1

* [313-remote-conn] allow remote connections to the websocket bridge (#314)

* [313-remote-conn] feat: compute wsURL based on window.location

* [313-remote-conn] feat: allow any host to connect to vue server

removing the 'host: "localhost"' specification causes the development
server to listen on all interfaces.

* [313-remote-conn] feat: allow any host to connect to angular dev server

* test: reinject tabs

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>

* fix: disable host check for vuetify 2 template

* v1.0.2-pre2

* fix: shutdown ipcmanager

* use channel to trigger shutdown

* load linuxdb from relative path

* Feat manjaro arm & deepin (#324)

* feat: new distros: manjaroARM & Deepin

* v1.0.2-pre3

* [326-platform-raspbian] feat: implement raspbian support (#327)

* fix: emit arguments (#306)

* v1.0.2-pre4 Raspbarian support

* Initial support for Typescript decl file (#330)

* v1.0.2-pre5

* revert to Go 1.12

* New CI (#331)

* prepare

* new CI/github actions

* Rename later-pre.yml to latest-pre.yml

* Update latest-pre.yml

* Update README.md

* Ensure version in go.mod is up to date (#339)

* release v1.0.2-pre6

* Fix typescript generation

* Release v1.0.2-pre7

* 316-multi-bridge-conn (#317)

* [316-multi-bridge-conn] feat: use callback func for bridge response

* [316-multi-bridge-conn] feat: implement multiple session support

* split client handling portion into 'session'
* keep track of sessions by remote address (ip & port)
* notify each of the sessions anytime an event comes across the bus

* [316-multi-bridge-conn] chore: move bridge files to package

* [316-multi-bridge-conn] chore: remove deprecated Callback function

The Callback function is no longer needed for the operation of
the frontend callback since the ipc.Dispatch function now requires
a callback function to be provided as an argument.
This function can be a private function since it is passed by reference.

* [316-multi-bridge-conn] chore: make webview.Callback private

* [316-multi-bridge-conn] chore: remove unused injectCSS function

I believe a slightly better method of doing this might need to be devised
if it is needed in the future. I presume it should collect the values
into a cache and then inject it into each sesssion as it appears.

* [316-multi-bridge-conn] ensure wails:ready event is emitted

Event is only emitted for the first session created from the Bridge.

* [316-multi-bridge-conn] emit events for session lifecycle

Emit an event for each session started and ended.

* [316-multi-bridge-conn] fix: session handling fixes

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>

* Release v1.0.2-pre8

* Release v1.0.2

Co-authored-by: Byron <ktc@protonmail.com>
Co-authored-by: Travis McLane <tmclane@gmail.com>
Co-authored-by: Michael Hipp <michael@redmule.com>
2020-02-08 09:58:16 +11:00
410 changed files with 41155 additions and 4550 deletions

View File

@@ -13,10 +13,10 @@ jobs:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Set up Go 1.12
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.12
go-version: 1.13
id: go
- name: Check out code into the Go module directory
@@ -27,6 +27,6 @@ jobs:
go get -v -d ./...
- name: Build
run: go build -v ./cmd/wails
- name: Test
run: ./wails version

View File

@@ -13,10 +13,10 @@ jobs:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Set up Go 1.12
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.12
go-version: 1.13
id: go
- name: Check out code into the Go module directory
@@ -27,6 +27,6 @@ jobs:
go get -v -d ./...
- name: Build
run: go build -v ./cmd/wails
- name: Test
run: ./wails version

View File

@@ -4,7 +4,7 @@ on:
branches:
- master
tags:
- '!**pre**'
- '!**pre**'
jobs:
build:
@@ -15,10 +15,10 @@ jobs:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Set up Go 1.12
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.12
go-version: 1.13
id: go
- name: Check out code into the Go module directory
@@ -29,6 +29,6 @@ jobs:
go get -v -d ./...
- name: Build
run: go build -v ./cmd/wails
- name: Test
run: ./wails version

11
.gitignore vendored
View File

@@ -16,4 +16,13 @@ examples/**/example*
cmd/wails/wails
.DS_Store
tmp
node_modules/
node_modules/
package.json.md5
v2/test/**/frontend/dist
v2/test/**/build/
v2/test/frameless/icon.png
v2/test/hidden/icon.png
v2/internal/ffenestri/runtime.c
v2/internal/runtime/assets/desktop.js
v2/test/kitchensink/frontend/public/bundle.*
v2/pkg/parser/testproject/frontend/wails

View File

@@ -15,11 +15,20 @@ Wails is what it is because of the time and effort given by these great people.
* [admin_3.exe](https://github.com/bh90210)
* [iceleo-com](https://github.com/iceleo-com)
* [fallendusk](https://github.com/fallendusk)
* [Florian Didran](https://github.com/fdidron)
* [Nikolai Zimmermann](https://github.com/Chronophylos)
* [Toyam Cox](https://github.com/Vaelatern)
* [Robin Eklind](https://github.com/mewmew)
* [Kris Raney](https://github.com/kraney)
* [Jack Mordaunt](https://github.com/JackMordaunt)
* [Michael Hipp](https://github.com/MichaelHipp)
* [Travis McLane](https://github.com/tmclane)
* [Travis McLane](https://github.com/tmclane)
* [Reuben Thomas-Davis](https://github.com/Rested)
* [Jarek](https://github.com/Jarek-SRT)
* [Konez2k](https://github.com/konez2k)
* [msms](https://github.com/sayuthisobri)
* [dedo1911](https://github.com/dedo1911)
* [Florian Didron](https://github.com/fdidron)
* [Christopher Murphy](https://github.com/Splode)
* [Zámbó, Levente](https://github.com/Lyimmi)
* [artem](https://github.com/Unix4ever)
* [Tim Kipp](https://github.com/timkippdev)

View File

@@ -36,7 +36,7 @@ The official docs can be found at [https://wails.app](https://wails.app).
Wails uses cgo to bind to the native rendering engines so a number of platform dependent libraries are needed as well as an installation of Go. The basic requirements are:
- Go 1.12
- Go 1.13
- npm
### MacOS
@@ -55,9 +55,9 @@ _Debian: 8, 9, 10_
_Ubuntu: 16.04, 18.04, 19.04_
_Also succesfully tested on: Zorin 15, Parrot 4.7, Linuxmint 19, Elementary 5, Kali, Neon_
_Also succesfully tested on: Zorin 15, Parrot 4.7, Linuxmint 19, Elementary 5, Kali, Neon_, Pop!_OS
#### Arch Linux
#### Arch Linux / ArchLabs
`sudo pacman -S webkit2gtk gtk3`

View File

@@ -1,4 +1,4 @@
// +build +linux +darwin !windows
// +build linux darwin !windows
package wails

File diff suppressed because one or more lines are too long

View File

@@ -12,6 +12,7 @@ import (
"path"
"path/filepath"
"runtime"
"strings"
"github.com/leaanthony/slicer"
)
@@ -47,6 +48,22 @@ func (fs *FSHelper) FileExists(path string) bool {
return fi.Mode().IsRegular()
}
// FindFile returns the first occurrence of match inside path.
func (fs *FSHelper) FindFile(path, match string) (string, error) {
files, err := ioutil.ReadDir(path)
if err != nil {
return "", err
}
for _, f := range files {
if !f.IsDir() && strings.Contains(f.Name(), match) {
return f.Name(), nil
}
}
return "", fmt.Errorf("file not found")
}
// CreateFile creates a file at the given filename location with the contents
// set to the given data. It will create intermediary directories if needed.
func (fs *FSHelper) CreateFile(filename string, data []byte) error {
@@ -100,10 +117,10 @@ func (fs *FSHelper) RemoveFile(filename string) error {
}
// RemoveFiles removes the given filenames
func (fs *FSHelper) RemoveFiles(files []string) error {
func (fs *FSHelper) RemoveFiles(files []string, continueOnError bool) error {
for _, filename := range files {
err := os.Remove(filename)
if err != nil {
if err != nil && !continueOnError {
return err
}
}

View File

@@ -5,12 +5,15 @@ import (
"io/ioutil"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"github.com/leaanthony/mewn"
"github.com/leaanthony/mewn/lib"
"github.com/leaanthony/slicer"
"github.com/leaanthony/spinner"
)
@@ -56,21 +59,141 @@ func InstallGoDependencies(verbose bool) error {
return nil
}
// BuildApplication will attempt to build the project based on the given inputs
func BuildApplication(binaryName string, forceRebuild bool, buildMode string, packageApp bool, projectOptions *ProjectOptions) error {
// EmbedAssets will embed the built frontend assets via mewn.
func EmbedAssets() ([]string, error) {
mewnFiles := lib.GetMewnFiles([]string{}, false)
// Generate Windows assets if needed
if runtime.GOOS == "windows" {
cleanUp := !packageApp
err := NewPackageHelper().PackageWindows(projectOptions, cleanUp)
if err != nil {
return err
}
referencedAssets, err := lib.GetReferencedAssets(mewnFiles)
if err != nil {
return []string{}, err
}
// Check Mewn is installed
err := CheckMewn(projectOptions.Verbose)
targetFiles := []string{}
for _, referencedAsset := range referencedAssets {
packfileData, err := lib.GeneratePackFileString(referencedAsset, false)
if err != nil {
return []string{}, err
}
targetFile := filepath.Join(referencedAsset.BaseDir, referencedAsset.PackageName+"-mewn.go")
targetFiles = append(targetFiles, targetFile)
ioutil.WriteFile(targetFile, []byte(packfileData), 0644)
}
return targetFiles, nil
}
func InitializeCrossCompilation(verbose bool) error {
// Check Docker
if err := CheckIfInstalled("docker"); err != nil {
return err
}
var packSpinner *spinner.Spinner
if !verbose {
packSpinner = spinner.New("Pulling wailsapp/xgo:latest docker image... (may take a while)")
packSpinner.SetSpinSpeed(50)
packSpinner.Start()
} else {
println("Pulling wailsapp/xgo:latest docker image... (may take a while)")
}
err := NewProgramHelper(verbose).RunCommandArray([]string{"docker",
"pull", "wailsapp/xgo:latest"})
if err != nil {
if packSpinner != nil {
packSpinner.Error()
}
return err
}
if packSpinner != nil {
packSpinner.Success()
}
return nil
}
// BuildDocker builds the project using the cross compiling wailsapp/xgo:latest container
func BuildDocker(binaryName string, buildMode string, projectOptions *ProjectOptions) error {
var packSpinner *spinner.Spinner
if buildMode == BuildModeBridge {
return fmt.Errorf("you cant serve the application in cross-compilation")
}
// Check build directory
buildDirectory := filepath.Join(fs.Cwd(), "build")
if !fs.DirExists(buildDirectory) {
fs.MkDir(buildDirectory)
}
buildCommand := slicer.String()
userid := 1000
user, _ := user.Current()
if i, err := strconv.Atoi(user.Uid); err == nil {
userid = i
}
for _, arg := range []string{
"docker",
"run",
"--rm",
"-v", fmt.Sprintf("%s:/build", filepath.Join(fs.Cwd(), "build")),
"-v", fmt.Sprintf("%s:/source", fs.Cwd()),
"-e", fmt.Sprintf("LOCAL_USER_ID=%v", userid),
"-e", fmt.Sprintf("FLAG_LDFLAGS=%s", ldFlags(projectOptions, buildMode)),
"-e", "FLAG_V=false",
"-e", "FLAG_X=false",
"-e", "FLAG_RACE=false",
"-e", "FLAG_BUILDMODE=default",
"-e", "FLAG_TRIMPATH=false",
"-e", fmt.Sprintf("TARGETS=%s", projectOptions.Platform+"/"+projectOptions.Architecture),
"-e", "GOPROXY=",
"-e", "GO111MODULE=on",
"wailsapp/xgo:latest",
".",
} {
buildCommand.Add(arg)
}
compileMessage := fmt.Sprintf(
"Packing + Compiling project for %s/%s using docker image wailsapp/xgo:latest",
projectOptions.Platform, projectOptions.Architecture)
if buildMode == BuildModeDebug {
compileMessage += " (Debug Mode)"
}
if !projectOptions.Verbose {
packSpinner = spinner.New(compileMessage + "...")
packSpinner.SetSpinSpeed(50)
packSpinner.Start()
} else {
println(compileMessage)
}
err := NewProgramHelper(projectOptions.Verbose).RunCommandArray(buildCommand.AsSlice())
if err != nil {
if packSpinner != nil {
packSpinner.Error()
}
return err
}
if packSpinner != nil {
packSpinner.Success()
}
return nil
}
// BuildNative builds on the target platform itself.
func BuildNative(binaryName string, forceRebuild bool, buildMode string, projectOptions *ProjectOptions) error {
// Check Mewn is installed
if err := CheckMewn(projectOptions.Verbose); err != nil {
return err
}
if err := CheckWindres(); err != nil {
return err
}
@@ -90,18 +213,17 @@ func BuildApplication(binaryName string, forceRebuild bool, buildMode string, pa
}
buildCommand := slicer.String()
buildCommand.Add("mewn")
buildCommand.Add("go")
buildCommand.Add("build")
if buildMode == BuildModeBridge {
// Ignore errors
buildCommand.Add("-i")
}
buildCommand.Add("build")
if binaryName != "" {
// Alter binary name based on OS
switch runtime.GOOS {
switch projectOptions.Platform {
case "windows":
if !strings.HasSuffix(binaryName, ".exe") {
binaryName += ".exe"
@@ -111,7 +233,7 @@ func BuildApplication(binaryName string, forceRebuild bool, buildMode string, pa
binaryName = strings.TrimSuffix(binaryName, ".exe")
}
}
buildCommand.Add("-o", binaryName)
buildCommand.Add("-o", filepath.Join("build", binaryName))
}
// If we are forcing a rebuild
@@ -119,31 +241,13 @@ func BuildApplication(binaryName string, forceRebuild bool, buildMode string, pa
buildCommand.Add("-a")
}
// Setup ld flags
ldflags := "-w -s "
if buildMode == BuildModeDebug {
ldflags = ""
buildCommand.AddSlice([]string{"-ldflags", ldFlags(projectOptions, buildMode)})
if projectOptions.Verbose {
fmt.Printf("Command: %v\n", buildCommand.AsSlice())
}
// Add windows flags
if runtime.GOOS == "windows" && buildMode == BuildModeProd {
ldflags += "-H windowsgui "
}
ldflags += "-X github.com/wailsapp/wails.BuildMode=" + buildMode
// If we wish to generate typescript
if projectOptions.typescriptDefsFilename != "" {
cwd, err := os.Getwd()
if err != nil {
return err
}
filename := filepath.Join(cwd, projectOptions.FrontEnd.Dir, projectOptions.typescriptDefsFilename)
ldflags += " -X github.com/wailsapp/wails/lib/binding.typescriptDefinitionFilename=" + filename
}
buildCommand.AddSlice([]string{"-ldflags", ldflags})
err = NewProgramHelper(projectOptions.Verbose).RunCommandArray(buildCommand.AsSlice())
err := NewProgramHelper(projectOptions.Verbose).RunCommandArray(buildCommand.AsSlice())
if err != nil {
if packSpinner != nil {
packSpinner.Error()
@@ -154,7 +258,57 @@ func BuildApplication(binaryName string, forceRebuild bool, buildMode string, pa
packSpinner.Success()
}
// packageApp
return nil
}
// BuildApplication will attempt to build the project based on the given inputs
func BuildApplication(binaryName string, forceRebuild bool, buildMode string, packageApp bool, projectOptions *ProjectOptions) error {
var err error
// embed resources
targetFiles, err := EmbedAssets()
if err != nil {
return err
}
if projectOptions.CrossCompile {
if err := InitializeCrossCompilation(projectOptions.Verbose); err != nil {
return err
}
}
helper := NewPackageHelper(projectOptions.Platform)
// Generate windows resources
if projectOptions.Platform == "windows" {
if err := helper.PackageWindows(projectOptions, false); err != nil {
return err
}
}
// cleanup temporary embedded assets
defer func() {
for _, filename := range targetFiles {
if err := os.Remove(filename); err != nil {
fmt.Println(err)
}
}
// Removed by popular demand
// TODO: Potentially add a flag to cleanup
// if projectOptions.Platform == "windows" {
// helper.CleanWindows(projectOptions)
// }
}()
if projectOptions.CrossCompile {
err = BuildDocker(binaryName, buildMode, projectOptions)
} else {
err = BuildNative(binaryName, forceRebuild, buildMode, projectOptions)
}
if err != nil {
return err
}
if packageApp {
err = PackageApplication(projectOptions)
if err != nil {
@@ -167,22 +321,14 @@ func BuildApplication(binaryName string, forceRebuild bool, buildMode string, pa
// PackageApplication will attempt to package the application in a platform dependent way
func PackageApplication(projectOptions *ProjectOptions) error {
// Package app
message := "Generating .app"
if runtime.GOOS == "windows" {
err := CheckWindres()
if err != nil {
return err
}
message = "Generating resource bundle"
}
var packageSpinner *spinner.Spinner
if projectOptions.Verbose {
packageSpinner = spinner.New(message)
packageSpinner = spinner.New("Packaging application...")
packageSpinner.SetSpinSpeed(50)
packageSpinner.Start()
}
err := NewPackageHelper().Package(projectOptions)
err := NewPackageHelper(projectOptions.Platform).Package(projectOptions)
if err != nil {
if packageSpinner != nil {
packageSpinner.Error()
@@ -244,7 +390,7 @@ func CheckMewn(verbose bool) (err error) {
// CheckWindres checks if Windres is installed and if not, aborts
func CheckWindres() (err error) {
if runtime.GOOS != "windows" {
if runtime.GOOS != "windows" { // FIXME: Handle windows cross-compile for windows!
return nil
}
programHelper := NewProgramHelper()
@@ -254,6 +400,15 @@ func CheckWindres() (err error) {
return nil
}
// CheckIfInstalled returns if application is installed
func CheckIfInstalled(application string) (err error) {
programHelper := NewProgramHelper()
if !programHelper.IsInstalled(application) {
return fmt.Errorf("%s not installed. Ensure you have installed %s correctly", application, application)
}
return nil
}
// InstallFrontendDeps attempts to install the frontend dependencies based on the given options
func InstallFrontendDeps(projectDir string, projectOptions *ProjectOptions, forceRebuild bool, caller string) error {
@@ -377,7 +532,7 @@ func ServeProject(projectOptions *ProjectOptions, logger *Logger) error {
time.Sleep(2 * time.Second)
logger.Green(">>>>> To connect, you will need to run '" + projectOptions.FrontEnd.Serve + "' in the '" + projectOptions.FrontEnd.Dir + "' directory <<<<<")
}()
location, err := filepath.Abs(projectOptions.BinaryName)
location, err := filepath.Abs(filepath.Join("build", projectOptions.BinaryName))
if err != nil {
return err
}
@@ -393,3 +548,33 @@ func ServeProject(projectOptions *ProjectOptions, logger *Logger) error {
return nil
}
func ldFlags(po *ProjectOptions, buildMode string) string {
// Setup ld flags
ldflags := "-w -s "
if buildMode == BuildModeDebug {
ldflags = ""
}
// Add windows flags
if po.Platform == "windows" && buildMode == BuildModeProd {
ldflags += "-H windowsgui "
}
ldflags += "-X github.com/wailsapp/wails.BuildMode=" + buildMode
// Add additional ldflags passed in via the `ldflags` cli flag
if len(po.LdFlags) > 0 {
ldflags += " " + po.LdFlags
}
// If we wish to generate typescript
if po.typescriptDefsFilename != "" {
cwd, err := os.Getwd()
if err == nil {
filename := filepath.Join(cwd, po.FrontEnd.Dir, po.typescriptDefsFilename)
ldflags += " -X github.com/wailsapp/wails/lib/binding.typescriptDefinitionFilename=" + filename
}
}
return ldflags
}

View File

@@ -53,6 +53,16 @@ const (
Deepin
// Raspbian distribution
Raspbian
// Tumbleweed (OpenSUSE) distribution
Tumbleweed
// Leap (OpenSUSE) distribution
Leap
// ArchLabs distribution
ArchLabs
// PopOS distribution
PopOS
// Solus distribution
Solus
)
// DistroInfo contains all the information relating to a linux distribution
@@ -101,7 +111,7 @@ func parseOsRelease(osRelease string) *DistroInfo {
}
switch splitLine[0] {
case "ID":
osID = strings.Trim(splitLine[1], "\"")
osID = strings.ToLower(strings.Trim(splitLine[1], "\""))
case "NAME":
osNAME = strings.Trim(splitLine[1], "\"")
case "VERSION_ID":
@@ -117,6 +127,8 @@ func parseOsRelease(osRelease string) *DistroInfo {
result.Distribution = CentOS
case "arch":
result.Distribution = Arch
case "archlabs":
result.Distribution = ArchLabs
case "debian":
result.Distribution = Debian
case "ubuntu":
@@ -147,6 +159,14 @@ func parseOsRelease(osRelease string) *DistroInfo {
result.Distribution = Deepin
case "raspbian":
result.Distribution = Raspbian
case "opensuse-tumbleweed":
result.Distribution = Tumbleweed
case "opensuse-leap":
result.Distribution = Leap
case "pop":
result.Distribution = PopOS
case "solus":
result.Distribution = Solus
default:
result.Distribution = Unknown
}
@@ -183,6 +203,17 @@ func DpkgInstalled(packageName string) (bool, error) {
return exitCode == 0, nil
}
// EOpkgInstalled uses dpkg to see if a package is installed
func EOpkgInstalled(packageName string) (bool, error) {
program := NewProgramHelper()
eopkg := program.FindProgram("eopkg")
if eopkg == nil {
return false, fmt.Errorf("cannot check dependencies: eopkg not found")
}
stdout, _, _, _ := eopkg.Run("info", packageName)
return strings.HasPrefix(stdout, "Installed"), nil
}
// PacmanInstalled uses pacman to see if a package is installed.
func PacmanInstalled(packageName string) (bool, error) {
program := NewProgramHelper()
@@ -254,5 +285,9 @@ func RequestSupportForDistribution(distroInfo *DistroInfo) error {
fmt.Println("Opening browser to file request.")
browser.OpenURL(fullURL + url.PathEscape(params))
result = Prompt("We have a guide for adding support for your distribution. Would you like to view it?", "yes")
if strings.ToLower(result) == "yes" {
browser.OpenURL("https://wails.app/guides/distro/")
}
return nil
}

View File

@@ -22,5 +22,25 @@ UBUNTU_CODENAME=bionic
if result.Distribution != Ubuntu {
t.Errorf("expected 'Ubuntu' ID but got '%d'", result.Distribution)
}
}
func TestTumbleweedDetection(t *testing.T) {
osrelease := `
NAME="openSUSE Tumbleweed"
# VERSION="20200414"
ID="opensuse-tumbleweed"
ID_LIKE="opensuse suse"
VERSION_ID="20200414"
PRETTY_NAME="openSUSE Tumbleweed"
ANSI_COLOR="0;32"
CPE_NAME="cpe:/o:opensuse:tumbleweed:20200414"
BUG_REPORT_URL="https://bugs.opensuse.org"
HOME_URL="https://www.opensuse.org/"
LOGO="distributor-logo"
`
result := parseOsRelease(osrelease)
if result.Distribution != Tumbleweed {
t.Errorf("expected 'Tumbleweed' ID but got '%d'", result.Distribution)
}
}

View File

@@ -28,6 +28,15 @@ distributions:
gccversioncommand: &gccdumpfullversion -dumpfullversion
programs: *debiandefaultprograms
libraries: *debiandefaultlibraries
pop:
id: pop
releases:
default:
version: default
name: Pop!_OS
gccversioncommand: &gccdumpfullversion -dumpfullversion
programs: *debiandefaultprograms
libraries: *debiandefaultlibraries
kali:
id: kali
releases:
@@ -176,6 +185,15 @@ distributions:
gccversioncommand: *gccdumpversion
programs: *archdefaultprograms
libraries: *archdefaultlibraries
archlabs:
id: archlabs
releases:
default:
version: default
name: ArchLabs
gccversioncommand: *gccdumpversion
programs: *archdefaultprograms
libraries: *archdefaultlibraries
manjaro:
id: manjaro
releases:
@@ -223,3 +241,51 @@ distributions:
gccversioncommand: *gccdumpfullversion
programs: *debiandefaultprograms
libraries: *debiandefaultlibraries
solus:
id: solus
releases:
default:
version: default
name: Solus
gccversioncommand: *gccdumpfullversion
programs: &solusdefaultprograms
- name: gcc
help: Please install with `sudo eopkg it -c system.devel` and try again
- name: pkg-config
help: Please install with `sudo eopkg it -c system.devel` and try again
- name: npm
help: Please install with `sudo eopkg it nodejs` and try again
libraries: &solusdefaultlibraries
- name: libgtk-3-devel
help: Please install with `sudo eopkg it libgtk-3-devel` and try again
- name: libwebkit-gtk-devel
help: Please install with `sudo eopkg it libwebkit-gtk-devel` and try again
opensuse-tumbleweed:
id: opensuse-tumbleweed
releases:
default:
version: default
name: openSUSE Tumbleweed
gccversioncommand: *gccdumpfullversion
programs: &opensusedefaultprograms
- name: gcc
help: Please install with `sudo zypper in gcc-c++` and try again
- name: pkg-config
help: Please install with `sudo zypper in pkgconf-pkg-config` and try again
- name: npm
help: Please install `sudo zypper in nodejs` and try again
libraries: &opensusedefaultlibraries
- name: gtk3-devel
help: Please install with `sudo zypper in gtk3-devel` and try again
- name: webkit2gtk3-devel
help: Please install with `sudo zypper in webkit2gtk3-devel` and try again
opensuse-leap:
id: opensuse-leap
releases:
default:
version: default
name: openSUSE Leap
gccversioncommand: *gccdumpfullversion
programs: *opensusedefaultprograms
libraries: *opensusedefaultlibraries

View File

@@ -1,9 +1,12 @@
package cmd
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"image"
"image/png"
"io/ioutil"
"os"
"path"
@@ -14,21 +17,24 @@ import (
"time"
"github.com/jackmordaunt/icns"
"golang.org/x/image/draw"
)
// PackageHelper helps with the 'wails package' command
type PackageHelper struct {
fs *FSHelper
log *Logger
system *SystemHelper
platform string
fs *FSHelper
log *Logger
system *SystemHelper
}
// NewPackageHelper creates a new PackageHelper!
func NewPackageHelper() *PackageHelper {
func NewPackageHelper(platform string) *PackageHelper {
return &PackageHelper{
fs: NewFSHelper(),
log: NewLogger(),
system: NewSystemHelper(),
platform: platform,
fs: NewFSHelper(),
log: NewLogger(),
system: NewSystemHelper(),
}
}
@@ -53,6 +59,111 @@ func newPlistData(title, exe, packageID, version, author string) *plistData {
}
}
type windowsIcoHeader struct {
_ uint16
imageType uint16
imageCount uint16
}
type windowsIcoDescriptor struct {
width uint8
height uint8
colours uint8
_ uint8
planes uint16
bpp uint16
size uint32
offset uint32
}
type windowsIcoContainer struct {
Header windowsIcoDescriptor
Data []byte
}
func generateWindowsIcon(pngFilename string, iconfile string) error {
sizes := []int{256, 128, 64, 48, 32, 16}
pngfile, err := os.Open(pngFilename)
if err != nil {
return err
}
defer pngfile.Close()
pngdata, err := png.Decode(pngfile)
if err != nil {
return err
}
icons := []windowsIcoContainer{}
for _, size := range sizes {
rect := image.Rect(0, 0, int(size), int(size))
rawdata := image.NewRGBA(rect)
scale := draw.CatmullRom
scale.Scale(rawdata, rect, pngdata, pngdata.Bounds(), draw.Over, nil)
icondata := new(bytes.Buffer)
writer := bufio.NewWriter(icondata)
err = png.Encode(writer, rawdata)
if err != nil {
return err
}
writer.Flush()
imgSize := size
if imgSize >= 256 {
imgSize = 0
}
data := icondata.Bytes()
icn := windowsIcoContainer{
Header: windowsIcoDescriptor{
width: uint8(imgSize),
height: uint8(imgSize),
planes: 1,
bpp: 32,
size: uint32(len(data)),
},
Data: data,
}
icons = append(icons, icn)
}
outfile, err := os.Create(iconfile)
if err != nil {
return err
}
defer outfile.Close()
ico := windowsIcoHeader{
imageType: 1,
imageCount: uint16(len(sizes)),
}
err = binary.Write(outfile, binary.LittleEndian, ico)
if err != nil {
return err
}
offset := uint32(6 + 16*len(sizes))
for _, icon := range icons {
icon.Header.offset = offset
err = binary.Write(outfile, binary.LittleEndian, icon.Header)
if err != nil {
return err
}
offset += icon.Header.size
}
for _, icon := range icons {
_, err = outfile.Write(icon.Data)
if err != nil {
return err
}
}
return nil
}
func defaultString(val string, defaultVal string) string {
if val != "" {
return val
@@ -63,29 +174,30 @@ func defaultString(val string, defaultVal string) string {
func (b *PackageHelper) getPackageFileBaseDir() string {
// Calculate template base dir
_, filename, _, _ := runtime.Caller(1)
return filepath.Join(path.Dir(filename), "packages", runtime.GOOS)
return filepath.Join(path.Dir(filename), "packages", b.platform)
}
// Package the application into a platform specific package
func (b *PackageHelper) Package(po *ProjectOptions) error {
switch runtime.GOOS {
switch b.platform {
case "darwin":
// Check we have the exe
if !b.fs.FileExists(po.BinaryName) {
return fmt.Errorf("cannot bundle non-existent binary file '%s'. Please build with 'wails build' first", po.BinaryName)
}
return b.packageOSX(po)
case "windows":
return b.PackageWindows(po, false)
return b.PackageWindows(po, true)
case "linux":
return fmt.Errorf("linux is not supported at this time. Please see https://github.com/wailsapp/wails/issues/2")
return b.packageLinux(po)
default:
return fmt.Errorf("platform '%s' not supported for bundling yet", runtime.GOOS)
return fmt.Errorf("platform '%s' not supported for bundling yet", b.platform)
}
}
func (b *PackageHelper) packageLinux(po *ProjectOptions) error {
return nil
}
// Package the application for OSX
func (b *PackageHelper) packageOSX(po *ProjectOptions) error {
build := path.Join(b.fs.Cwd(), "build")
system := NewSystemHelper()
config, err := system.LoadConfig()
@@ -100,39 +212,68 @@ func (b *PackageHelper) packageOSX(po *ProjectOptions) error {
packageID := strings.Join([]string{"wails", name, version}, ".")
plistData := newPlistData(name, exe, packageID, version, author)
appname := po.Name + ".app"
plistFilename := path.Join(build, appname, "Contents", "Info.plist")
customPlist := path.Join(b.fs.Cwd(), "info.plist")
// Check binary exists
source := path.Join(b.fs.Cwd(), exe)
if !b.fs.FileExists(source) {
// We need to build!
return fmt.Errorf("Target '%s' not available. Has it been compiled yet?", exe)
source := path.Join(build, exe)
if po.CrossCompile == true {
file, err := b.fs.FindFile(build, "darwin")
if err != nil {
return err
}
source = path.Join(build, file)
}
if !b.fs.FileExists(source) {
// We need to build!
return fmt.Errorf("Target '%s' not available. Has it been compiled yet?", source)
}
// Remove the existing package
os.RemoveAll(appname)
exeDir := path.Join(b.fs.Cwd(), appname, "/Contents/MacOS")
// Create directories
exeDir := path.Join(build, appname, "/Contents/MacOS")
b.fs.MkDirs(exeDir, 0755)
resourceDir := path.Join(b.fs.Cwd(), appname, "/Contents/Resources")
resourceDir := path.Join(build, appname, "/Contents/Resources")
b.fs.MkDirs(resourceDir, 0755)
tmpl := template.New("infoPlist")
plistFile := filepath.Join(b.getPackageFileBaseDir(), "info.plist")
infoPlist, err := ioutil.ReadFile(plistFile)
if err != nil {
return err
}
tmpl.Parse(string(infoPlist))
// Write the template to a buffer
var tpl bytes.Buffer
err = tmpl.Execute(&tpl, plistData)
if err != nil {
return err
}
filename := path.Join(b.fs.Cwd(), appname, "Contents", "Info.plist")
err = ioutil.WriteFile(filename, tpl.Bytes(), 0644)
if err != nil {
return err
// Do we have a custom plist in the project directory?
if !fs.FileExists(customPlist) {
// No - create a new plist from our defaults
tmpl := template.New("infoPlist")
plistFile := filepath.Join(b.getPackageFileBaseDir(), "info.plist")
infoPlist, err := ioutil.ReadFile(plistFile)
if err != nil {
return err
}
tmpl.Parse(string(infoPlist))
// Write the template to a buffer
var tpl bytes.Buffer
err = tmpl.Execute(&tpl, plistData)
if err != nil {
return err
}
// Save to the package
err = ioutil.WriteFile(plistFilename, tpl.Bytes(), 0644)
if err != nil {
return err
}
// Also write to project directory for customisation
err = ioutil.WriteFile(customPlist, tpl.Bytes(), 0644)
if err != nil {
return err
}
} else {
// Yes - we have a plist. Copy it to the package verbatim
err = fs.CopyFile(customPlist, plistFilename)
if err != nil {
return err
}
}
// Copy executable
@@ -150,22 +291,37 @@ func (b *PackageHelper) packageOSX(po *ProjectOptions) error {
return err
}
// CleanWindows removes any windows related files found in the directory
func (b *PackageHelper) CleanWindows(po *ProjectOptions) {
pdir := b.fs.Cwd()
basename := strings.TrimSuffix(po.BinaryName, ".exe")
exts := []string{".ico", ".exe.manifest", ".rc", "-res.syso"}
rsrcs := []string{}
for _, ext := range exts {
rsrcs = append(rsrcs, filepath.Join(pdir, basename+ext))
}
b.fs.RemoveFiles(rsrcs, true)
}
// PackageWindows packages the application for windows platforms
func (b *PackageHelper) PackageWindows(po *ProjectOptions, cleanUp bool) error {
outputDir := b.fs.Cwd()
basename := strings.TrimSuffix(po.BinaryName, ".exe")
// Copy icon
tgtIconFile := filepath.Join(b.fs.Cwd(), basename+".ico")
if !b.fs.FileExists(tgtIconFile) {
srcIconfile := filepath.Join(b.getPackageFileBaseDir(), "wails.ico")
err := b.fs.CopyFile(srcIconfile, tgtIconFile)
if err != nil {
return err
}
// Copy default icon if needed
icon, err := b.copyIcon()
if err != nil {
return err
}
// Generate icon from PNG
err = generateWindowsIcon(icon, basename+".ico")
if err != nil {
return err
}
// Copy manifest
tgtManifestFile := filepath.Join(b.fs.Cwd(), basename+".exe.manifest")
tgtManifestFile := filepath.Join(outputDir, basename+".exe.manifest")
if !b.fs.FileExists(tgtManifestFile) {
srcManifestfile := filepath.Join(b.getPackageFileBaseDir(), "wails.exe.manifest")
err := b.fs.CopyFile(srcManifestfile, tgtManifestFile)
@@ -175,7 +331,7 @@ func (b *PackageHelper) PackageWindows(po *ProjectOptions, cleanUp bool) error {
}
// Copy rc file
tgtRCFile := filepath.Join(b.fs.Cwd(), basename+".rc")
tgtRCFile := filepath.Join(outputDir, basename+".rc")
if !b.fs.FileExists(tgtRCFile) {
srcRCfile := filepath.Join(b.getPackageFileBaseDir(), "wails.rc")
rcfilebytes, err := ioutil.ReadFile(srcRCfile)
@@ -190,33 +346,37 @@ func (b *PackageHelper) PackageWindows(po *ProjectOptions, cleanUp bool) error {
}
// Build syso
sysofile := filepath.Join(b.fs.Cwd(), basename+"-res.syso")
sysofile := filepath.Join(outputDir, basename+"-res.syso")
batfile, err := fs.LocalDir(".")
if err != nil {
return err
}
// cross-compile
if b.platform != runtime.GOOS {
args := []string{
"docker", "run", "--rm",
"-v", outputDir + ":/build",
"--entrypoint", "/bin/sh",
"wailsapp/xgo:latest",
"-c", "/usr/bin/x86_64-w64-mingw32-windres -o /build/" + basename + "-res.syso /build/" + basename + ".rc",
}
if err := NewProgramHelper().RunCommandArray(args); err != nil {
return err
}
} else {
batfile, err := fs.LocalDir(".")
if err != nil {
return err
}
windresBatFile := filepath.Join(batfile.fullPath, "windres.bat")
windresCommand := []string{windresBatFile, sysofile, tgtRCFile}
err = NewProgramHelper().RunCommandArray(windresCommand)
if err != nil {
return err
}
// clean up
if cleanUp {
filesToDelete := []string{tgtIconFile, tgtManifestFile, tgtRCFile, sysofile}
err := b.fs.RemoveFiles(filesToDelete)
windresBatFile := filepath.Join(batfile.fullPath, "windres.bat")
windresCommand := []string{windresBatFile, sysofile, tgtRCFile}
err = NewProgramHelper().RunCommandArray(windresCommand)
if err != nil {
return err
}
}
return nil
}
func (b *PackageHelper) copyIcon(resourceDir string) (string, error) {
func (b *PackageHelper) copyIcon() (string, error) {
// TODO: Read this from project.json
const appIconFilename = "appicon.png"
@@ -241,7 +401,7 @@ func (b *PackageHelper) copyIcon(resourceDir string) (string, error) {
func (b *PackageHelper) packageIconOSX(resourceDir string) error {
srcIcon, err := b.copyIcon(resourceDir)
srcIcon, err := b.copyIcon()
if err != nil {
return err
}

View File

@@ -1,5 +1,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleName</key><string>{{.Title}}</string>
<key>CFBundleExecutable</key><string>{{.Exe}}</string>
<key>CFBundleIdentifier</key><string>{{.PackageID}}</string>

View File

@@ -1,2 +1,2 @@
100 ICON "$NAME$.ico"
100 24 "$NAME$.exe.manifest"
110 24 "$NAME$.exe.manifest"

View File

@@ -138,11 +138,11 @@ func (p *ProgramHelper) RunCommand(command string) error {
// RunCommandArray runs the command specified in the array
func (p *ProgramHelper) RunCommandArray(args []string, dir ...string) error {
program := args[0]
programCommand := args[0]
// TODO: Run FindProgram here and get the full path to the exe
program, err := exec.LookPath(program)
program, err := exec.LookPath(programCommand)
if err != nil {
fmt.Printf("ERROR: Looks like '%s' isn't installed. Please install and try again.", program)
fmt.Printf("ERROR: Looks like '%s' isn't installed. Please install and try again.", programCommand)
return err
}

View File

@@ -6,7 +6,6 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
@@ -159,6 +158,10 @@ type ProjectOptions struct {
WailsVersion string
typescriptDefsFilename string
Verbose bool `json:"-"`
CrossCompile bool
Platform string
Architecture string
LdFlags string
}
// Defaults sets the default project template
@@ -333,11 +336,6 @@ func processBinaryName(po *ProjectOptions) {
if po.BinaryName == "" {
var binaryNameComputed = computeBinaryName(po.Name)
po.BinaryName = Prompt("The output binary name", binaryNameComputed)
if runtime.GOOS == "windows" {
if !strings.HasSuffix(po.BinaryName, ".exe") {
po.BinaryName += ".exe"
}
}
}
fmt.Println("Output binary Name: " + po.BinaryName)
}

View File

@@ -5,12 +5,11 @@ import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"strconv"
"time"
homedir "github.com/mitchellh/go-homedir"
)
// SystemHelper - Defines everything related to the system
@@ -38,10 +37,11 @@ func NewSystemHelper() *SystemHelper {
// setSystemDirs calculates the system directories it is interested in
func (s *SystemHelper) setSystemDirs() {
var err error
s.homeDir, err = homedir.Dir()
s.homeDir, err = os.UserHomeDir()
if err != nil {
log.Fatal("Cannot find home directory! Please file a bug report!")
}
// TODO: A better config system
s.wailsSystemDir = filepath.Join(s.homeDir, ".wails")
s.wailsSystemConfig = filepath.Join(s.wailsSystemDir, s.configFilename)
@@ -132,7 +132,7 @@ func (s *SystemHelper) setup() error {
}
const introText = `
Wails is a lightweight framework for creating web-like desktop apps in Go.
Wails is a lightweight framework for creating web-like desktop apps in Go.
I'll need to ask you a few questions so I can fill in your project templates and then I will try and see if you have the correct dependencies installed. If you don't have the right tools installed, I'll try and suggest how to install them.
`
@@ -274,16 +274,18 @@ func CheckDependencies(logger *Logger) (bool, error) {
distroInfo := GetLinuxDistroInfo()
switch distroInfo.Distribution {
case Ubuntu, Debian, Zorin, Parrot, Linuxmint, Elementary, Kali, Neon, Deepin, Raspbian:
case Ubuntu, Debian, Zorin, Parrot, Linuxmint, Elementary, Kali, Neon, Deepin, Raspbian, PopOS:
libraryChecker = DpkgInstalled
case Arch, ArcoLinux, Manjaro, ManjaroARM:
case Arch, ArcoLinux, ArchLabs, Manjaro, ManjaroARM:
libraryChecker = PacmanInstalled
case CentOS, Fedora:
case CentOS, Fedora, Tumbleweed, Leap:
libraryChecker = RpmInstalled
case Gentoo:
libraryChecker = EqueryInstalled
case VoidLinux:
libraryChecker = XbpsInstalled
case Solus:
libraryChecker = EOpkgInstalled
default:
return false, RequestSupportForDistribution(distroInfo)
}

View File

@@ -5,8 +5,8 @@
"private": true,
"dependencies": {
"core-js": "^3.6.4",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"wails-react-scripts": "3.0.1-2",
"react-modal": "3.11.2",
"@wailsapp/runtime": "^1.0.10"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,10 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
@@ -20,8 +23,9 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="app"></div>
<!--
@@ -34,5 +38,6 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

View File

@@ -6,6 +6,16 @@
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -3,11 +3,16 @@
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;

View File

@@ -1,48 +1,35 @@
import React from 'react';
import React, { useState } from 'react';
import Modal from 'react-modal';
class HelloWorld extends React.Component {
constructor(props, context) {
super();
this.state = {
showModal: false
};
function HelloWorld() {
const [showModal, setShowModal] = useState(false);
const [result, setResult] = useState(null);
this.handleOpenModal = this.handleOpenModal.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
}
const handleOpenModal = () => {
setShowModal(true);
handleOpenModal () {
this.setState({ showModal: true });
window.backend.basic().then((result) => setResult(result));
};
window.backend.basic().then(result =>
this.setState({
result
})
);
}
const handleCloseModal = () => {
setShowModal(false);
};
handleCloseModal () {
this.setState({ showModal: false });
}
render() {
const { result } = this.state;
return (
<div className="App">
<button onClick={this.handleOpenModal} type="button">
Hello
</button>
<Modal
isOpen={this.state.showModal}
contentLabel="Minimal Modal Example"
>
<p>{result}</p>
<button onClick={this.handleCloseModal}>Close Modal</button>
</Modal>
</div>
);
}
return (
<div className="App">
<button onClick={() => handleOpenModal()} type="button">
Hello
</button>
<Modal
appElement={document.getElementById("app")}
isOpen={showModal}
contentLabel="Minimal Modal Example"
>
<p>{result}</p>
<button onClick={() => handleCloseModal()}>Close Modal</button>
</Modal>
</div>
);
}
export default HelloWorld;

View File

@@ -3,9 +3,20 @@ import ReactDOM from 'react-dom';
import 'core-js/stable';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import * as Wails from '@wailsapp/runtime';
Wails.Init(() => {
ReactDOM.render(<App />, document.getElementById('app'));
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("app")
);
});
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@@ -1,8 +1,8 @@
{
"name": "React JS",
"version": "1.0.0",
"shortdescription": "Create React App v3 template",
"description": "Create React App v3 standar tooling",
"shortdescription": "Create React App v4 template",
"description": "Create React App v4 standard tooling",
"install": "npm install",
"build": "npm run build",
"author": "bh90210 <ktc@pm.me>",

View File

@@ -0,0 +1,5 @@
# README
This is an experimental template for vanilla HTML/JS/CSS.
The webpack rules may need to be adjusted to correctly embed all assets. Babel may also need to be setup correctly.

View File

@@ -0,0 +1,42 @@
{
"name": "vanilla",
"author": "Lea<l>",
"private": true,
"scripts": {
"serve": "webpack-dev-server",
"build": "npx webpack"
},
"dependencies": {
"core-js": "^3.6.4",
"regenerator-runtime": "^0.13.3",
"@wailsapp/runtime": "^1.0.10"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"copy-webpack-plugin": "^6.0.2",
"eslint": "^6.8.0",
"eventsource-polyfill": "^0.9.6",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0",
"webpack-hot-middleware": "^2.25.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

View File

@@ -0,0 +1,9 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<div id="app"></div>
<script src="main.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,30 @@
import 'core-js/stable';
const runtime = require('@wailsapp/runtime');
// Main entry point
function start() {
// Ensure the default app div is 100% wide/high
var app = document.getElementById('app');
app.style.width = '100%';
app.style.height = '100%';
// Inject html
app.innerHTML = `
<div class='logo'></div>
<div class='container'>
<button id='button'>Click Me!</button>
<div id='result'/>
</div>
`;
// Connect button to Go method
document.getElementById('button').onclick = function() {
window.backend.basic().then( function(result) {
document.getElementById('result').innerText = result;
});
};
};
// We provide our entrypoint as a callback for runtime.Init
runtime.Init(start);

View File

@@ -0,0 +1,56 @@
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
let imageSizeLimit = 9007199254740991; // Number.MAX_SAFE_INTEGER
let sourceDir = path.resolve(__dirname, 'src');
let buildDir = path.resolve(__dirname, 'build');
module.exports = {
entry: {
index: path.resolve(sourceDir, 'main.js')
},
output: {
path: buildDir,
filename: 'main.js'
},
optimization: {
splitChunks: false
},
devServer: {
disableHostCheck: true,
contentBase: path.join(__dirname, 'src'),
compress: true,
open: true,
port: 8090
},
mode: 'production',
module: {
rules: [
{
test: /\.(png|gif|jpg|woff2?|eot|ttf|otf|svg)(\?.*)?$/i,
use: [
{
loader: 'url-loader',
options: {
limit: imageSizeLimit
}
}
],
}
]
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(sourceDir, 'main.css'),
to: path.resolve(buildDir, 'main.css')
},
{
from: path.resolve(sourceDir, 'index.html'),
to: path.resolve(buildDir, 'index.html')
},
]
})
]
};

View File

@@ -0,0 +1,5 @@
module {{.BinaryName}}
require (
github.com/wailsapp/wails {{.WailsVersion}}
)

View File

@@ -0,0 +1,27 @@
package main
import (
"github.com/leaanthony/mewn"
"github.com/wailsapp/wails"
)
func basic() string {
return "Hello World!"
}
func main() {
js := mewn.String("./frontend/build/main.js")
css := mewn.String("./frontend/build/main.css")
app := wails.CreateApp(&wails.AppConfig{
Width: 1024,
Height: 768,
Title: "{{.Name}}",
JS: js,
CSS: css,
Colour: "#131313",
})
app.Bind(basic)
app.Run()
}

View File

@@ -0,0 +1,12 @@
{
"name": "Vanilla",
"shortdescription": "A Vanilla HTML/JS template",
"description": "A basic template using plain html/js and bundled using Webpack 4",
"author": "Lea Anthony<lea.anthony@gmail.com>",
"created": "2020-06-14",
"frontenddir": "frontend",
"install": "npm install",
"build": "npm run build",
"serve": "npm run serve",
"bridge": "src"
}

View File

@@ -1,4 +1,4 @@
package cmd
// Version - Wails version
const Version = "v1.0.3-pre2"
const Version = "v1.7.2-pre3"

View File

@@ -3,11 +3,24 @@ package main
import (
"fmt"
"os"
"runtime"
"strings"
"github.com/leaanthony/spinner"
"github.com/wailsapp/wails/cmd"
)
// getSupportedPlatforms returns a slice of platform/architecture
// targets that are buildable using the cross-platform 'x' option.
func getSupportedPlatforms() []string {
return []string{
"darwin/amd64",
"linux/amd64",
"linux/arm-7",
"windows/amd64",
}
}
func init() {
var packageApp = false
@@ -15,6 +28,8 @@ func init() {
var debugMode = false
var typescriptFilename = ""
var verbose = false
var platform = ""
var ldflags = ""
buildSpinner := spinner.NewSpinner()
buildSpinner.SetSpinSpeed(50)
@@ -26,7 +41,16 @@ func init() {
BoolFlag("f", "Force rebuild of application components", &forceRebuild).
BoolFlag("d", "Build in Debug mode", &debugMode).
BoolFlag("verbose", "Verbose output", &verbose).
StringFlag("t", "Generate Typescript definitions to given file (at runtime)", &typescriptFilename)
StringFlag("t", "Generate Typescript definitions to given file (at runtime)", &typescriptFilename).
StringFlag("ldflags", "Extra options for -ldflags", &ldflags)
var b strings.Builder
for _, plat := range getSupportedPlatforms() {
fmt.Fprintf(&b, " - %s\n", plat)
}
initCmd.StringFlag("x",
fmt.Sprintf("Cross-compile application to specified platform via xgo\n%s", b.String()),
&platform)
initCmd.Action(func() error {
@@ -52,6 +76,28 @@ func init() {
return fmt.Errorf("Unable to find 'project.json'. Please check you are in a Wails project directory")
}
// Set cross-compile
projectOptions.Platform = runtime.GOOS
if len(platform) > 0 {
supported := false
for _, plat := range getSupportedPlatforms() {
if plat == platform {
supported = true
}
}
if !supported {
return fmt.Errorf("unsupported platform '%s' specified.\nPlease run `wails build -h` to see the supported platform/architecture options", platform)
}
projectOptions.CrossCompile = true
plat := strings.Split(platform, "/")
projectOptions.Platform = plat[0]
projectOptions.Architecture = plat[1]
}
// Add ldflags
projectOptions.LdFlags = ldflags
// Validate config
// Check if we have a frontend
err = cmd.ValidateFrontendConfig(projectOptions)

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"runtime"
"github.com/leaanthony/spinner"
"github.com/wailsapp/wails/cmd"
@@ -34,7 +35,6 @@ func init() {
// Project options
projectOptions := &cmd.ProjectOptions{}
projectOptions.Verbose = verbose
// Check we are in project directory
// Check project.json loads correctly
@@ -44,6 +44,10 @@ func init() {
return err
}
// Set project options
projectOptions.Verbose = verbose
projectOptions.Platform = runtime.GOOS
// Save project directory
projectDir := fs.Cwd()

View File

@@ -3,9 +3,9 @@ package main
import (
"fmt"
"log"
"os"
"github.com/leaanthony/spinner"
"github.com/mitchellh/go-homedir"
"github.com/wailsapp/wails/cmd"
)
@@ -146,7 +146,7 @@ func updateToVersion(targetVersion *cmd.SemanticVersion, force bool) error {
updateSpinner.Start("Installing Wails " + desiredVersion)
// Run command in non module directory
homeDir, err := homedir.Dir()
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatal("Cannot find home directory! Please file a bug report!")
}

9
go.mod
View File

@@ -14,19 +14,18 @@ require (
github.com/leaanthony/spinner v0.5.3
github.com/mattn/go-colorable v0.1.1 // indirect
github.com/mattn/go-isatty v0.0.7 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
github.com/pkg/errors v0.8.1 // indirect
github.com/sirupsen/logrus v1.4.1
github.com/stretchr/testify v1.3.0 // indirect
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 // indirect
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 // indirect
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862
golang.org/x/image v0.0.0-20200430140353-33d19683fad8
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/text v0.3.0
gopkg.in/AlecAivazis/survey.v1 v1.8.4
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22
)
go 1.12
go 1.13

18
go.sum
View File

@@ -46,8 +46,6 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
@@ -68,19 +66,23 @@ github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba h1:2DHfQOxcpWdGf5
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba/go.mod h1:iLnlXG2Pakcii2CU0cbY07DRCSvpWNa7nFxtevhOChk=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 h1:6M3SDHlHHDCx2PcQw3S4KsR170vGqDhJDOmpVd4Hjak=
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/AlecAivazis/survey.v1 v1.8.4 h1:10xXXN3wgIhPheb5NI58zFgZv32Ana7P3Tl4shW+0Qc=

View File

@@ -17,9 +17,9 @@ type Renderer interface {
NotifyEvent(eventData *messages.EventData) error
// Dialog Runtime
SelectFile() string
SelectFile(title string, filter string) string
SelectDirectory() string
SelectSaveFile() string
SelectSaveFile(title string, filter string) string
// Window Runtime
SetColour(string) error

View File

@@ -42,12 +42,12 @@ type Bridge struct {
server *http.Server
lock sync.Mutex
sessions map[string]session
sessions map[string]*session
}
// Initialise the Bridge Renderer
func (h *Bridge) Initialise(appConfig interfaces.AppConfig, ipcManager interfaces.IPCManager, eventManager interfaces.EventManager) error {
h.sessions = map[string]session{}
h.sessions = map[string]*session{}
h.ipcManager = ipcManager
h.appConfig = appConfig
h.eventManager = eventManager
@@ -70,13 +70,11 @@ func (h *Bridge) wsBridgeHandler(w http.ResponseWriter, r *http.Request) {
}
func (h *Bridge) startSession(conn *websocket.Conn) {
s := session{
conn: conn,
bindingCache: h.bindingCache,
ipc: h.ipcManager,
log: h.log,
eventManager: h.eventManager,
}
s := newSession(conn,
h.bindingCache,
h.ipcManager,
logger.NewCustomLogger("BridgeSession"),
h.eventManager)
conn.SetCloseHandler(func(int, string) error {
h.log.Infof("Connection dropped [%s].", s.Identifier())
@@ -116,7 +114,7 @@ func (h *Bridge) NewBinding(methodName string) error {
// SelectFile is unsupported for Bridge but required
// for the Renderer interface
func (h *Bridge) SelectFile() string {
func (h *Bridge) SelectFile(title string, filter string) string {
h.log.Warn("SelectFile() unsupported in bridge mode")
return ""
}
@@ -130,7 +128,7 @@ func (h *Bridge) SelectDirectory() string {
// SelectSaveFile is unsupported for Bridge but required
// for the Renderer interface
func (h *Bridge) SelectSaveFile() string {
func (h *Bridge) SelectSaveFile(title string, filter string) string {
h.log.Warn("SelectSaveFile() unsupported in bridge mode")
return ""
}
@@ -154,13 +152,20 @@ func (h *Bridge) NotifyEvent(event *messages.EventData) error {
// Marshall the data
data, err = json.Marshal(event.Data)
if err != nil {
h.log.Errorf("Cannot unmarshall JSON data in event: %s ", err.Error())
h.log.Errorf("Cannot marshal JSON data in event: %s ", err.Error())
return err
}
}
message := fmt.Sprintf("window.wails._.Notify('%s','%s')", event.Name, data)
dead := []session{}
// Double encode data to ensure everything is escaped correctly.
data, err = json.Marshal(string(data))
if err != nil {
h.log.Errorf("Cannot marshal JSON data in event: %s ", err.Error())
return err
}
message := "window.wails._.Notify('" + event.Name + "'," + string(data) + ")"
dead := []*session{}
for _, session := range h.sessions {
err := session.evalJS(message, notifyMessage)
if err != nil {
@@ -207,6 +212,9 @@ func (h *Bridge) SetTitle(title string) {
// for the Renderer interface
func (h *Bridge) Close() {
h.log.Debug("Shutting down")
for _, session := range h.sessions {
session.Shutdown()
}
err := h.server.Close()
if err != nil {
h.log.Errorf(err.Error())

View File

@@ -0,0 +1,9 @@
package renderer
// Autogenerated by Mewn - Do not alter
import "github.com/leaanthony/mewn"
func init() {
mewn.AddAsset(".", "../../runtime/assets/wails.js", "1f8b08000000000000ff94586d8f1bb711fe2b2ba255481f8f91dc14a977c318cee582a838df053ebbfda00a01b53b92e85b915b2e758a20f1bf17b3efba97a2050c7bc519728633cf3c33f468b533a9d7d650c38e8fca45208f216917234f1d3bea1585b95b30077ee74c84df02fe28acf365825bacc42579d4b1e3793c9af246181f43489a4d0637a52acfa96df772cbfb6fcfb815b91c4dfab5e0c5561aee452a817b91c9de550edcb1a317163fd9e974b7fc0aa91719acb481df9c2dc0f943a57604b3db8253cb1ce2d184afc1c72eb0c0bd70727875b233f5ee8c8ca43f146057d1fd61bbb4f9785cff2bbcbdf74e9bf567b51e8f5fb3f85c971f1f55be83987cb4d92e0712187f6d33f9fd77281bb576db6852bbebcfae5f25653a86f1981ae9a9618cff6d0c6d864ca257f43b94125b9922b2bd93198ff18fe82df59b30974e36cea50e94076a7679cef0382f1c75afb9ee38c960a576b9274f235edfc204c6df560e95555cfa201bb6b28e56308ab4890cf322a38e5bde5d17d8b103112c82586a93557e71cb588b2f873132f2399a9fdcf67da7d19f2a1adf43fc82b04330fa059c28c281714073f6494a1ac5264485b3dee225c54695777bd306abae02dc8067149210eea917a59cb040e76718f788cb12228c59ea49823900c61b4f66467bc2e9739f6781b12699c7903489abc34a7e86e56efde2ae5d609dd6ccacec8b4af940e99fca194ce54b7aab81deb573d6bda8950db47e515ee52f6a95ed756c7b1d5befb29cdc1560be7cba7971dfa13dbdd1fb456359bda0b86c0d68395ff4dca7da1c34a1c45cc68663fa962a7d98fd1c7b5ea8436e55164348ce9854afe85e9bcceec55ee9bc5c3a9dade1fdf325b1876569d307f0a20484358b1b25f8c383332a17da3cda07a086712d72306bbff971d2d50cc849023fb482042e2e989ec3821a16e8dfefef6e455d6e7a75a08eb1d0dd2dad11ab28c9ed9af0630e8f90c7866fa12cd51a620803e51dde28a524abb16306a2bc11e90a3043c9aa91ec5b940c855923841a1a4351d98856351e0c0b78cf3d2fcefad2639b9bb64c77793e927e3c6e3e4e27eae5847103fbe83767b7ba04da67de715b675527993d6a692ec825b9d85316f61b9d032de67a5193de8f13867aa92cc17fd65bb03b4f8708b2b4423725572acf236f2372612e48e4f516b2c8eebc883ec1bf7750fa68f6731c910bcd5860dcb3044dc8a3af8ffc55992c8738e50e903a62cb1d94367f84d885c4bb03e6096147f811c94f9bf5adda221633e555fc24cfc82b9a8554f97453c5d29ad2e620aa582330c21008eb33ea7aa4a446a720171543757a073caad15a53f293b3fb129ce8ea6f98c2e5ebba750d1a16f6b2c179ea0e85b7435ac6801b8999fba28dffcbdb0fcea9039d76547fb651acc17f5226b3db7f60a341f4cc278b9798fcdd64f2fdf4ddbbb77ffdeefbef26efde4ddf7c547e235cb597b25011c0b663f4c8d00a5fa3675d77444da44de99549b17f01637ee3ec3e42873f1f0ae800618cf511a62d52519aabb28c5419a9a83d90b040fd4697dc30ee11b197538ebfc555433067cdac9dbd842a8afc50b73fc3f8e5742411f6132925f59772ca42e09bb352b96b4b6533370b897f9d4ef345d31fd0e96d75d30425a2d8951bea06b9bcee3be9139ccd17a2cc75da7aa4dc7ab705e34b562fd329b2af3c9a015221248a127804e309f77565df9f39fb80e610f16d5dc33efaa50d03417d7261181f4d7a7cd79aa369e89dbeeab24eafda794695a55e9bd36918d49e45a709fcd0dd60c8a6d5ddbdec64735824ed3687038b67ff53bff7dc311cd67050f6384c876ec0604d042b30f4610c0dce110a6032790c7cd83fe4f0c7e9d44b7bfd2ac75fe5f116f63fd5b4113f9f8fe60b915a932a4f8d288b5c7b4a0461d56797c4f3939118a16d45d3ae15393949dc0fade0729ab80b39adadd40f04dc377aa096b141729b6a21ad139693489711968e8a1e55aeb3e8ab7a5465ea74e1239d81f17aa5c109c258e2e776814c3fb70b790c5519cded22d4ad1c44610bca1aabfabf5ad5ff9f55bd904ff80afbf0e0d1d4c2a6ab910a037d6e938e6f0df7c8b3dd8cd9f799b3f20769705a5cbf20edb921701f280bbc2590e7d94e8ccc20b5197cf934bbb2dbc21a3098780745ae52a0dffeabbcf876cd0961fdd27c72f94e5dae16c7b701457ffed3b80a823b1c1b4a28942b713e694a12dacb9399a943895a51a1ca12326c91ed0015475dfc413483072722fad8cc2011611d34595293ec8efabaa9d7196c38c4c93ac278aaceb895450bb7b6cf4bd20625faa6b3ea38f9a6cab983b52e3d64a3d188b496cedba61e5ac5ee9a8372ed4060c5591f673c831c3c44e8056f4e786f45dddc69b3c0625ca9ba3c0581f4c802bfb55eaf0ef1b39e83dcdcd395af58bca159c6ebc2ab961ae21a561eaea31fd56c8b95cb30757a983a18a42ea3e76943c7a2128cc7cc99ca3d115d238347c8ec91ac0839d8ae6b51cdc663dff287e35316aac6e343e01fb2ecbe2aa92737ac2f95d9b42a8fe6e9799d03fea2a4ae424c8cf0f0879786779a4b9b1d903dc164571b9d67880e188fafab21686630de57f7f72fb0deabb6fc2107c212c042fce0bdd3cb9d074a90d7092768ffdbb42c09e3202addfb0d807f3ffc21d2b2fc5cf919c3996f4f6ca2ceadcda07abcf7c20da8ec74ea7eaec137ce953f1d3eab6af0a30495080e3aeccc427d6bede319867af6dbd50dc2da803b8b80aebb3c4e82fc561e6fec3a76bc19d262cbabe496f1f1ce3cc9d25df368bd331f77b9d7450ef11dbfde6a1f5ff35f4139bf04759ed9fef184f34a728f3818f056554433e3c13daabc7aa3227b399cb5bbd5e1b08db34860950f1fd20763f739646b889fbcbb466866309365dd2ca6fa4dd1cee0b78936ade3035e309c7c831c8707518cd2eff1d7c18c32434386b27075f6c6e3b7ac6dc1d654257efe1f569e57cf8ee1167163d7a2e9456fdebc79135da9dd7ae3a32f6653314956134e8432c2f86b5b7bc2c4d9e835ad2f9f6e50035ed7b8d106a25b8b5afe75ad2b9befb6a6d173afeb5521401dcb02bfc687a0cecb185fcb901116162cf94f000000ffff04075a367c140000")
}

View File

@@ -1,7 +1,8 @@
package renderer
import (
"sync"
"time"
"unsafe"
"github.com/gorilla/websocket"
"github.com/leaanthony/mewn"
@@ -20,7 +21,22 @@ type session struct {
ipc interfaces.IPCManager
// Mutex for writing to the socket
lock sync.Mutex
shutdown chan bool
writeChan chan []byte
done bool
}
func newSession(conn *websocket.Conn, bindingCache []string, ipc interfaces.IPCManager, logger *logger.CustomLogger, eventMgr interfaces.EventManager) *session {
return &session{
conn: conn,
bindingCache: bindingCache,
ipc: ipc,
log: logger,
eventManager: eventMgr,
shutdown: make(chan bool),
writeChan: make(chan []byte, 100),
}
}
// Identifier returns a string identifier for the remote connection.
@@ -33,19 +49,15 @@ func (s *session) Identifier() string {
}
func (s *session) sendMessage(msg string) error {
s.lock.Lock()
defer s.lock.Unlock()
if err := s.conn.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil {
s.log.Debug(err.Error())
return err
if !s.done {
s.writeChan <- *(*[]byte)(unsafe.Pointer(&msg))
}
return nil
}
func (s *session) start(firstSession bool) {
s.log.Infof("Connected to frontend.")
go s.writePump()
wailsRuntime := mewn.String("../../runtime/assets/wails.js")
s.evalJS(wailsRuntime, wailsRuntimeMessage)
@@ -74,6 +86,10 @@ func (s *session) start(firstSession bool) {
s.log.Debugf("Got message: %#v\n", string(buffer))
s.ipc.Dispatch(string(buffer), s.Callback)
if s.done {
break
}
}
}
@@ -83,8 +99,38 @@ func (s *session) Callback(data string) error {
}
func (s *session) evalJS(js string, mtype messageType) error {
// Prepend message type to message
return s.sendMessage(mtype.toString() + js)
}
// Shutdown
func (s *session) Shutdown() {
s.done = true
s.shutdown <- true
s.log.Debugf("session %v exit", s.Identifier())
}
// writePump pulls messages from the writeChan and sends them to the client
// since it uses a channel to read the messages the socket is protected without locks
func (s *session) writePump() {
s.log.Debugf("Session %v - writePump start", s.Identifier())
for {
select {
case msg, ok := <-s.writeChan:
s.conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
if !ok {
s.log.Debug("writeChan was closed!")
s.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
if err := s.conn.WriteMessage(websocket.TextMessage, msg); err != nil {
s.log.Debug(err.Error())
return
}
case <-s.shutdown:
break
}
}
s.log.Debug("writePump exiting...")
}

File diff suppressed because one or more lines are too long

View File

@@ -245,7 +245,7 @@ func (w *WebView) NewBinding(methodName string) error {
}
// SelectFile opens a dialog that allows the user to select a file
func (w *WebView) SelectFile() string {
func (w *WebView) SelectFile(title string, filter string) string {
var result string
// We need to run this on the main thread, however Dispatch is
@@ -255,7 +255,7 @@ func (w *WebView) SelectFile() string {
wg.Add(1)
go func() {
w.window.Dispatch(func() {
result = w.window.Dialog(wv.DialogTypeOpen, 0, "Select File", "")
result = w.window.Dialog(wv.DialogTypeOpen, 0, title, "", filter)
wg.Done()
})
}()
@@ -273,7 +273,7 @@ func (w *WebView) SelectDirectory() string {
wg.Add(1)
go func() {
w.window.Dispatch(func() {
result = w.window.Dialog(wv.DialogTypeOpen, wv.DialogFlagDirectory, "Select Directory", "")
result = w.window.Dialog(wv.DialogTypeOpen, wv.DialogFlagDirectory, "Select Directory", "", "")
wg.Done()
})
}()
@@ -282,7 +282,7 @@ func (w *WebView) SelectDirectory() string {
}
// SelectSaveFile opens a dialog that allows the user to select a file to save
func (w *WebView) SelectSaveFile() string {
func (w *WebView) SelectSaveFile(title string, filter string) string {
var result string
// We need to run this on the main thread, however Dispatch is
// non-blocking so we launch this in a goroutine and wait for
@@ -291,7 +291,7 @@ func (w *WebView) SelectSaveFile() string {
wg.Add(1)
go func() {
w.window.Dispatch(func() {
result = w.window.Dialog(wv.DialogTypeSave, 0, "Save file", "")
result = w.window.Dialog(wv.DialogTypeSave, 0, title, "", filter)
wg.Done()
})
}()
@@ -329,7 +329,14 @@ func (w *WebView) NotifyEvent(event *messages.EventData) error {
}
}
message := fmt.Sprintf("wails._.Notify('%s','%s')", event.Name, data)
// Double encode data to ensure everything is escaped correctly.
data, err = json.Marshal(string(data))
if err != nil {
w.log.Errorf("Cannot marshal JSON data in event: %s ", err.Error())
return err
}
message := "window.wails._.Notify('" + event.Name + "'," + string(data) + ")"
return w.evalJS(message)
}

View File

@@ -74,9 +74,9 @@ static inline void CgoWebViewSetColor(void *w, uint8_t r, uint8_t g, uint8_t b,
}
static inline void CgoDialog(void *w, int dlgtype, int flags,
char *title, char *arg, char *res, size_t ressz) {
char *title, char *arg, char *res, size_t ressz, char *filter) {
webview_dialog(w, dlgtype, flags,
(const char*)title, (const char*) arg, res, ressz);
(const char*)title, (const char*) arg, res, ressz, filter);
}
static inline int CgoWebViewEval(void *w, char *js) {
@@ -186,7 +186,7 @@ type WebView interface {
// Dialog() opens a system dialog of the given type and title. String
// argument can be provided for certain dialogs, such as alert boxes. For
// alert boxes argument is a message inside the dialog box.
Dialog(dlgType DialogType, flags int, title string, arg string) string
Dialog(dlgType DialogType, flags int, title string, arg string, filter string) string
// Terminate() breaks the main UI loop. This method must be called from the main thread
// only. See Dispatch() for more details.
Terminate()
@@ -311,7 +311,7 @@ func (w *webview) SetFullscreen(fullscreen bool) {
C.CgoWebViewSetFullscreen(w.w, C.int(boolToInt(fullscreen)))
}
func (w *webview) Dialog(dlgType DialogType, flags int, title string, arg string) string {
func (w *webview) Dialog(dlgType DialogType, flags int, title string, arg string, filter string) string {
const maxPath = 4096
titlePtr := C.CString(title)
defer C.free(unsafe.Pointer(titlePtr))
@@ -319,8 +319,10 @@ func (w *webview) Dialog(dlgType DialogType, flags int, title string, arg string
defer C.free(unsafe.Pointer(argPtr))
resultPtr := (*C.char)(C.calloc((C.size_t)(unsafe.Sizeof((*C.char)(nil))), (C.size_t)(maxPath)))
defer C.free(unsafe.Pointer(resultPtr))
filterPtr := C.CString(filter)
defer C.free(unsafe.Pointer(filterPtr))
C.CgoDialog(w.w, C.int(dlgType), C.int(flags), titlePtr,
argPtr, resultPtr, C.size_t(maxPath))
argPtr, resultPtr, C.size_t(maxPath), filterPtr)
return C.GoString(resultPtr)
}

View File

@@ -139,7 +139,7 @@ struct webview_priv
#define DEFAULT_URL \
"data:text/" \
"html,%3C%21DOCTYPE%20html%3E%0A%3Chtml%20lang=%22en%22%3E%0A%3Chead%3E%" \
"3Cmeta%20charset=%22utf-8%22%3E%3Cmeta%20http-equiv=%22IE=edge%22%" \
"3Cmeta%20charset=%22utf-8%22%3E%3Cmeta%20http-equiv=%22IE=edge%22%" \
"20content=%22IE=edge%22%3E%3C%2Fhead%3E%0A%3Cbody%3E%3Cdiv%20id=%22app%22%" \
"3E%3C%2Fdiv%3E%3Cscript%20type=%22text%2Fjavascript%22%3E%3C%2Fscript%3E%" \
"3C%2Fbody%3E%0A%3C%2Fhtml%3E"
@@ -174,7 +174,7 @@ struct webview_priv
WEBVIEW_API void webview_dialog(struct webview *w,
enum webview_dialog_type dlgtype, int flags,
const char *title, const char *arg,
char *result, size_t resultsz);
char *result, size_t resultsz, char *filter);
WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn,
void *arg);
WEBVIEW_API void webview_terminate(struct webview *w);
@@ -418,7 +418,7 @@ struct webview_priv
WEBVIEW_API void webview_dialog(struct webview *w,
enum webview_dialog_type dlgtype, int flags,
const char *title, const char *arg,
char *result, size_t resultsz)
char *result, size_t resultsz, char *filter)
{
GtkWidget *dlg;
if (result != NULL)
@@ -438,6 +438,17 @@ struct webview_priv
"_Cancel", GTK_RESPONSE_CANCEL,
(dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ? "_Open" : "_Save"),
GTK_RESPONSE_ACCEPT, NULL);
if (filter[0] != '\0') {
GtkFileFilter *file_filter = gtk_file_filter_new();
gchar **filters = g_strsplit(filter, ",", -1);
gint i;
for(i = 0; filters && filters[i]; i++) {
gtk_file_filter_add_pattern(file_filter, filters[i]);
}
gtk_file_filter_set_name(file_filter, filter);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dlg), file_filter);
g_strfreev(filters);
}
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), FALSE);
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dlg), FALSE);
gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dlg), TRUE);
@@ -1227,7 +1238,7 @@ struct webview_priv
}
VariantInit(&myURL);
myURL.vt = VT_BSTR;
// #ifndef UNICODE
// #ifndef UNICODE
{
wchar_t *buffer = webview_to_utf16(webPageName);
if (buffer == NULL)
@@ -1237,9 +1248,9 @@ struct webview_priv
myURL.bstrVal = SysAllocString(buffer);
GlobalFree(buffer);
}
// #else
// myURL.bstrVal = SysAllocString(webPageName);
// #endif
// #else
// myURL.bstrVal = SysAllocString(webPageName);
// #endif
if (!myURL.bstrVal)
{
badalloc:
@@ -1277,7 +1288,7 @@ struct webview_priv
if (!SafeArrayAccessData(sfArray, (void **)&pVar))
{
pVar->vt = VT_BSTR;
// #ifndef UNICODE
// #ifndef UNICODE
{
wchar_t *buffer = webview_to_utf16(url);
if (buffer == NULL)
@@ -1287,9 +1298,9 @@ struct webview_priv
bstr = SysAllocString(buffer);
GlobalFree(buffer);
}
// #else
// bstr = SysAllocString(url);
// #endif
// #else
// bstr = SysAllocString(url);
// #endif
if ((pVar->bstrVal = bstr))
{
htmlDoc2->lpVtbl->write(htmlDoc2, sfArray);
@@ -1410,6 +1421,8 @@ struct webview_priv
wc.hInstance = hInstance;
wc.lpfnWndProc = wndproc;
wc.lpszClassName = classname;
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(100));
wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(100));
RegisterClassEx(&wc);
style = WS_OVERLAPPEDWINDOW;
@@ -1444,12 +1457,12 @@ struct webview_priv
rect.right - rect.left, rect.bottom - rect.top,
HWND_DESKTOP, NULL, hInstance, (void *)w);
#else
w->priv.hwnd =
w->priv.hwnd =
CreateWindowEx(0, classname, w->title, style, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
HWND_DESKTOP, NULL, hInstance, (void *)w);
#endif
if (w->priv.hwnd == 0)
{
OleUninitialize();
@@ -1466,8 +1479,7 @@ struct webview_priv
#else
SetWindowText(w->priv.hwnd, w->title);
#endif
ShowWindow(w->priv.hwnd, SW_SHOWDEFAULT);
UpdateWindow(w->priv.hwnd);
SetFocus(w->priv.hwnd);
@@ -1494,6 +1506,11 @@ struct webview_priv
case WM_KEYDOWN:
case WM_KEYUP:
{
// Disable refresh when pressing F5 on windows
if (msg.wParam == VK_F5)
{
break;
}
HRESULT r = S_OK;
IWebBrowser2 *webBrowser2;
IOleObject *browser = *w->priv.browser;
@@ -1603,7 +1620,7 @@ struct webview_priv
WEBVIEW_API void webview_set_title(struct webview *w, const char *title)
{
#ifdef UNICODE
#ifdef UNICODE
wchar_t *u16title = webview_to_utf16(title);
if (u16title == NULL)
{
@@ -1611,12 +1628,11 @@ struct webview_priv
}
SetWindowText(w->priv.hwnd, u16title);
GlobalFree(u16title);
#else
#else
SetWindowText(w->priv.hwnd, title);
#endif
#endif
}
WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen)
{
if (w->priv.is_fullscreen == !!fullscreen)
@@ -1777,7 +1793,7 @@ struct webview_priv
WEBVIEW_API void webview_dialog(struct webview *w,
enum webview_dialog_type dlgtype, int flags,
const char *title, const char *arg,
char *result, size_t resultsz)
char *result, size_t resultsz, char *filter)
{
if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
dlgtype == WEBVIEW_DIALOG_TYPE_SAVE)
@@ -1817,6 +1833,32 @@ struct webview_priv
FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS |
FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE;
}
if (filter[0] != '\0')
{
int count;
int i=0;
char* token;
char* filter_dup = strdup(filter);
for (count=1; filter[count]; filter[count]==',' ? count++ : *filter++);
COMDLG_FILTERSPEC rgSpec[count];
char* filters[count];
token = strtok(filter_dup, ",");
while(token != NULL)
{
filters[i] = token;
token = strtok(NULL, ",");
i++;
}
for (int i=0; i < count; i++) {
wchar_t *wFilter = (wchar_t *)malloc(4096);
MultiByteToWideChar(CP_ACP, 0, filters[i], -1, wFilter, 4096);
rgSpec[i].pszName = wFilter;
rgSpec[i].pszSpec = wFilter;
}
if (dlg->lpVtbl->SetFileTypes(dlg, count, rgSpec) != S_OK) {
goto error_dlg;
}
}
if (dlg->lpVtbl->GetOptions(dlg, &opts) != S_OK)
{
goto error_dlg;
@@ -1927,22 +1969,26 @@ struct webview_priv
[script setValue:self forKey:@"external"];
}
static void webview_run_input_open_panel(id self, SEL cmd, id webview,
id listener, BOOL allowMultiple) {
static void webview_run_input_open_panel(id self, SEL cmd, id webview,
id listener, BOOL allowMultiple)
{
char filename[256] = "";
struct webview *w =
(struct webview *)objc_getAssociatedObject(self, "webview");
webview_dialog(w, WEBVIEW_DIALOG_TYPE_OPEN, WEBVIEW_DIALOG_FLAG_FILE, "", "",
filename, 255);
if (strlen(filename)) {
filename, 255, "");
filename[255] = '\0';
if (strlen(filename) > 0)
{
[listener chooseFilename:[NSString stringWithUTF8String:filename]];
} else {
}
else
{
[listener cancel];
}
}
static void webview_external_invoke(id self, SEL cmd, id arg)
{
struct webview *w =
@@ -1955,7 +2001,7 @@ static void webview_run_input_open_panel(id self, SEL cmd, id webview,
{
return;
}
w->external_invoke_cb(w, [(NSString *)(arg)UTF8String]);
w->external_invoke_cb(w, [(NSString *)(arg) UTF8String]);
}
WEBVIEW_API int webview_init(struct webview *w)
@@ -2194,12 +2240,16 @@ static void webview_run_input_open_panel(id self, SEL cmd, id webview,
WEBVIEW_API void webview_dialog(struct webview *w,
enum webview_dialog_type dlgtype, int flags,
const char *title, const char *arg,
char *result, size_t resultsz)
char *result, size_t resultsz, char *filter)
{
if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
dlgtype == WEBVIEW_DIALOG_TYPE_SAVE)
{
NSSavePanel *panel;
NSString *filter_str = [NSString stringWithUTF8String:filter];
filter_str = [filter_str stringByReplacingOccurrencesOfString:@"*."
withString:@""];
NSArray *fileTypes = [filter_str componentsSeparatedByString:@","];
if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN)
{
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
@@ -2212,6 +2262,10 @@ static void webview_run_input_open_panel(id self, SEL cmd, id webview,
{
[openPanel setCanChooseFiles:YES];
[openPanel setCanChooseDirectories:NO];
if(filter[0] != NULL)
{
[openPanel setAllowedFileTypes:fileTypes];
}
}
[openPanel setResolvesAliases:NO];
[openPanel setAllowsMultipleSelection:NO];
@@ -2225,6 +2279,10 @@ static void webview_run_input_open_panel(id self, SEL cmd, id webview,
[panel setShowsHiddenFiles:YES];
[panel setExtensionHidden:NO];
[panel setCanSelectHiddenExtension:NO];
if(filter[0] != NULL)
{
[panel setAllowedFileTypes:fileTypes];
}
[panel setTreatsFilePackagesAsDirectories:YES];
[panel beginSheetModalForWindow:w->priv.window
completionHandler:^(NSInteger result) {

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,7 @@
package runtime
import "github.com/wailsapp/wails/lib/interfaces"
import "strings"
// Dialog exposes an interface to native dialogs
type Dialog struct {
@@ -15,8 +16,16 @@ func NewDialog(renderer interfaces.Renderer) *Dialog {
}
// SelectFile prompts the user to select a file
func (r *Dialog) SelectFile() string {
return r.renderer.SelectFile()
func (r *Dialog) SelectFile(params ...string) string {
title := "Select File"
filter := ""
if len(params) > 0 {
title = params[0]
}
if len(params) > 1 {
filter = strings.Replace(params[1], " ", "", -1)
}
return r.renderer.SelectFile(title, filter)
}
// SelectDirectory prompts the user to select a directory
@@ -25,6 +34,14 @@ func (r *Dialog) SelectDirectory() string {
}
// SelectSaveFile prompts the user to select a file for saving
func (r *Dialog) SelectSaveFile() string {
return r.renderer.SelectSaveFile()
func (r *Dialog) SelectSaveFile(params ...string) string {
title := "Select Save"
filter := ""
if len(params) > 0 {
title = params[0]
}
if len(params) > 1 {
filter = strings.Replace(params[1], " ", "", -1)
}
return r.renderer.SelectSaveFile(title, filter)
}

View File

@@ -1,9 +1,9 @@
package runtime
import homedir "github.com/mitchellh/go-homedir"
import "os"
// FileSystem exposes file system utilities to the runtime
type FileSystem struct {}
type FileSystem struct{}
// NewFileSystem creates a new FileSystem struct
func NewFileSystem() *FileSystem {
@@ -12,5 +12,5 @@ func NewFileSystem() *FileSystem {
// HomeDir returns the user's home directory
func (r *FileSystem) HomeDir() (string, error) {
return homedir.Dir()
return os.UserHomeDir()
}

File diff suppressed because it is too large Load Diff

View File

@@ -29,15 +29,15 @@
},
"homepage": "https://github.com/wailsapp/runtime#readme",
"devDependencies": {
"@babel/cli": "^7.5.0",
"@babel/core": "^7.5.4",
"@babel/plugin-transform-object-assign": "^7.2.0",
"@babel/preset-env": "^7.5.4",
"babel-loader": "^8.0.6",
"babel-preset-minify": "^0.5.0",
"core-js": "^3.1.4",
"eslint": "^6.5.1",
"webpack": "^4.35.3",
"webpack-cli": "^3.3.5"
"@babel/cli": "^7.11.5",
"@babel/core": "^7.11.5",
"@babel/plugin-transform-object-assign": "^7.10.4",
"@babel/preset-env": "^7.11.5",
"babel-loader": "^8.1.0",
"babel-preset-minify": "^0.5.1",
"core-js": "^3.6.5",
"eslint": "^7.8.1",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@wailsapp/runtime",
"version": "1.0.10",
"version": "1.0.11",
"description": "Wails Javascript runtime library",
"main": "main.js",
"types": "runtime.d.ts",
@@ -25,4 +25,4 @@
"devDependencies": {
"dts-gen": "^0.5.8"
}
}
}

View File

@@ -6,12 +6,12 @@ declare const wailsapp__runtime: {
OpenURL(url: string): Promise<any>;
};
Events: {
Acknowledge(eventName: string): void;
Emit(eventName: string): void;
Heartbeat(eventName: string, timeInMilliseconds: number, callback: () => void): void;
On(eventName: string, callback: () => void): void;
OnMultiple(eventName: string, callback: () => void, maxCallbacks: number): void;
Once(eventName: string, callback: () => void): void;
Acknowledge(eventName: string): void;
Emit(eventName: string, data?: any): void;
Heartbeat(eventName: string, timeInMilliseconds: number, callback: (data?: any) => void): void;
On(eventName: string, callback: (data?: any) => void): void;
OnMultiple(eventName: string, callback: (data?: any) => void, maxCallbacks: number): void;
Once(eventName: string, callback: (data?: any) => void): void;
};
Init(callback: () => void): void;
Log: {

View File

@@ -1,5 +1,13 @@
#!/usr/bin/env bash
echo "**** Checking if Wails passes unit tests ****"
if ! go test ./...
then
echo ""
echo "ERROR: Unit tests failed!"
exit 1;
fi
# Build runtime
echo "**** Building Runtime ****"
cd runtime/js
@@ -15,9 +23,23 @@ cd lib/renderer
mewn
cd ../..
echo "**** Installing Wails locally ****"
cd cmd/wails
go install
echo "**** Checking if Wails compiles ****"
if ! go build .
then
echo ""
echo "ERROR: Build failed!"
exit 1;
fi
echo "**** Installing Wails locally ****"
if ! go install
then
echo ""
echo "ERROR: Install failed!"
exit 1;
fi
cd ../..
echo "**** Tidying the mods! ****"

13
v2/.vscode/settings.json vendored Normal file
View File

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

40
v2/NOTES.md Normal file
View File

@@ -0,0 +1,40 @@
# Packing linux
* create app, app.desktop, app.png (512x512)
* chmod +x app!
* ./linuxdeploy-x86_64.AppImage --appdir AppDir -i react.png -d react.desktop -e react --output appimage
# Wails Doctor
Tested on:
* Debian 8
* Ubuntu 20.04
* Ubuntu 19.10
* Solus 4.1
* Centos 8
* Gentoo
* OpenSUSE/leap
* Fedora 31
### Development
Add a new package manager processor here: `v2/internal/system/packagemanager/`. IsAvailable should work even if the package is installed.
Add your new package manager to the list of package managers in `v2/internal/system/packagemanager/packagemanager.go`:
```
var db = map[string]PackageManager{
"eopkg": NewEopkg(),
"apt": NewApt(),
"yum": NewYum(),
"pacman": NewPacman(),
"emerge": NewEmerge(),
"zypper": NewZypper(),
}
```
## Gentoo
* Setup docker image using: emerge-webrsync -x -v

5
v2/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Wails v2 ALPHA
This branch contains WORK IN PROGRESS! There are no guarantees. Use at your peril!
This document will be updated as progress is made.

View File

@@ -0,0 +1,113 @@
package build
import (
"fmt"
"io"
"runtime"
"strings"
"time"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
// AddBuildSubcommand adds the `build` command for the Wails application
func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
outputType := "desktop"
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
command := app.NewSubCommand("build", "Builds the application")
// Setup target type flag
description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
// Setup production flag
production := false
command.BoolFlag("production", "Build in production mode", &production)
// Setup pack flag
pack := false
command.BoolFlag("pack", "Create a platform specific package", &pack)
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
// Setup Platform flag
platform := runtime.GOOS
command.StringFlag("platform", "Platform to target", &platform)
// Quiet Build
quiet := false
command.BoolFlag("q", "Supress output to console", &quiet)
// ldflags to pass to `go`
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
// Log to file
logFile := ""
command.StringFlag("l", "Log to file", &logFile)
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
// Validate output type
if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType)
}
if !quiet {
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
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Mode: mode,
Pack: pack,
Platform: platform,
LDFlags: ldflags,
Compiler: compilerCommand,
}
return doBuild(buildOptions)
})
}
// doBuild is our main build command
func doBuild(buildOptions *build.Options) error {
// Start Time
start := time.Now()
outputFilename, err := build.Build(buildOptions)
if err != nil {
return err
}
// Output stats
elapsed := time.Since(start)
buildOptions.Logger.Println("")
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Println("")
return nil
}

View File

@@ -0,0 +1,261 @@
package dev
import (
"fmt"
"io"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"time"
"github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("dev", "Development mode")
outputType := "desktop"
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
// Setup target type flag
description := "Type of application to develop. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
// Passthrough ldflags
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
// compiler command
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
// extensions to trigger rebuilds
extensions := "go"
command.StringFlag("m", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
command.Action(func() error {
// Validate inputs
if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType)
}
// Create logger
logger := clilogger.New(w)
app.PrintBanner()
// TODO: Check you are in a project directory
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()
var debugBinaryProcess *process.Process = nil
var buildFrontend bool = true
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
// Setup signal handler
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
debounceQuit := make(chan bool, 1)
// Do initial build
logger.Println("Building application for development...")
debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Println("event: %+v", event)
// Check for new directories
if event.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list
if fs.DirExists(event.Name) {
if !strings.Contains(event.Name, "node_modules") {
watcher.Add(event.Name)
logger.Println("Watching directory: %s", event.Name)
}
}
return
}
// 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")
break
}
}
if !rebuild {
logger.Println("Filename change: %s did not match extension list %s", event.Name, extensions)
return
}
if buildFrontend {
logger.Println("Full rebuild triggered: %s updated", event.Name)
} else {
logger.Println("Partial build triggered: %s updated", event.Name)
}
// Do a rebuild
// Try and build the app
newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
// If we have a new process, save it
if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess
}
}
})
// Get project dir
dir, err := os.Getwd()
if err != nil {
return err
}
// Get all subdirectories
dirs, err := fs.GetSubdirectories(dir)
if err != nil {
return err
}
// 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)
err = watcher.Add(dir)
if err != nil {
logger.Fatal(err.Error())
}
})
// Wait until we get a quit signal
quit := false
for quit == false {
select {
case <-quitChannel:
println()
// Notify debouncer to quit
debounceQuit <- true
quit = true
}
}
// Kill the current program if running
if debugBinaryProcess != nil {
debugBinaryProcess.Kill()
}
logger.Println("Development mode exited")
return nil
})
return nil
}
// Credit: https://drailing.net/2018/01/debounce-function-for-golang/
func debounce(interval time.Duration, input chan fsnotify.Event, quitChannel chan bool, cb func(arg fsnotify.Event)) {
var item fsnotify.Event
timer := time.NewTimer(interval)
exit:
for {
select {
case item = <-input:
timer.Reset(interval)
case <-timer.C:
if item.Name != "" {
cb(item)
}
case <-quitChannel:
break exit
}
}
}
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend)
println()
if err != nil {
logger.Println("[ERROR] Build Failed: %s", err.Error())
return nil
}
logger.Println("Build new binary: %s", appBinary)
// Kill existing binary if need be
if debugBinaryProcess != nil {
killError := debugBinaryProcess.Kill()
if killError != nil {
logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
}
debugBinaryProcess = nil
}
// TODO: Generate `backend.js`
// Start up new binary
newProcess := process.NewProcess(logger, appBinary)
err = newProcess.Start()
if err != nil {
// Remove binary
fs.DeleteFile(appBinary)
logger.Fatal("Unable to start application: %s", err.Error())
}
return newProcess
}
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
// Create random output file
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Mode: build.Debug,
Pack: false,
Platform: runtime.GOOS,
LDFlags: ldflags,
Compiler: compilerCommand,
OutputFile: outputFile,
IgnoreFrontend: !buildFrontend,
}
return build.Build(buildOptions)
}

View File

@@ -0,0 +1,154 @@
package doctor
import (
"fmt"
"io"
"log"
"os"
"runtime"
"strings"
"text/tabwriter"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// AddSubcommand adds the `doctor` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("doctor", "Diagnose your environment")
command.Action(func() error {
logger := clilogger.New(w)
app.PrintBanner()
logger.Print("Scanning system - please wait...")
// Get system info
info, err := system.GetInfo()
if err != nil {
return err
}
logger.Println("Done.")
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
// Write out the system information
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "System\n")
fmt.Fprintf(w, "------\n")
fmt.Fprintf(w, "%s\t%s\n", "OS:", info.OS.Name)
fmt.Fprintf(w, "%s\t%s\n", "Version: ", info.OS.Version)
fmt.Fprintf(w, "%s\t%s\n", "ID:", info.OS.ID)
// Exit early if PM not found
if info.PM == nil {
fmt.Fprintf(w, "\n%s\t%s", "Package Manager:", "Not Found")
w.Flush()
println()
return nil
}
fmt.Fprintf(w, "%s\t%s\n", "Package Manager: ", info.PM.Name())
// Output Go Information
fmt.Fprintf(w, "%s\t%s\n", "Go Version:", runtime.Version())
fmt.Fprintf(w, "%s\t%s\n", "Platform:", runtime.GOOS)
fmt.Fprintf(w, "%s\t%s\n", "Architecture:", runtime.GOARCH)
// Output Dependencies Status
var dependenciesMissing = []string{}
var externalPackages = []*packagemanager.Dependancy{}
var dependenciesAvailableRequired = 0
var dependenciesAvailableOptional = 0
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Dependency\tPackage Name\tStatus\tVersion\n")
fmt.Fprintf(w, "----------\t------------\t------\t-------\n")
// Loop over dependencies
for _, dependency := range info.Dependencies {
name := dependency.Name
if dependency.Optional {
name += "*"
}
packageName := "Unknown"
status := "Not Found"
// If we found the package
if dependency.PackageName != "" {
packageName = dependency.PackageName
// If it's installed, update the status
if dependency.Installed {
status = "Installed"
} else {
// Generate meaningful status text
status = "Available"
if dependency.Optional {
dependenciesAvailableOptional++
} else {
dependenciesAvailableRequired++
}
}
} else {
if !dependency.Optional {
dependenciesMissing = append(dependenciesMissing, dependency.Name)
}
if dependency.External {
externalPackages = append(externalPackages, dependency)
}
}
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")
w.Flush()
logger.Println("")
logger.Println("Diagnosis")
logger.Println("---------\n")
// Generate an appropriate diagnosis
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
logger.Println("Your system is ready for Wails development!")
}
if dependenciesAvailableRequired != 0 {
log.Println("Install required packages using: " + 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)
}
}
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")
}
log.Println("")
return nil
})
return nil
}

View File

@@ -0,0 +1,91 @@
package generate
import (
"io"
"time"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/parser"
)
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("generate", "Code Generation Tools")
// Backend API
backendAPI := command.NewSubCommand("module", "Generates a JS module for the frontend to interface with the backend")
// Quiet Init
quiet := false
backendAPI.BoolFlag("q", "Supress output to console", &quiet)
backendAPI.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
app.PrintBanner()
logger.Print("Generating Javascript module for Go code...")
// Start Time
start := time.Now()
p, err := parser.GenerateWailsFrontendPackage()
if err != nil {
return err
}
logger.Println("done.")
logger.Println("")
elapsed := time.Since(start)
packages := p.Packages
// Print report
for _, pkg := range p.Packages {
if pkg.ShouldGenerate() {
logPackage(pkg, logger)
}
}
logger.Println("%d packages parsed in %s.", len(packages), elapsed)
return nil
})
return nil
}
func logPackage(pkg *parser.Package, logger *clilogger.CLILogger) {
logger.Println("Processed Go package '" + pkg.Gopackage.Name + "' as '" + pkg.Name + "'")
for _, strct := range pkg.Structs() {
logger.Println("")
logger.Println(" Processed struct '" + strct.Name + "'")
if strct.IsBound {
for _, method := range strct.Methods {
logger.Println(" Bound method '" + method.Name + "'")
}
}
if strct.IsUsedAsData {
for _, field := range strct.Fields {
if !field.Ignored {
logger.Print(" Processed ")
if field.IsOptional {
logger.Print("optional ")
}
logger.Println("field '" + field.Name + "' as '" + field.JSName() + "'")
}
}
}
}
logger.Println("")
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
}

View File

@@ -0,0 +1,130 @@
package initialise
import (
"fmt"
"io"
"strings"
"time"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/templates"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// AddSubcommand adds the `init` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Load the template shortnames
validShortNames, err := templates.TemplateShortNames()
if err != nil {
return err
}
command := app.NewSubCommand("init", "Initialise a new Wails project")
// Setup template name flag
templateName := "vanilla"
description := "Name of template to use. Valid tempates: " + validShortNames.Join(" ")
command.StringFlag("t", description, &templateName)
// Setup project name
projectName := ""
command.StringFlag("n", "Name of project", &projectName)
// Setup project directory
projectDirectory := ""
command.StringFlag("d", "Project directory", &projectDirectory)
// Quiet Init
quiet := false
command.BoolFlag("q", "Supress output to console", &quiet)
// VSCode project files
vscode := false
command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
// List templates
list := false
command.BoolFlag("l", "List templates", &list)
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
// Are we listing templates?
if list {
app.PrintBanner()
err := templates.OutputList(logger)
logger.Println("")
return err
}
// Validate output type
if !validShortNames.Contains(templateName) {
logger.Print(fmt.Sprintf("[ERROR] Template '%s' is not valid", templateName))
logger.Println("")
command.PrintHelp()
return nil
}
// Validate name
if len(projectName) == 0 {
logger.Println("ERROR: Project name required")
logger.Println("")
command.PrintHelp()
return nil
}
if !quiet {
app.PrintBanner()
}
task := fmt.Sprintf("Initialising Project %s", strings.Title(projectName))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
// Create Template Options
options := &templates.Options{
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
GenerateVSCode: vscode,
}
return initProject(options)
})
return nil
}
// initProject is our main init command
func initProject(options *templates.Options) error {
// Start Time
start := time.Now()
// Install the template
err := templates.Install(options)
if err != nil {
return err
}
// Output stats
elapsed := time.Since(start)
options.Logger.Println("")
options.Logger.Println("Project Name: " + options.ProjectName)
options.Logger.Println("Project Directory: " + options.TargetDir)
options.Logger.Println("Project Template: " + options.TemplateName)
options.Logger.Println("")
if options.GenerateVSCode {
options.Logger.Println("VSCode config files generated.")
}
options.Logger.Println("")
options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Println("")
return nil
}

50
v2/cmd/wails/main.go Normal file
View File

@@ -0,0 +1,50 @@
package main
import (
"os"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
)
func fatal(message string) {
println(message)
os.Exit(1)
}
func main() {
var err error
version := "v2.0.0-alpha"
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
build.AddBuildSubcommand(app, os.Stdout)
err = initialise.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = doctor.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = dev.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = generate.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = app.Run()
if err != nil {
println("\n\nERROR: " + err.Error())
}
}

26
v2/go.mod Normal file
View File

@@ -0,0 +1,26 @@
module github.com/wailsapp/wails/v2
go 1.13
require (
github.com/davecgh/go-spew v1.1.1
github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9
github.com/imdario/mergo v0.3.11
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/gosod v0.0.4
github.com/leaanthony/slicer v1.5.0
github.com/matryer/is v1.4.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/olekukonko/tablewriter v0.0.4
github.com/pkg/errors v0.9.1
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.6 // indirect
github.com/xyproto/xpm v1.2.1
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
nhooyr.io/websocket v1.8.6
)

130
v2/go.sum Normal file
View File

@@ -0,0 +1,130 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/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/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82 h1:shxDsb9Dz27xzk3A0DxP0JuJnZMpKrdg8+E14eiUAX4=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=

9
v2/internal/app/debug.go Normal file
View File

@@ -0,0 +1,9 @@
// +build debug
package app
// Init initialises the application for a debug environment
func (a *App) Init() error {
a.debug = true
return nil
}

View File

@@ -0,0 +1,42 @@
// +build !desktop,!hybrid,!server
package app
// This is the default application that will get run if the user compiles using `go build`.
// The reason we want to prevent that is that the `wails build` command does a lot of behind
// the scenes work such as asset compilation. If we allow `go build`, the state of these assets
// will be unknown and the application will not work as expected.
import (
"os"
"github.com/wailsapp/wails/v2/pkg/options"
)
// App defines a Wails application structure
type App struct {
Title string
Width int
Height int
Resizable bool
// Indicates if the app is running in debug mode
debug bool
}
// CreateApp returns a null application
func CreateApp(options *options.App) *App {
return &App{}
}
// Run the application
func (a *App) Run() error {
println(`FATAL: This application was built using "go build". This is unsupported. Please compile using "wails build".`)
os.Exit(1)
return nil
}
// Bind the dummy interface
func (a *App) Bind(dummy interface{}) error {
return nil
}

159
v2/internal/app/desktop.go Normal file
View File

@@ -0,0 +1,159 @@
// +build desktop,!server
package app
import (
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/signal"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/internal/runtime"
)
// App defines a Wails application structure
type App struct {
window *ffenestri.Application
servicebus *servicebus.ServiceBus
logger *logger.Logger
signal *signal.Manager
options *options.App
// Subsystems
log *subsystem.Log
runtime *subsystem.Runtime
event *subsystem.Event
binding *subsystem.Binding
call *subsystem.Call
dispatcher *messagedispatcher.Dispatcher
// Indicates if the app is in debug mode
debug bool
// This is our binding DB
bindings *binding.Bindings
// LogLevel Store
loglevelStore *runtime.Store
}
// Create App
func CreateApp(options *options.App) *App {
// Merge default options
options.MergeDefaults()
// Set up logger
myLogger := logger.New(options.Logger)
myLogger.SetLogLevel(options.LogLevel)
window := ffenestri.NewApplicationWithConfig(options, myLogger)
result := &App{
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
}
result.options = options
// Initialise the app
result.Init()
return result
}
// Run the application
func (a *App) Run() error {
// Setup signal handler
signal, err := signal.NewManager(a.servicebus, a.logger)
if err != nil {
return err
}
a.signal = signal
a.signal.Start()
// Start the service bus
a.servicebus.Debug()
a.servicebus.Start()
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("loglevel", a.options.LogLevel)
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime())
if err != nil {
return err
}
a.binding = binding
a.binding.Start()
// Start the logging subsystem
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
return err
}
a.log = log
a.log.Start()
// create the dispatcher
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
if err != nil {
return err
}
a.dispatcher = dispatcher
dispatcher.Start()
// Start the eventing subsystem
event, err := subsystem.NewEvent(a.servicebus, a.logger)
if err != nil {
return err
}
a.event = event
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
a.call = call
a.call.Start()
// Dump bindings as a debug
bindingDump, err := a.bindings.ToJSON()
if err != nil {
return err
}
result := a.window.Run(dispatcher, bindingDump)
a.logger.Trace("Ffenestri.Run() exited")
a.servicebus.Stop()
return result
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

207
v2/internal/app/hybrid.go Normal file
View File

@@ -0,0 +1,207 @@
// +build !server,!desktop,hybrid
package app
import (
"os"
"path/filepath"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/internal/webserver"
)
// Config defines the Application's configuration
type Config struct {
Title string // Title is the value to be displayed in the title bar
Width int // Width is the desired window width
Height int // Height is the desired window height
DevTools bool // DevTools enables or disables the browser development tools
Resizable bool // Resizable when False prevents window resizing
ServerEnabled bool // ServerEnabled when True allows remote connections
}
// App defines a Wails application structure
type App struct {
config Config
window *ffenestri.Application
webserver *webserver.WebServer
binding *subsystem.Binding
call *subsystem.Call
event *subsystem.Event
log *subsystem.Log
runtime *subsystem.Runtime
bindings *binding.Bindings
logger *logger.Logger
dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus
debug bool
}
// Create App
func CreateApp(options *Options) *App {
// Merge default options
options.mergeDefaults()
// Set up logger
myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.INFO)
window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
Title: options.Title,
Width: options.Width,
Height: options.Height,
MinWidth: options.MinWidth,
MinHeight: options.MinHeight,
MaxWidth: options.MaxWidth,
MaxHeight: options.MaxHeight,
StartHidden: options.StartHidden,
// This should be controlled by the compile time flags...
DevTools: true,
Resizable: !options.DisableResize,
Fullscreen: options.Fullscreen,
}, myLogger)
app := &App{
window: window,
webserver: webserver.NewWebServer(myLogger),
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
}
// Initialise the app
app.Init()
return app
}
// Run the application
func (a *App) Run() error {
// Default app options
var port = 8080
var ip = "localhost"
var suppressLogging = false
// Create CLI
cli := clir.NewCli(filepath.Base(os.Args[0]), "Desktop/Server Build", "")
// Setup flags
cli.IntFlag("p", "Port to serve on", &port)
cli.StringFlag("i", "IP to serve on", &ip)
cli.BoolFlag("q", "Suppress logging", &suppressLogging)
// Setup main action
cli.Action(func() error {
// Set IP + Port
a.webserver.SetPort(port)
a.webserver.SetIP(ip)
a.webserver.SetBindings(a.bindings)
// Log information (if we aren't suppressing it)
if !suppressLogging {
cli.PrintBanner()
a.logger.Info("Running server at %s", a.webserver.URL())
}
a.servicebus.Start()
log, err := subsystem.NewLog(a.servicebus, a.logger)
if err != nil {
return err
}
a.log = log
a.log.Start()
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
if err != nil {
return err
}
a.dispatcher = dispatcher
a.dispatcher.Start()
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
if err != nil {
return err
}
a.binding = binding
a.binding.Start()
// Start the eventing subsystem
event, err := subsystem.NewEvent(a.servicebus, a.logger)
if err != nil {
return err
}
a.event = event
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
if err != nil {
return err
}
a.call = call
a.call.Start()
// Required so that the WailsInit functions are fired!
runtime.GoRuntime().Events.Emit("wails:loaded")
// Set IP + Port
a.webserver.SetPort(port)
a.webserver.SetIP(ip)
// Log information (if we aren't suppressing it)
if !suppressLogging {
cli.PrintBanner()
println("Running server at " + a.webserver.URL())
}
// Dump bindings as a debug
bindingDump, err := a.bindings.ToJSON()
if err != nil {
return err
}
go func() {
if err := a.webserver.Start(dispatcher, event); err != nil {
a.logger.Error("Webserver failed to start %s", err)
}
}()
result := a.window.Run(dispatcher, bindingDump)
a.servicebus.Stop()
return result
})
return cli.Run()
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

View File

@@ -0,0 +1,9 @@
// +build !debug
package app
// Init initialises the application for a production environment
func (a *App) Init() error {
println("Processing production cli options")
return nil
}

160
v2/internal/app/server.go Normal file
View File

@@ -0,0 +1,160 @@
// +build server,!desktop
package app
import (
"os"
"path/filepath"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/internal/webserver"
)
// App defines a Wails application structure
type App struct {
binding *subsystem.Binding
call *subsystem.Call
event *subsystem.Event
log *subsystem.Log
runtime *subsystem.Runtime
bindings *binding.Bindings
logger *logger.Logger
dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus
webserver *webserver.WebServer
debug bool
}
// Create App
func CreateApp(options *Options) *App {
options.mergeDefaults()
// We ignore the inputs (for now)
// TODO: Allow logger output override on CLI
myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.TRACE)
result := &App{
bindings: binding.NewBindings(myLogger),
logger: myLogger,
servicebus: servicebus.New(myLogger),
webserver: webserver.NewWebServer(myLogger),
}
// Initialise app
result.Init()
return result
}
// Run the application
func (a *App) Run() error {
// Default app options
var port = 8080
var ip = "localhost"
var supressLogging = false
var debugMode = false
// Create CLI
cli := clir.NewCli(filepath.Base(os.Args[0]), "Server Build", "")
// Setup flags
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)
// Setup main action
cli.Action(func() error {
// Set IP + Port
a.webserver.SetPort(port)
a.webserver.SetIP(ip)
a.webserver.SetBindings(a.bindings)
// Log information (if we aren't supressing it)
if !supressLogging {
cli.PrintBanner()
a.logger.Info("Running server at %s", a.webserver.URL())
}
if debugMode {
a.servicebus.Debug()
}
a.servicebus.Start()
log, err := subsystem.NewLog(a.servicebus, a.logger)
if err != nil {
return err
}
a.log = log
a.log.Start()
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
if err != nil {
return err
}
a.dispatcher = dispatcher
a.dispatcher.Start()
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
if err != nil {
return err
}
a.binding = binding
a.binding.Start()
// Start the eventing subsystem
event, err := subsystem.NewEvent(a.servicebus, a.logger)
if err != nil {
return err
}
a.event = event
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
if err != nil {
return err
}
a.call = call
a.call.Start()
// Required so that the WailsInit functions are fired!
runtime.GoRuntime().Events.Emit("wails:loaded")
if err := a.webserver.Start(dispatcher, event); err != nil {
a.logger.Error("Webserver failed to start %s", err)
return err
}
return nil
})
return cli.Run()
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

View File

@@ -0,0 +1,112 @@
package assetdb
import (
"fmt"
"strings"
"unsafe"
)
// AssetDB is a database for assets encoded as byte slices
type AssetDB struct {
db map[string][]byte
}
// NewAssetDB creates a new AssetDB and initialises a blank db
func NewAssetDB() *AssetDB {
return &AssetDB{
db: make(map[string][]byte),
}
}
// AddAsset saves the given byte slice under the given name
func (a *AssetDB) AddAsset(name string, data []byte) {
a.db[name] = data
}
// Remove removes the named asset
func (a *AssetDB) Remove(name string) {
delete(a.db, name)
}
// Asset retrieves the byte slice for the given name
func (a *AssetDB) Read(name string) ([]byte, error) {
result := a.db[name]
if result == nil {
return nil, fmt.Errorf("asset '%s' not found", name)
}
return result, nil
}
// AssetAsString returns the asset as a string.
// It also returns a boolean indicating whether the asset existed or not.
func (a *AssetDB) String(name string) (string, error) {
asset, err := a.Read(name)
if err != nil {
return "", err
}
return *(*string)(unsafe.Pointer(&asset)), nil
}
func (a *AssetDB) Dump() {
fmt.Printf("Assets:\n")
for k, _ := range a.db {
fmt.Println(k)
}
}
// Serialize converts the entire database to a file that when compiled will
// reconstruct the AssetDB during init()
// name: name of the asset.AssetDB instance
// pkg: package name placed at the top of the file
func (a *AssetDB) Serialize(name, pkg string) string {
var cdata strings.Builder
// Set buffer size to 4k
cdata.Grow(4096)
// Write header
header := `// DO NOT EDIT - Generated automatically
package %s
import "github.com/wailsapp/wails/v2/internal/assetdb"
var (
%s *assetdb.AssetDB = assetdb.NewAssetDB()
)
// AssetsDB is a clean interface to the assetdb.AssetDB struct
type AssetsDB interface {
Read(string) ([]byte, error)
String(string) (string, error)
}
// Assets returns the asset database
func Assets() AssetsDB {
return %s
}
func init() {
`
cdata.WriteString(fmt.Sprintf(header, pkg, name, name))
for aname, bytes := range a.db {
cdata.WriteString(fmt.Sprintf("\t%s.AddAsset(\"%s\", []byte{",
name,
aname))
l := len(bytes)
if l == 0 {
cdata.WriteString("0x00})\n")
continue
}
// Convert each byte to hex
for _, b := range bytes[:l-1] {
cdata.WriteString(fmt.Sprintf("0x%x, ", b))
}
cdata.WriteString(fmt.Sprintf("0x%x})\n", bytes[l-1]))
}
cdata.WriteString(`}`)
return cdata.String()
}

View File

@@ -0,0 +1,70 @@
package assetdb
import "testing"
import "github.com/matryer/is"
func TestExistsAsBytes(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("hello", helloworld)
result, err := db.Read("hello")
is.True(err == nil)
is.Equal(result, helloworld)
}
func TestNotExistsAsBytes(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("hello4", helloworld)
result, err := db.Read("hello")
is.True(err != nil)
is.True(result == nil)
}
func TestExistsAsString(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("hello", helloworld)
result, err := db.String("hello")
// Ensure it exists
is.True(err == nil)
// Ensure the string is the same as the byte slice
is.Equal(result, "Hello, World!")
}
func TestNotExistsAsString(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("hello", helloworld)
result, err := db.String("help")
// Ensure it doesn't exist
is.True(err != nil)
// Ensure the string is blank
is.Equal(result, "")
}

View File

@@ -0,0 +1,176 @@
// +build !desktop
package assetdb
import (
"errors"
"io"
"net/http"
"os"
"path"
"sort"
"strings"
"time"
)
var errWhence = errors.New("Seek: invalid whence")
var errOffset = errors.New("Seek: invalid offset")
// Open implements the http.FileSystem interface for the AssetDB
func (a *AssetDB) Open(name string) (http.File, error) {
if name == "/" || name == "" {
return &Entry{name: "/", dir: true, db: a}, nil
}
if data, ok := a.db[name]; ok {
return &Entry{name: name, data: data, size: len(data)}, nil
} else {
for n, _ := range a.db {
if strings.HasPrefix(n, name+"/") {
return &Entry{name: name, db: a, dir: true}, nil
}
}
}
return &Entry{}, os.ErrNotExist
}
// readdir returns the directory entries for the requested directory
func (a *AssetDB) readdir(name string) ([]os.FileInfo, error) {
dir := name
var ents []string
fim := make(map[string]os.FileInfo)
for fn, data := range a.db {
if strings.HasPrefix(fn, dir) {
pieces := strings.Split(fn[len(dir)+1:], "/")
if len(pieces) == 1 {
fim[pieces[0]] = FI{name: pieces[0], dir: false, size: len(data)}
ents = append(ents, pieces[0])
} else {
fim[pieces[0]] = FI{name: pieces[0], dir: true, size: -1}
ents = append(ents, pieces[0])
}
}
}
if len(ents) == 0 {
return nil, os.ErrNotExist
}
sort.Strings(ents)
var list []os.FileInfo
for _, dir := range ents {
list = append(list, fim[dir])
}
return list, nil
}
// Entry implements the http.File interface to allow for
// use in the http.FileSystem implementation of AssetDB
type Entry struct {
name string
data []byte
dir bool
size int
db *AssetDB
off int
}
// Close is a noop
func (e Entry) Close() error {
return nil
}
// Read fills the slice provided returning how many bytes were written
// and any errors encountered
func (e *Entry) Read(p []byte) (n int, err error) {
if e.off >= e.size {
return 0, io.EOF
}
numout := len(p)
if numout > e.size {
numout = e.size
}
for i := 0; i < numout; i++ {
p[i] = e.data[e.off+i]
}
e.off += numout
n = int(numout)
err = nil
return
}
// Seek seeks to the specified offset from the location specified by whence
func (e *Entry) Seek(offset int64, whence int) (int64, error) {
switch whence {
default:
return 0, errWhence
case io.SeekStart:
offset += 0
case io.SeekCurrent:
offset += int64(e.off)
case io.SeekEnd:
offset += int64(e.size)
}
if offset < 0 {
return 0, errOffset
}
e.off = int(offset)
return offset, nil
}
// Readdir returns the directory entries inside this entry if it is a directory
func (e Entry) Readdir(count int) ([]os.FileInfo, error) {
ents := []os.FileInfo{}
if !e.dir {
return ents, errors.New("Not a directory")
}
return e.db.readdir(e.name)
}
// Stat returns information about this directory entry
func (e Entry) Stat() (os.FileInfo, error) {
return FI{e.name, e.size, e.dir}, nil
}
// FI is the AssetDB implementation of os.FileInfo.
type FI struct {
name string
size int
dir bool
}
// IsDir returns true if this is a directory
func (fi FI) IsDir() bool {
return fi.dir
}
// ModTime always returns now
func (fi FI) ModTime() time.Time {
return time.Time{}
}
// Mode returns the file as readonly and directories
// as world writeable and executable
func (fi FI) Mode() os.FileMode {
if fi.IsDir() {
return 0755 | os.ModeDir
}
return 0444
}
// Name returns the name of this object without
// any leading slashes
func (fi FI) Name() string {
return path.Base(fi.name)
}
// Size returns the size of this item
func (fi FI) Size() int64 {
return int64(fi.size)
}
// Sys returns nil
func (fi FI) Sys() interface{} {
return nil
}

View File

@@ -0,0 +1,108 @@
package assetdb
import (
"fmt"
"os"
"testing"
"github.com/matryer/is"
)
func TestOpenLeadingSlash(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("/hello", helloworld)
file, err := db.Open("/hello")
// Ensure it does exist
is.True(err == nil)
buff := make([]byte, len(helloworld))
n, err := file.Read(buff)
fmt.Printf("Error %v\n", err)
is.True(err == nil)
is.Equal(n, len(helloworld))
result := string(buff)
// Ensure the string is blank
is.Equal(result, string(helloworld))
}
func TestOpen(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("/hello", helloworld)
file, err := db.Open("/hello")
// Ensure it does exist
is.True(err == nil)
buff := make([]byte, len(helloworld))
n, err := file.Read(buff)
is.True(err == nil)
is.Equal(n, len(helloworld))
result := string(buff)
// Ensure the string is blank
is.Equal(result, string(helloworld))
}
func TestReaddir(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("/hello", helloworld)
db.AddAsset("/directory/hello", helloworld)
db.AddAsset("/directory/subdirectory/hello", helloworld)
dir, err := db.Open("/doesntexist")
is.True(err == os.ErrNotExist)
ents, err := dir.Readdir(-1)
is.Equal([]os.FileInfo{}, ents)
dir, err = db.Open("/")
is.True(dir != nil)
is.True(err == nil)
ents, err = dir.Readdir(-1)
is.True(err == nil)
is.Equal(3, len(ents))
}
func TestReaddirSubdirectory(t *testing.T) {
is := is.New(t)
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
db := NewAssetDB()
db.AddAsset("/hello", helloworld)
db.AddAsset("/directory/hello", helloworld)
db.AddAsset("/directory/subdirectory/hello", helloworld)
expected := []os.FileInfo{
FI{name: "hello", dir: false, size: len(helloworld)},
FI{name: "subdirectory", dir: true, size: -1},
}
dir, err := db.Open("/directory")
is.True(dir != nil)
is.True(err == nil)
ents, err := dir.Readdir(-1)
is.Equal(expected, ents)
// Check sub-subdirectory
dir, err = db.Open("/directory/subdirectory")
is.True(dir != nil)
is.True(err == nil)
ents, err = dir.Readdir(-1)
is.True(err == nil)
is.Equal([]os.FileInfo{FI{name: "hello", size: len(helloworld)}}, ents)
}

9
v2/internal/bind/bind.go Normal file
View File

@@ -0,0 +1,9 @@
package bind
func IsStructPointer(value interface{}) bool {
switch t := value.(type) {
default:
println(t)
}
return false
}

70
v2/internal/binding/binding.go Executable file
View File

@@ -0,0 +1,70 @@
package binding
import (
"fmt"
"strings"
"github.com/wailsapp/wails/v2/internal/logger"
)
type Bindings struct {
db *DB
logger logger.CustomLogger
}
// NewBindings returns a new Bindings object
func NewBindings(logger *logger.Logger) *Bindings {
return &Bindings{
db: newDB(),
logger: logger.CustomLogger("Bindings"),
}
}
// Add the given struct methods to the Bindings
func (b *Bindings) Add(structPtr interface{}) error {
methods, err := getMethods(structPtr)
if err != nil {
return fmt.Errorf("cannout bind value to app: %s", err.Error())
}
for _, method := range methods {
splitName := strings.Split(method.Name, ".")
packageName := splitName[0]
structName := splitName[1]
methodName := splitName[2]
// Is this WailsInit?
if method.IsWailsInit() {
err := b.db.AddWailsInit(method)
if err != nil {
return err
}
b.logger.Trace("Registered WailsInit method: %s", method.Name)
continue
}
// Is this WailsShutdown?
if method.IsWailsShutdown() {
err := b.db.AddWailsShutdown(method)
if err != nil {
return err
}
b.logger.Trace("Registered WailsShutdown method: %s", method.Name)
continue
}
// Add it as a regular method
b.db.AddMethod(packageName, structName, methodName, method)
}
return nil
}
func (b *Bindings) DB() *DB {
return b.db
}
func (b *Bindings) ToJSON() (string, error) {
return b.db.ToJSON()
}

View File

@@ -0,0 +1,150 @@
package binding
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// BoundMethod defines all the data related to a Go method that is
// bound to the Wails application
type BoundMethod struct {
Name string `json:"name"`
Inputs []*Parameter `json:"inputs,omitempty"`
Outputs []*Parameter `json:"outputs,omitempty"`
Comments string `json:"comments,omitempty"`
Method reflect.Value `json:"-"`
}
// IsWailsInit returns true if the method name is "WailsInit"
func (b *BoundMethod) IsWailsInit() bool {
return strings.HasSuffix(b.Name, "WailsInit")
}
// IsWailsShutdown returns true if the method name is "WailsShutdown"
func (b *BoundMethod) IsWailsShutdown() bool {
return strings.HasSuffix(b.Name, "WailsShutdown")
}
// VerifyWailsInit checks if the WailsInit signature is correct
func (b *BoundMethod) VerifyWailsInit() error {
// Must only have 1 input
if b.InputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check input type
if !b.Inputs[0].IsType("*runtime.Runtime") {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Must only have 1 output
if b.OutputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check output type
if !b.Outputs[0].IsError() {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Input must be of type Runtime
return nil
}
// VerifyWailsShutdown checks if the WailsShutdown signature is correct
func (b *BoundMethod) VerifyWailsShutdown() error {
// Must have no inputs
if b.InputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Must have no outputs
if b.OutputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Input must be of type Runtime
return nil
}
// InputCount returns the number of inputs this bound method has
func (b *BoundMethod) InputCount() int {
return len(b.Inputs)
}
// OutputCount returns the number of outputs this bound method has
func (b *BoundMethod) OutputCount() int {
return len(b.Outputs)
}
// ParseArgs method converts the input json into the types expected by the method
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
result := make([]interface{}, b.InputCount())
for index, arg := range args {
typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface()
err := json.Unmarshal(arg, inputValue)
if err != nil {
return nil, err
}
if inputValue == nil {
result[index] = reflect.Zero(typ).Interface()
} else {
result[index] = reflect.ValueOf(inputValue).Elem().Interface()
}
}
return result, nil
}
// Call will attempt to call this bound method with the given args
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Check inputs
expectedInputLength := len(b.Inputs)
actualInputLength := len(args)
if expectedInputLength != actualInputLength {
return nil, fmt.Errorf("%s takes %d inputs. Received %d", b.Name, expectedInputLength, actualInputLength)
}
/** Convert inputs to reflect values **/
// Create slice for the input arguments to the method call
callArgs := make([]reflect.Value, expectedInputLength)
// Iterate over given arguments
for index, arg := range args {
// Save the converted argument
callArgs[index] = reflect.ValueOf(arg)
}
// Do the call
callResults := b.Method.Call(callArgs)
//** Check results **//
var returnValue interface{}
var err error
switch b.OutputCount() {
case 1:
// Loop over results and determine if the result
// is an error or not
for _, result := range callResults {
interfac := result.Interface()
temp, ok := interfac.(error)
if ok {
err = temp
} else {
returnValue = interfac
}
}
case 2:
returnValue = callResults[0].Interface()
if temp, ok := callResults[1].Interface().(error); ok {
err = temp
}
}
return returnValue, err
}

150
v2/internal/binding/db.go Normal file
View File

@@ -0,0 +1,150 @@
package binding
import (
"encoding/json"
"sync"
"unsafe"
)
// DB is our database of method bindings
type DB struct {
// map[packagename] -> map[structname] -> map[methodname]*method
store map[string]map[string]map[string]*BoundMethod
// This uses fully qualified method names as a shortcut for store traversal.
// It used for performance gains at runtime
methodMap map[string]*BoundMethod
// These are slices of methods registered using WailsInit and WailsShutdown
wailsInitMethods []*BoundMethod
wailsShutdownMethods []*BoundMethod
// Lock to ensure sync access to the data
lock sync.RWMutex
}
func newDB() *DB {
return &DB{
store: make(map[string]map[string]map[string]*BoundMethod),
methodMap: make(map[string]*BoundMethod),
}
}
// GetMethodFromStore returns the method for the given package/struct/method names
// nil is returned if any one of those does not exist
func (d *DB) GetMethodFromStore(packageName string, structName string, methodName string) *BoundMethod {
// Lock the db whilst processing and unlock on return
d.lock.RLock()
defer d.lock.RUnlock()
structMap, exists := d.store[packageName]
if !exists {
return nil
}
methodMap, exists := structMap[structName]
if !exists {
return nil
}
return methodMap[methodName]
}
// GetMethod returns the method for the given qualified method name
// qualifiedMethodName is "packagename.structname.methodname"
func (d *DB) GetMethod(qualifiedMethodName string) *BoundMethod {
// Lock the db whilst processing and unlock on return
d.lock.RLock()
defer d.lock.RUnlock()
return d.methodMap[qualifiedMethodName]
}
// AddMethod adds the given method definition to the db using the given qualified path: packageName.structName.methodName
func (d *DB) AddMethod(packageName string, structName string, methodName string, methodDefinition *BoundMethod) {
// TODO: Validate inputs?
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
// Get the map associated with the package name
structMap, exists := d.store[packageName]
if !exists {
// Create a new map for this packagename
d.store[packageName] = make(map[string]map[string]*BoundMethod)
structMap = d.store[packageName]
}
// Get the map associated with the struct name
methodMap, exists := structMap[structName]
if !exists {
// Create a new map for this packagename
structMap[structName] = make(map[string]*BoundMethod)
methodMap = structMap[structName]
}
// Store the method definition
methodMap[methodName] = methodDefinition
// Store in the methodMap
key := packageName + "." + structName + "." + methodName
d.methodMap[key] = methodDefinition
}
// AddWailsInit checks the given method is a WailsInit method and if it
// is, adds it to the list of WailsInit methods
func (d *DB) AddWailsInit(method *BoundMethod) error {
err := method.VerifyWailsInit()
if err != nil {
return err
}
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
d.wailsInitMethods = append(d.wailsInitMethods, method)
return nil
}
// AddWailsShutdown checks the given method is a WailsInit method and if it
// is, adds it to the list of WailsShutdown methods
func (d *DB) AddWailsShutdown(method *BoundMethod) error {
err := method.VerifyWailsShutdown()
if err != nil {
return err
}
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
d.wailsShutdownMethods = append(d.wailsShutdownMethods, method)
return nil
}
// ToJSON converts the method map to JSON
func (d *DB) ToJSON() (string, error) {
// Lock the db whilst processing and unlock on return
d.lock.RLock()
defer d.lock.RUnlock()
bytes, err := json.Marshal(&d.store)
// Return zero copy string as this string will be read only
return *(*string)(unsafe.Pointer(&bytes)), err
}
// WailsInitMethods returns the list of registered WailsInit methods
func (d *DB) WailsInitMethods() []*BoundMethod {
return d.wailsInitMethods
}
// WailsShutdownMethods returns the list of registered WailsInit methods
func (d *DB) WailsShutdownMethods() []*BoundMethod {
return d.wailsShutdownMethods
}

View File

@@ -0,0 +1,28 @@
package binding
import "reflect"
// Parameter defines a Go method parameter
type Parameter struct {
Name string `json:"name,omitempty"`
TypeName string `json:"type"`
reflectType reflect.Type
}
func newParameter(Name string, Type reflect.Type) *Parameter {
return &Parameter{
Name: Name,
TypeName: Type.String(),
reflectType: Type,
}
}
// IsType returns true if the given
func (p *Parameter) IsType(typename string) bool {
return p.TypeName == typename
}
// IsError returns true if the parameter type is an error
func (p *Parameter) IsError() bool {
return p.IsType("error")
}

123
v2/internal/binding/reflect.go Executable file
View File

@@ -0,0 +1,123 @@
package binding
import (
"encoding/json"
"fmt"
"reflect"
)
// isStructPtr returns true if the value given is a
// pointer to a struct
func isStructPtr(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Ptr &&
reflect.ValueOf(value).Elem().Kind() == reflect.Struct
}
// isStructPtr returns true if the value given is a struct
func isStruct(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Struct
}
func getMethods(value interface{}) ([]*BoundMethod, error) {
// Create result placeholder
var result []*BoundMethod
// Check type
if !isStructPtr(value) {
if isStruct(value) {
name := reflect.ValueOf(value).Type().Name()
return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name)
}
return nil, fmt.Errorf("not a pointer to a struct")
}
// Process Struct
structType := reflect.TypeOf(value)
structValue := reflect.ValueOf(value)
baseName := structType.String()[1:]
// Process Methods
for i := 0; i < structType.NumMethod(); i++ {
methodDef := structType.Method(i)
methodName := methodDef.Name
fullMethodName := baseName + "." + methodName
method := structValue.MethodByName(methodName)
// Create new method
boundMethod := &BoundMethod{
Name: fullMethodName,
Inputs: nil,
Outputs: nil,
Comments: "",
Method: method,
}
// Iterate inputs
methodType := method.Type()
inputParamCount := methodType.NumIn()
var inputs []*Parameter
for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ {
input := methodType.In(inputIndex)
thisParam := newParameter("", input)
inputs = append(inputs, thisParam)
}
boundMethod.Inputs = inputs
// Iterate outputs
// TODO: Determine what to do about limiting return types
// especially around errors.
outputParamCount := methodType.NumOut()
var outputs []*Parameter
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
output := methodType.Out(outputIndex)
thisParam := newParameter("", output)
outputs = append(outputs, thisParam)
}
boundMethod.Outputs = outputs
// Save method in result
result = append(result, boundMethod)
}
return result, nil
}
// convertArgToValue
func convertArgToValue(input json.RawMessage, target *Parameter) (result reflect.Value, err error) {
// Catch type conversion panics thrown by convert
defer func() {
if r := recover(); r != nil {
// Modify error
err = fmt.Errorf("%s", r.(string)[23:])
}
}()
// Do the conversion
// Handle nil values
if input == nil {
switch target.reflectType.Kind() {
case reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Map,
reflect.Ptr,
reflect.Slice:
result = reflect.ValueOf(input).Convert(target.reflectType)
default:
return reflect.Zero(target.reflectType), fmt.Errorf("Unable to use null value")
}
} else {
result = reflect.ValueOf(input).Convert(target.reflectType)
}
// We don't like doing this but it's the only way to
// handle recover() correctly
return
}

View File

@@ -0,0 +1,17 @@
package crypto
import (
"crypto/rand"
"fmt"
"log"
)
// RandomID returns a random ID as a string
func RandomID() string {
b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("%x", b)
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,181 @@
package ffenestri
import (
"runtime"
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/pkg/options"
)
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -lobjc
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
// DEBUG is the global Ffenestri debug flag.
// TODO: move to compile time.
var DEBUG bool = true
// Application is our main application object
type Application struct {
config *options.App
memory []unsafe.Pointer
// This is the main app pointer
app unsafe.Pointer
// Logger
logger logger.CustomLogger
}
func (a *Application) saveMemoryReference(mem unsafe.Pointer) {
a.memory = append(a.memory, mem)
}
func (a *Application) string2CString(str string) *C.char {
result := C.CString(str)
a.saveMemoryReference(unsafe.Pointer(result))
return result
}
func init() {
runtime.LockOSThread()
}
// NewApplicationWithConfig creates a new application based on the given config
func NewApplicationWithConfig(config *options.App, logger *logger.Logger) *Application {
return &Application{
config: config,
logger: logger.CustomLogger("Ffenestri"),
}
}
// NewApplication creates a new Application with the default config
func NewApplication(logger *logger.Logger) *Application {
return &Application{
config: options.Default,
logger: logger.CustomLogger("Ffenestri"),
}
}
func (a *Application) freeMemory() {
for _, mem := range a.memory {
// fmt.Printf("Freeing memory: %+v\n", mem)
C.free(mem)
}
}
// bool2Cint converts a Go boolean to a C integer
func (a *Application) bool2Cint(value bool) C.int {
if value {
return C.int(1)
}
return C.int(0)
}
// dispatcher is the interface to send messages to
var dispatcher *messagedispatcher.DispatchClient
// Dispatcher is what we register out client with
type Dispatcher interface {
RegisterClient(client messagedispatcher.Client) *messagedispatcher.DispatchClient
}
// DispatchClient is the means for passing messages to the backend
type DispatchClient interface {
SendMessage(string)
}
func intToColour(colour int) (C.int, C.int, C.int, C.int) {
var alpha = C.int(colour & 0xFF)
var blue = C.int((colour >> 8) & 0xFF)
var green = C.int((colour >> 16) & 0xFF)
var red = C.int((colour >> 24) & 0xFF)
return red, green, blue, alpha
}
// Run the application
func (a *Application) Run(incomingDispatcher Dispatcher, bindings string) error {
title := a.string2CString(a.config.Title)
width := C.int(a.config.Width)
height := C.int(a.config.Height)
resizable := a.bool2Cint(!a.config.DisableResize)
devtools := a.bool2Cint(a.config.DevTools)
fullscreen := a.bool2Cint(a.config.Fullscreen)
startHidden := a.bool2Cint(a.config.StartHidden)
logLevel := C.int(a.config.LogLevel)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel)
// Save app reference
a.app = unsafe.Pointer(app)
// Set Min Window Size
minWidth := C.int(a.config.MinWidth)
minHeight := C.int(a.config.MinHeight)
C.SetMinWindowSize(a.app, minWidth, minHeight)
// Set Max Window Size
maxWidth := C.int(a.config.MaxWidth)
maxHeight := C.int(a.config.MaxHeight)
C.SetMaxWindowSize(a.app, maxWidth, maxHeight)
// 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.RGBA != 0 {
r, g, b, alpha := intToColour(a.config.RGBA)
C.SetColour(a.app, r, g, b, alpha)
}
// Escape bindings so C doesn't freak out
bindings = strings.ReplaceAll(bindings, `"`, `\"`)
// Set bindings
C.SetBindings(app, a.string2CString(bindings))
// save the dispatcher in a package variable so that the C callbacks
// can access it
dispatcher = incomingDispatcher.RegisterClient(newClient(a))
// Process platform settings
a.processPlatformSettings()
// Check we could initialise the application
if app != nil {
// Yes - Save memory reference and run app, cleaning up afterwards
a.saveMemoryReference(unsafe.Pointer(app))
C.Run(app, 0, nil)
} else {
// Oh no! We couldn't initialise the application
a.logger.Fatal("Cannot initialise Application.")
}
a.freeMemory()
return nil
}
// messageFromWindowCallback is called by any messages sent in
// webkit to window.external.invoke. It relays the message on to
// the dispatcher.
//export messageFromWindowCallback
func messageFromWindowCallback(data *C.char) {
dispatcher.DispatchMessage(C.GoString(data))
}

View File

@@ -0,0 +1,35 @@
#ifndef __FFENESTRI_H__
#define __FFENESTRI_H__
#include <stdio.h>
extern void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel);
extern void SetMinWindowSize(void *app, int minWidth, int minHeight);
extern void SetMaxWindowSize(void *app, int maxWidth, int maxHeight);
extern void Run(void *app, int argc, char **argv);
extern void DestroyApplication(void *app);
extern void SetDebug(void *app, int flag);
extern void SetBindings(void *app, const char *bindings);
extern void ExecJS(void *app, const char *script);
extern void Hide(void *app);
extern void Show(void *app);
extern void Center(void *app);
extern void Maximise(void *app);
extern void Unmaximise(void *app);
extern void ToggleMaximise(void *app);
extern void Minimise(void *app);
extern void Unminimise(void *app);
extern void ToggleMinimise(void *app);
extern void SetColour(void *app, int red, int green, int blue, int alpha);
extern void SetSize(void *app, int width, int height);
extern void SetPosition(void *app, int x, int y);
extern void Quit(void *app);
extern void SetTitle(void *app, const char *title);
extern void Fullscreen(void *app);
extern void UnFullscreen(void *app);
extern void ToggleFullscreen(void *app);
extern void DisableFrame(void *app);
extern void OpenDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolveAliases, int treatPackagesAsDirectories);
extern void SaveDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
extern void DarkModeEnabled(void *appPointer, char *callbackID);
#endif

View File

@@ -0,0 +1,156 @@
package ffenestri
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
import (
"strconv"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
// Client is our implentation 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))
}
// 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)
}
// OpenDialog will open a dialog with the given title and filter
func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string) {
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.AllowFiles),
c.app.bool2Cint(dialogOptions.AllowDirectories),
c.app.bool2Cint(dialogOptions.AllowMultiple),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.ResolveAliases),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
C.SaveDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
func (c *Client) DarkModeEnabled(callbackID string) {
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
package ffenestri
/*
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -lobjc
extern void TitlebarAppearsTransparent(void *);
extern void HideTitle(void *);
extern void HideTitleBar(void *);
extern void FullSizeContent(void *);
extern void UseToolbar(void *);
extern void HideToolbarSeparator(void *);
extern void DisableFrame(void *);
extern void SetAppearance(void *, const char *);
extern void WebviewIsTransparent(void *);
extern void SetWindowBackgroundIsTranslucent(void *);
*/
import "C"
func (a *Application) processPlatformSettings() {
mac := a.config.Mac
titlebar := mac.TitleBar
// HideTitle
if titlebar.HideTitle {
C.HideTitle(a.app)
}
// HideTitleBar
if titlebar.HideTitleBar {
C.HideTitleBar(a.app)
}
// Full Size Content
if titlebar.FullSizeContent {
C.FullSizeContent(a.app)
}
// Toolbar
if titlebar.UseToolbar {
C.UseToolbar(a.app)
}
if titlebar.HideToolbarSeparator {
C.HideToolbarSeparator(a.app)
}
if titlebar.TitlebarAppearsTransparent {
C.TitlebarAppearsTransparent(a.app)
}
// Process window Appearance
if mac.Appearance != "" {
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
}
// Check if the webview should be transparent
if mac.WebviewIsTransparent {
C.WebviewIsTransparent(a.app)
}
// Check if window should be translucent
if mac.WindowBackgroundIsTranslucent {
C.SetWindowBackgroundIsTranslucent(a.app)
}
}

View File

@@ -0,0 +1,984 @@
#ifndef __FFENESTRI_LINUX_H__
#define __FFENESTRI_LINUX_H__
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
// References to assets
extern const unsigned char *assets[];
extern const unsigned char runtime;
extern const char *icon[];
// Constants
#define PRIMARY_MOUSE_BUTTON 1
#define MIDDLE_MOUSE_BUTTON 2
#define SECONDARY_MOUSE_BUTTON 3
// 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, ...)
{
if (debug)
{
char *temp = concat("TRACE | Ffenestri (C) | ", message);
message = concat(temp, "\n");
free(temp);
va_list args;
va_start(args, message);
vprintf(message, args);
free(message);
va_end(args);
}
}
extern void messageFromWindowCallback(const char *);
typedef void (*ffenestriCallback)(const char *);
struct Application
{
// Gtk Data
GtkApplication *application;
GtkWindow *mainWindow;
GtkWidget *webView;
int signalInvoke;
int signalWindowDrag;
int signalButtonPressed;
int signalButtonReleased;
int signalLoadChanged;
// Saves the events for the drag mouse button
GdkEventButton *dragButtonEvent;
// The number of the default drag button
int dragButton;
// Window Data
const char *title;
char *id;
int width;
int height;
int resizable;
int devtools;
int startHidden;
int fullscreen;
int minWidth;
int minHeight;
int maxWidth;
int maxHeight;
int frame;
// User Data
char *HTML;
// Callback
ffenestriCallback sendMessageToBackend;
// Bindings
const char *bindings;
// Lock - used for sync operations (Should we be using g_mutex?)
int lock;
};
void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden)
{
// Setup main application struct
struct Application *result = malloc(sizeof(struct Application));
result->title = title;
result->width = width;
result->height = height;
result->resizable = resizable;
result->devtools = devtools;
result->fullscreen = fullscreen;
result->minWidth = 0;
result->minHeight = 0;
result->maxWidth = 0;
result->maxHeight = 0;
result->frame = 1;
result->startHidden = startHidden;
// Default drag button is PRIMARY
result->dragButton = PRIMARY_MOUSE_BUTTON;
result->sendMessageToBackend = (ffenestriCallback)messageFromWindowCallback;
// Create a unique ID based on the current unix timestamp
char temp[11];
sprintf(&temp[0], "%d", (int)time(NULL));
result->id = concat("wails.app", &temp[0]);
// Create the main GTK application
GApplicationFlags flags = G_APPLICATION_FLAGS_NONE;
result->application = gtk_application_new(result->id, flags);
// Return the application struct
return (void *)result;
}
void DestroyApplication(struct Application *app)
{
Debug("Destroying Application");
g_application_quit(G_APPLICATION(app->application));
// Release the GTK ID string
if (app->id != NULL)
{
free(app->id);
app->id = NULL;
}
else
{
Debug("Almost a double free for app->id");
}
// Free the bindings
if (app->bindings != NULL)
{
free((void *)app->bindings);
app->bindings = NULL;
}
else
{
Debug("Almost a double free for app->bindings");
}
// Disconnect signal handlers
WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager((WebKitWebView *)app->webView);
g_signal_handler_disconnect(manager, app->signalInvoke);
if( app->frame == 0) {
g_signal_handler_disconnect(manager, app->signalWindowDrag);
g_signal_handler_disconnect(app->webView, app->signalButtonPressed);
g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
}
g_signal_handler_disconnect(app->webView, app->signalLoadChanged);
// Release the main GTK Application
if (app->application != NULL)
{
g_object_unref(app->application);
app->application = NULL;
}
else
{
Debug("Almost a double free for app->application");
}
Debug("Finished Destroying Application");
}
// Quit will stop the gtk application and free up all the memory
// used by the application
void Quit(struct Application *app)
{
Debug("Quit Called");
gtk_window_close((GtkWindow *)app->mainWindow);
g_application_quit((GApplication *)app->application);
DestroyApplication(app);
}
// SetTitle sets the main window title to the given string
void SetTitle(struct Application *app, const char *title)
{
gtk_window_set_title(app->mainWindow, title);
}
// Fullscreen sets the main window to be fullscreen
void Fullscreen(struct Application *app)
{
gtk_window_fullscreen(app->mainWindow);
}
// UnFullscreen resets the main window after a fullscreen
void UnFullscreen(struct Application *app)
{
gtk_window_unfullscreen(app->mainWindow);
}
void setMinMaxSize(struct Application *app)
{
GdkGeometry size;
size.min_width = size.min_height = size.max_width = size.max_height = 0;
int flags = 0;
if (app->maxHeight > 0 && app->maxWidth > 0)
{
size.max_height = app->maxHeight;
size.max_width = app->maxWidth;
flags |= GDK_HINT_MAX_SIZE;
}
if (app->minHeight > 0 && app->minWidth > 0)
{
size.min_height = app->minHeight;
size.min_width = app->minWidth;
flags |= GDK_HINT_MIN_SIZE;
}
gtk_window_set_geometry_hints(app->mainWindow, NULL, &size, flags);
}
char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAction, char **args) {
GtkFileChooserNative *native;
GtkFileChooserAction action = chooserAction;
gint res;
char *filename;
char *title = args[0];
char *filter = args[1];
native = gtk_file_chooser_native_new(title,
app->mainWindow,
action,
"_Open",
"_Cancel");
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
// If we have filters, process them
if (filter[0] != '\0') {
GtkFileFilter *file_filter = gtk_file_filter_new();
gchar **filters = g_strsplit(filter, ",", -1);
gint i;
for(i = 0; filters && filters[i]; i++) {
gtk_file_filter_add_pattern(file_filter, filters[i]);
// Debug("Adding filter pattern: %s\n", filters[i]);
}
gtk_file_filter_set_name(file_filter, filter);
gtk_file_chooser_add_filter(chooser, file_filter);
g_strfreev(filters);
}
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT)
{
filename = gtk_file_chooser_get_filename(chooser);
}
g_object_unref(native);
return filename;
}
// openFileDialogInternal opens a dialog to select a file
// NOTE: The result is a string that will need to be freed!
char *openFileDialogInternal(struct Application *app, char **args)
{
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_OPEN, args);
}
// saveFileDialogInternal opens a dialog to select a file
// NOTE: The result is a string that will need to be freed!
char *saveFileDialogInternal(struct Application *app, char **args)
{
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SAVE, args);
}
// openDirectoryDialogInternal opens a dialog to select a directory
// NOTE: The result is a string that will need to be freed!
char *openDirectoryDialogInternal(struct Application *app, char **args)
{
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, args);
}
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
{
app->minWidth = minWidth;
app->minHeight = minHeight;
}
void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
{
app->maxWidth = maxWidth;
app->maxHeight = maxHeight;
}
// SetColour sets the colour of the webview to the given colour string
int SetColour(struct Application *app, const char *colourString)
{
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;
}
// DisableFrame disables the window frame
void DisableFrame(struct Application *app)
{
app->frame = 0;
}
void syncCallback(GObject *source_object,
GAsyncResult *res,
void *data)
{
struct Application *app = (struct Application *)data;
app->lock = 0;
}
void syncEval(struct Application *app, const gchar *script)
{
WebKitWebView *webView = (WebKitWebView *)(app->webView);
// wait for lock to free
while (app->lock == 1)
{
g_main_context_iteration(0, true);
}
// Set lock
app->lock = 1;
webkit_web_view_run_javascript(
webView,
script,
NULL, syncCallback, (void*)app);
while (app->lock == 1)
{
g_main_context_iteration(0, true);
}
}
void asyncEval(WebKitWebView *webView, const gchar *script)
{
webkit_web_view_run_javascript(
webView,
script,
NULL, NULL, NULL);
}
typedef void (*dispatchMethod)(struct Application *app, void *);
struct dispatchData
{
struct Application *app;
dispatchMethod method;
void *args;
};
gboolean executeMethod(gpointer data)
{
struct dispatchData *d = (struct dispatchData *)data;
(d->method)(d->app, d->args);
g_free(d);
return FALSE;
}
void ExecJS(struct Application *app, char *js)
{
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)syncEval;
data->args = js;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
typedef char *(*dialogMethod)(struct Application *app, void *);
struct dialogCall
{
struct Application *app;
dialogMethod method;
void *args;
void *filter;
char *result;
int done;
};
gboolean executeMethodWithReturn(gpointer data)
{
struct dialogCall *d = (struct dialogCall *)data;
d->result = (d->method)(d->app, d->args);
d->done = 1;
return FALSE;
}
char *OpenFileDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)openFileDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
usleep(100000);
}
g_free(data);
return data->result;
}
char *SaveFileDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)saveFileDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
usleep(100000);
}
Debug("Dialog done");
Debug("Result = %s\n", data->result);
g_free(data);
// Fingers crossed this wasn't freed by g_free above
return data->result;
}
char *OpenDirectoryDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)openDirectoryDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
usleep(100000);
}
Debug("Directory Dialog done");
Debug("Result = %s\n", data->result);
g_free(data);
// Fingers crossed this wasn't freed by g_free above
return data->result;
}
// Sets the icon to the XPM stored in icon
void setIcon(struct Application *app)
{
GdkPixbuf *appIcon = gdk_pixbuf_new_from_xpm_data((const char **)icon);
gtk_window_set_icon(app->mainWindow, appIcon);
}
static void load_finished_cb(WebKitWebView *webView,
WebKitLoadEvent load_event,
struct Application *app)
{
switch (load_event)
{
// case WEBKIT_LOAD_STARTED:
// /* New load, we have now a provisional URI */
// // printf("Start downloading %s\n", webkit_web_view_get_uri(web_view));
// /* Here we could start a spinner or update the
// * location bar with the provisional URI */
// break;
// case WEBKIT_LOAD_REDIRECTED:
// // printf("Redirected to: %s\n", webkit_web_view_get_uri(web_view));
// break;
// case WEBKIT_LOAD_COMMITTED:
// /* The load is being performed. Current URI is
// * the final one and it won't change unless a new
// * load is requested or a navigation within the
// * same page is performed */
// // printf("Loading: %s\n", webkit_web_view_get_uri(web_view));
// break;
case WEBKIT_LOAD_FINISHED:
/* Load finished, we can now stop the spinner */
// printf("Finished loading: %s\n", webkit_web_view_get_uri(web_view));
// Bindings
Debug("Binding Methods");
syncEval(app, app->bindings);
// Runtime
Debug("Setting up Wails runtime");
syncEval(app, &runtime);
// Loop over assets
int index = 1;
while (1)
{
// Get next asset pointer
const char *asset = assets[index];
// If we have no more assets, break
if (asset == 0x00)
{
break;
}
// sync eval the asset
syncEval(app, asset);
index++;
};
// Set the icon
setIcon(app);
// Setup fullscreen
if (app->fullscreen)
{
Debug("Going fullscreen");
Fullscreen(app);
}
// Setup resize
gtk_window_resize(GTK_WINDOW(app->mainWindow), app->width, app->height);
if (app->resizable)
{
gtk_window_set_default_size(GTK_WINDOW(app->mainWindow), app->width, app->height);
}
else
{
gtk_widget_set_size_request(GTK_WIDGET(app->mainWindow), app->width, app->height);
gtk_window_resize(GTK_WINDOW(app->mainWindow), app->width, app->height);
// Fix the min/max to the window size for good measure
app->minHeight = app->maxHeight = app->height;
app->minWidth = app->maxWidth = app->width;
}
gtk_window_set_resizable(GTK_WINDOW(app->mainWindow), app->resizable ? TRUE : FALSE);
setMinMaxSize(app);
// Centre by default
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
// Show window and focus
if( app->startHidden == 0) {
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_grab_focus(app->webView);
}
break;
}
}
static gboolean disable_context_menu_cb(
WebKitWebView *web_view,
WebKitContextMenu *context_menu,
GdkEvent *event,
WebKitHitTestResult *hit_test_result,
gpointer user_data)
{
return TRUE;
}
static void printEvent(const char *message, GdkEventButton *event)
{
Debug("%s: [button:%d] [x:%f] [y:%f] [time:%d]",
message,
event->button,
event->x_root,
event->y_root,
event->time);
}
static void dragWindow(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result,
struct Application *app)
{
// If we get this message erroneously, ignore
if (app->dragButtonEvent == NULL)
{
return;
}
// Ignore non-toplevel widgets
GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(app->webView));
if (!GTK_IS_WINDOW(window))
{
return;
}
// Initiate the drag
printEvent("Starting drag with event", app->dragButtonEvent);
gtk_window_begin_move_drag(app->mainWindow,
app->dragButton,
app->dragButtonEvent->x_root,
app->dragButtonEvent->y_root,
app->dragButtonEvent->time);
// Clear the event
app->dragButtonEvent = NULL;
return;
}
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, struct Application *app)
{
if (event->type == GDK_BUTTON_PRESS && event->button == app->dragButton)
{
app->dragButtonEvent = event;
}
return FALSE;
}
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Application *app)
{
if (event->type == GDK_BUTTON_RELEASE && event->button == app->dragButton)
{
app->dragButtonEvent = NULL;
}
return FALSE;
}
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result,
struct Application *app)
{
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
JSCValue *value = webkit_javascript_result_get_js_value(result);
char *message = jsc_value_to_string(value);
#else
JSGlobalContextRef context = webkit_javascript_result_get_global_context(result);
JSValueRef value = webkit_javascript_result_get_value(result);
JSStringRef js = JSValueToStringCopy(context, value, NULL);
size_t messageSize = JSStringGetMaximumUTF8CStringSize(js);
char *message = g_new(char, messageSize);
JSStringGetUTF8CString(js, message, messageSize);
JSStringRelease(js);
#endif
app->sendMessageToBackend(message);
g_free(message);
}
void SetDebug(struct Application *app, int flag)
{
debug = flag;
}
// getCurrentMonitorGeometry gets the geometry of the monitor
// that the window is mostly on.
GdkRectangle getCurrentMonitorGeometry(GtkWindow *window) {
// Get the monitor that the window is currently on
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
GdkMonitor *monitor = gdk_display_get_monitor_at_window (display, gdk_window);
// Get the geometry of the monitor
GdkRectangle result;
gdk_monitor_get_geometry (monitor,&result);
return result;
}
/*******************
* Window Position *
*******************/
// Position holds an x/y corrdinate
struct Position {
int x;
int y;
};
// Internal call for setting the position of the window.
void setPositionInternal(struct Application *app, struct Position *pos) {
// Get the monitor geometry
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
// Move the window relative to the monitor
gtk_window_move(app->mainWindow, m.x + pos->x, m.y + pos->y);
// Free memory
free(pos);
}
// SetPosition sets the position of the window to the given x/y
// coordinates. The x/y values are relative to the monitor
// the window is mostly on.
void SetPosition(struct Application *app, int x, int y) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)setPositionInternal;
struct Position *pos = malloc(sizeof(struct Position));
pos->x = x;
pos->y = y;
data->args = pos;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
/***************
* Window Size *
***************/
// Size holds a width/height
struct Size {
int width;
int height;
};
// Internal call for setting the size of the window.
void setSizeInternal(struct Application *app, struct Size *size) {
gtk_window_resize(app->mainWindow, size->width, size->height);
free(size);
}
// SetSize sets the size of the window to the given width/height
void SetSize(struct Application *app, int width, int height) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)setSizeInternal;
struct Size *size = malloc(sizeof(struct Size));
size->width = width;
size->height = height;
data->args = size;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
// centerInternal will center the main window on the monitor it is mostly in
void centerInternal(struct Application *app)
{
// Get the geometry of the monitor
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
// Get the window width/height
int windowWidth, windowHeight;
gtk_window_get_size(app->mainWindow, &windowWidth, &windowHeight);
// Place the window at the center of the monitor
gtk_window_move(app->mainWindow, ((m.width - windowWidth) / 2) + m.x, ((m.height - windowHeight) / 2) + m.y);
}
// Center the window
void Center(struct Application *app) {
// Setup a call to centerInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)centerInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// hideInternal hides the main window
void hideInternal(struct Application *app) {
gtk_widget_hide (GTK_WIDGET(app->mainWindow));
}
// Hide places the hideInternal method onto the main thread for execution
void Hide(struct Application *app) {
// Setup a call to hideInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)hideInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// showInternal shows the main window
void showInternal(struct Application *app) {
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_grab_focus(app->webView);
}
// Show places the showInternal method onto the main thread for execution
void Show(struct Application *app) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)showInternal;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
// maximiseInternal maximises the main window
void maximiseInternal(struct Application *app) {
gtk_window_maximize(GTK_WIDGET(app->mainWindow));
}
// Maximise places the maximiseInternal method onto the main thread for execution
void Maximise(struct Application *app) {
// Setup a call to maximiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)maximiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// unmaximiseInternal unmaximises the main window
void unmaximiseInternal(struct Application *app) {
gtk_window_unmaximize(GTK_WIDGET(app->mainWindow));
}
// Unmaximise places the unmaximiseInternal method onto the main thread for execution
void Unmaximise(struct Application *app) {
// Setup a call to unmaximiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)unmaximiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// minimiseInternal minimises the main window
void minimiseInternal(struct Application *app) {
gtk_window_iconify(app->mainWindow);
}
// Minimise places the minimiseInternal method onto the main thread for execution
void Minimise(struct Application *app) {
// Setup a call to minimiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)minimiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// unminimiseInternal unminimises the main window
void unminimiseInternal(struct Application *app) {
gtk_window_present(app->mainWindow);
}
// Unminimise places the unminimiseInternal method onto the main thread for execution
void Unminimise(struct Application *app) {
// Setup a call to unminimiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)unminimiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
void SetBindings(struct Application *app, const char *bindings)
{
const char *temp = concat("window.wailsbindings = \"", bindings);
const char *jscall = concat(temp, "\";");
free((void *)temp);
app->bindings = jscall;
}
// This is called when the close button on the window is pressed
gboolean close_button_pressed(GtkWidget *widget,
GdkEvent *event,
struct Application *app)
{
app->sendMessageToBackend("WC"); // Window Close
return TRUE;
}
static void setupWindow(struct Application *app)
{
// Create the window
GtkWidget *mainWindow = gtk_application_window_new(app->application);
// Save reference
app->mainWindow = GTK_WINDOW(mainWindow);
// Setup frame
gtk_window_set_decorated((GtkWindow *)mainWindow, app->frame);
// Setup title
gtk_window_set_title(GTK_WINDOW(mainWindow), app->title);
// Setup script handler
WebKitUserContentManager *contentManager = webkit_user_content_manager_new();
// Setup the invoke handler
webkit_user_content_manager_register_script_message_handler(contentManager, "external");
app->signalInvoke = g_signal_connect(contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), app);
// Setup the window drag handler if this is a frameless app
if ( app->frame == 0 ) {
webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
// Setup the mouse handlers
app->signalButtonPressed = g_signal_connect(app->webView, "button-press-event", G_CALLBACK(buttonPress), app);
app->signalButtonReleased = g_signal_connect(app->webView, "button-release-event", G_CALLBACK(buttonRelease), app);
}
GtkWidget *webView = webkit_web_view_new_with_user_content_manager(contentManager);
// Save reference
app->webView = webView;
// Add the webview to the window
gtk_container_add(GTK_CONTAINER(mainWindow), webView);
// Load default HTML
app->signalLoadChanged = g_signal_connect(G_OBJECT(webView), "load-changed", G_CALLBACK(load_finished_cb), app);
// Load the user's HTML
// assets[0] is the HTML because the asset array is bundled like that by convention
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webView), assets[0]);
// Check if we want to enable the dev tools
if (app->devtools)
{
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webView));
// webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
webkit_settings_set_enable_developer_extras(settings, true);
}
else
{
g_signal_connect(G_OBJECT(webView), "context-menu", G_CALLBACK(disable_context_menu_cb), app);
}
// Listen for close button signal
g_signal_connect(GTK_WIDGET(mainWindow), "delete-event", G_CALLBACK(close_button_pressed), app);
}
static void activate(GtkApplication* _, struct Application *app)
{
setupWindow(app);
}
void Run(struct Application *app, int argc, char **argv)
{
g_signal_connect(app->application, "activate", G_CALLBACK(activate), app);
g_application_run(G_APPLICATION(app->application), argc, argv);
}
#endif

1383
v2/internal/ffenestri/json.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
/*
Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Source: http://git.ozlabs.org/?p=ccan;a=tree;f=ccan/json;hb=HEAD
*/
#ifndef CCAN_JSON_H
#define CCAN_JSON_H
#include <stdbool.h>
#include <stddef.h>
typedef enum {
JSON_NULL,
JSON_BOOL,
JSON_STRING,
JSON_NUMBER,
JSON_ARRAY,
JSON_OBJECT,
} JsonTag;
typedef struct JsonNode JsonNode;
struct JsonNode
{
/* only if parent is an object or array (NULL otherwise) */
JsonNode *parent;
JsonNode *prev, *next;
/* only if parent is an object (NULL otherwise) */
char *key; /* Must be valid UTF-8. */
JsonTag tag;
union {
/* JSON_BOOL */
bool bool_;
/* JSON_STRING */
char *string_; /* Must be valid UTF-8. */
/* JSON_NUMBER */
double number_;
/* JSON_ARRAY */
/* JSON_OBJECT */
struct {
JsonNode *head, *tail;
} children;
};
};
/*** Encoding, decoding, and validation ***/
JsonNode *json_decode (const char *json);
char *json_encode (const JsonNode *node);
char *json_encode_string (const char *str);
char *json_stringify (const JsonNode *node, const char *space);
void json_delete (JsonNode *node);
bool json_validate (const char *json);
/*** Lookup and traversal ***/
JsonNode *json_find_element (JsonNode *array, int index);
JsonNode *json_find_member (JsonNode *object, const char *key);
JsonNode *json_first_child (const JsonNode *node);
#define json_foreach(i, object_or_array) \
for ((i) = json_first_child(object_or_array); \
(i) != NULL; \
(i) = (i)->next)
/*** Construction and manipulation ***/
JsonNode *json_mknull(void);
JsonNode *json_mkbool(bool b);
JsonNode *json_mkstring(const char *s);
JsonNode *json_mknumber(double n);
JsonNode *json_mkarray(void);
JsonNode *json_mkobject(void);
void json_append_element(JsonNode *array, JsonNode *element);
void json_prepend_element(JsonNode *array, JsonNode *element);
void json_append_member(JsonNode *object, const char *key, JsonNode *value);
void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);
void json_remove_from_parent(JsonNode *node);
/*** Debugging ***/
/*
* Look for structure and encoding problems in a JsonNode or its descendents.
*
* If a problem is detected, return false, writing a description of the problem
* to errmsg (unless errmsg is NULL).
*/
bool json_check(const JsonNode *node, char errmsg[256]);
#endif

202
v2/internal/fs/fs.go Normal file
View File

@@ -0,0 +1,202 @@
package fs
import (
"crypto/md5"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"unsafe"
"github.com/leaanthony/slicer"
)
// LocalDirectory gets the caller's file directory
// Equivalent to node's __DIRNAME
func LocalDirectory() string {
_, thisFile, _, _ := runtime.Caller(1)
return filepath.Dir(thisFile)
}
// RelativeToCwd returns an absolute path based on the cwd
// and the given relative path
func RelativeToCwd(relativePath string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(cwd, relativePath), nil
}
// Mkdir will create the given directory
func Mkdir(dirname string) error {
return os.Mkdir(dirname, 0755)
}
// DeleteFile will delete the given file
func DeleteFile(filename string) error {
return os.Remove(filename)
}
// CopyFile from source to target
func CopyFile(source string, target string) error {
s, err := os.Open(source)
if err != nil {
return err
}
defer s.Close()
d, err := os.Create(target)
if err != nil {
return err
}
if _, err := io.Copy(d, s); err != nil {
d.Close()
return err
}
return d.Close()
}
// DirExists - Returns true if the given path resolves to a directory on the filesystem
func DirExists(path string) bool {
fi, err := os.Lstat(path)
if err != nil {
return false
}
return fi.Mode().IsDir()
}
// FileExists returns a boolean value indicating whether
// the given file exists
func FileExists(path string) bool {
fi, err := os.Lstat(path)
if err != nil {
return false
}
return fi.Mode().IsRegular()
}
// RelativePath returns a qualified path created by joining the
// directory of the calling file and the given relative path.
//
// Example: RelativePath("..") in *this* file would give you '/path/to/wails2/v2/internal`
func RelativePath(relativepath string, optionalpaths ...string) string {
_, thisFile, _, _ := runtime.Caller(1)
localDir := filepath.Dir(thisFile)
// If we have optional paths, join them to the relativepath
if len(optionalpaths) > 0 {
paths := []string{relativepath}
paths = append(paths, optionalpaths...)
relativepath = filepath.Join(paths...)
}
result, err := filepath.Abs(filepath.Join(localDir, relativepath))
if err != nil {
// I'm allowing this for 1 reason only: It's fatal if the path
// supplied is wrong as it's only used internally in Wails. If we get
// that path wrong, we should know about it immediately. The other reason is
// that it cuts down a ton of unnecassary error handling.
panic(err)
}
return result
}
// MustLoadString attempts to load a string and will abort with a fatal message if
// something goes wrong
func MustLoadString(filename string) string {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Printf("FATAL: Unable to load file '%s': %s\n", filename, err.Error())
os.Exit(1)
}
return *(*string)(unsafe.Pointer(&data))
}
// MD5File returns the md5sum of the given file
func MD5File(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close()
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
// MustMD5File will call MD5File and abort the program on error
func MustMD5File(filename string) string {
result, err := MD5File(filename)
if err != nil {
println("FATAL: Unable to MD5Sum file:", err.Error())
os.Exit(1)
}
return result
}
// MustWriteString will attempt to write the given data to the given filename
// It will abort the program in the event of a failure
func MustWriteString(filename string, data string) {
err := ioutil.WriteFile(filename, []byte(data), 0755)
if err != nil {
fatal("Unable to write file", filename, ":", err.Error())
os.Exit(1)
}
}
// fatal will print the optional messages and die
func fatal(message ...string) {
if len(message) > 0 {
print("FATAL:")
for text := range message {
print(text)
}
}
os.Exit(1)
}
// GetSubdirectories returns a list of subdirectories for the given root directory
func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
var result slicer.StringSlicer
// Iterate root dir
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// If we have a directory, save it
if info.IsDir() {
result.Add(path)
}
return nil
})
return &result, err
}
func DirIsEmpty(dir string) (bool, error) {
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 {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1) // Or f.Readdir(1)
if err == io.EOF {
return true, nil
}
return false, err // Either not empty or error, suits both cases
}

31
v2/internal/fs/fs_test.go Normal file
View File

@@ -0,0 +1,31 @@
package fs
import (
"os"
"path/filepath"
"testing"
"github.com/matryer/is"
)
func TestRelativePath(t *testing.T) {
is := is.New(t)
cwd, err := os.Getwd()
is.Equal(err, nil)
// Check current directory
actual := RelativePath(".")
is.Equal(actual, cwd)
// Check 2 parameters
actual = RelativePath("..", "fs")
is.Equal(actual, cwd)
// Check 3 parameters including filename
actual = RelativePath("..", "fs", "fs.go")
expected := filepath.Join(cwd, "fs.go")
is.Equal(actual, expected)
}

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