Skip to content

Commit 8c7480d

Browse files
authored
Develop (#265)
* Patch for file dialog on OSX * Update CONTRIBUTORS.md * 262 add wails shutdown method (#264) * feat: support close in bridge mode * feat: WailsShutdown callback Now handles proper shutdown through: * runtime.Window.Close() * Killing the main window * Ctrl-C * chore: version bump
1 parent d399b75 commit 8c7480d

15 files changed

+158
-33
lines changed

CONTRIBUTORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ Wails is what it is because of the time and effort given by these great people.
1919
* [Nikolai Zimmermann](https://github.com/Chronophylos)
2020
* [Toyam Cox](https://github.com/Vaelatern)
2121
* [Robin Eklind](https://github.com/mewmew)
22+
* [Kris Raney](https://github.com/kraney)

app.go

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package wails
22

33
import (
4+
"os"
5+
"syscall"
6+
7+
"github.com/syossan27/tebata"
48
"github.com/wailsapp/wails/cmd"
59
"github.com/wailsapp/wails/lib/binding"
610
"github.com/wailsapp/wails/lib/event"
@@ -66,6 +70,7 @@ func CreateApp(optionalConfig ...*AppConfig) *App {
6670

6771
// Run the app
6872
func (a *App) Run() error {
73+
6974
if BuildMode != cmd.BuildModeProd {
7075
return a.cli.Run()
7176
}
@@ -97,6 +102,13 @@ func (a *App) start() error {
97102
return err
98103
}
99104

105+
// Start signal handler
106+
t := tebata.New(os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
107+
t.Reserve(func() {
108+
a.log.Debug("SIGNAL CAUGHT! Starting Shutdown")
109+
a.renderer.Close()
110+
})
111+
100112
// Start event manager and give it our renderer
101113
a.eventManager.Start(a.renderer)
102114

@@ -112,8 +124,33 @@ func (a *App) start() error {
112124
return err
113125
}
114126

127+
// Defer the shutdown
128+
defer a.shutdown()
129+
115130
// Run the renderer
116-
return a.renderer.Run()
131+
err = a.renderer.Run()
132+
if err != nil {
133+
return err
134+
}
135+
136+
return nil
137+
}
138+
139+
// shutdown the app
140+
func (a *App) shutdown() {
141+
// Make sure this is only called once
142+
a.log.Debug("Shutting down")
143+
144+
// Shutdown Binding Manager
145+
a.bindingManager.Shutdown()
146+
147+
// Shutdown IPC Manager
148+
a.ipc.Shutdown()
149+
150+
// Shutdown Event Manager
151+
a.eventManager.Shutdown()
152+
153+
a.log.Debug("Cleanly Shutdown")
117154
}
118155

119156
// Bind allows the user to bind the given object

cmd/version.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
package cmd
22

33
// Version - Wails version
4-
const Version = "v0.18.3"
4+
const Version = "v0.18.4"

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/pkg/errors v0.8.1 // indirect
2323
github.com/sirupsen/logrus v1.4.1
2424
github.com/stretchr/testify v1.3.0 // indirect
25+
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba
2526
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 // indirect
2627
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 // indirect
2728
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
6868
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
6969
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
7070
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
71+
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba h1:2DHfQOxcpWdGf5q5IzCUFPNvRX9Icf+09RvQK2VnJq0=
72+
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba/go.mod h1:iLnlXG2Pakcii2CU0cbY07DRCSvpWNa7nFxtevhOChk=
7173
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
7274
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
7375
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=

lib/binding/manager.go

+14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type Manager struct {
1616
functions map[string]*boundFunction
1717
internalMethods *internalMethods
1818
initMethods []*boundMethod
19+
shutdownMethods []*boundMethod
1920
log *logger.CustomLogger
2021
renderer interfaces.Renderer
2122
runtime interfaces.Runtime // The runtime object to pass to bound structs
@@ -127,6 +128,9 @@ func (b *Manager) bindMethod(object interface{}) error {
127128
if newMethod.isWailsInit {
128129
b.log.Debugf("Detected WailsInit function: %s", fullMethodName)
129130
b.initMethods = append(b.initMethods, newMethod)
131+
} else if newMethod.isWailsShutdown {
132+
b.log.Debugf("Detected WailsShutdown function: %s", fullMethodName)
133+
b.shutdownMethods = append(b.shutdownMethods, newMethod)
130134
} else {
131135
// Save boundMethod
132136
b.log.Infof("Bound Method: %s()", fullMethodName)
@@ -292,3 +296,13 @@ func (b *Manager) callWailsInitMethods() error {
292296
}
293297
return nil
294298
}
299+
300+
// Shutdown the binding manager
301+
func (b *Manager) Shutdown() {
302+
b.log.Debug("Shutdown called")
303+
for _, method := range b.shutdownMethods {
304+
b.log.Debugf("Calling Shutdown for method: %s", method.fullName)
305+
method.call("[]")
306+
}
307+
b.log.Debug("Shutdown complete")
308+
}

lib/binding/method.go

+23
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type boundMethod struct {
1818
log *logger.CustomLogger
1919
hasErrorReturnType bool // Indicates if there is an error return type
2020
isWailsInit bool
21+
isWailsShutdown bool
2122
}
2223

2324
// Creates a new bound method based on the given method + type
@@ -39,6 +40,11 @@ func newBoundMethod(name string, fullName string, method reflect.Value, objectTy
3940
err = result.processWailsInit()
4041
}
4142

43+
// Are we a WailsShutdown method?
44+
if result.Name == "WailsShutdown" {
45+
err = result.processWailsShutdown()
46+
}
47+
4248
return result, err
4349
}
4450

@@ -211,3 +217,20 @@ func (b *boundMethod) processWailsInit() error {
211217

212218
return nil
213219
}
220+
221+
func (b *boundMethod) processWailsShutdown() error {
222+
// We must have only 1 input, it must be *wails.Runtime
223+
if len(b.inputs) != 0 {
224+
return fmt.Errorf("Invalid WailsShutdown() definition. Expected 0 inputs, but got %d", len(b.inputs))
225+
}
226+
227+
// We must have only 1 output, it must be error
228+
if len(b.returnTypes) != 0 {
229+
return fmt.Errorf("Invalid WailsShutdown() definition. Expected 0 return types, but got %d", len(b.returnTypes))
230+
}
231+
232+
// We are indeed a wails Shutdown method
233+
b.isWailsShutdown = true
234+
235+
return nil
236+
}

lib/event/manager.go

+19-13
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,29 @@ package event
33
import (
44
"fmt"
55
"sync"
6+
"time"
7+
8+
"github.com/wailsapp/wails/lib/interfaces"
69
"github.com/wailsapp/wails/lib/logger"
710
"github.com/wailsapp/wails/lib/messages"
8-
"github.com/wailsapp/wails/lib/interfaces"
911
)
1012

1113
// Manager handles and processes events
1214
type Manager struct {
1315
incomingEvents chan *messages.EventData
1416
listeners map[string][]*eventListener
15-
exit bool
17+
running bool
1618
log *logger.CustomLogger
1719
renderer interfaces.Renderer // Messages will be dispatched to the frontend
20+
wg sync.WaitGroup
1821
}
1922

2023
// NewManager creates a new event manager with a 100 event buffer
2124
func NewManager() interfaces.EventManager {
2225
return &Manager{
2326
incomingEvents: make(chan *messages.EventData, 100),
2427
listeners: make(map[string][]*eventListener),
25-
exit: false,
28+
running: false,
2629
log: logger.NewCustomLogger("Events"),
2730
}
2831
}
@@ -87,15 +90,14 @@ func (e *Manager) Start(renderer interfaces.Renderer) {
8790
// Store renderer
8891
e.renderer = renderer
8992

90-
// Set up waitgroup so we can wait for goroutine to start
91-
var wg sync.WaitGroup
92-
wg.Add(1)
93+
// Set up waitgroup so we can wait for goroutine to quit
94+
e.running = true
95+
e.wg.Add(1)
9396

9497
// Run main loop in separate goroutine
9598
go func() {
96-
wg.Done()
9799
e.log.Info("Listening")
98-
for e.exit == false {
100+
for e.running {
99101
// TODO: Listen for application exit
100102
select {
101103
case event := <-e.incomingEvents:
@@ -139,14 +141,18 @@ func (e *Manager) Start(renderer interfaces.Renderer) {
139141
}
140142
}
141143
}
144+
default:
145+
time.Sleep(1 * time.Millisecond)
142146
}
143147
}
148+
e.wg.Done()
144149
}()
145-
146-
// Wait for goroutine to start
147-
wg.Wait()
148150
}
149151

150-
func (e *Manager) stop() {
151-
e.exit = true
152+
// Shutdown is called when exiting the Application
153+
func (e *Manager) Shutdown() {
154+
e.log.Debug("Shutting Down")
155+
e.running = false
156+
e.log.Debug("Waiting for main loop to exit")
157+
e.wg.Wait()
152158
}

lib/interfaces/bindingmanager.go

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ type BindingManager interface {
77
Bind(object interface{})
88
Start(renderer Renderer, runtime Runtime) error
99
ProcessCall(callData *messages.CallData) (result interface{}, err error)
10+
Shutdown()
1011
}

lib/interfaces/eventmanager.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ type EventManager interface {
88
Emit(eventName string, optionalData ...interface{})
99
On(eventName string, callback func(...interface{}))
1010
Start(Renderer)
11+
Shutdown()
1112
}

lib/interfaces/ipcmanager.go

+1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ type IPCManager interface {
55
BindRenderer(Renderer)
66
Dispatch(message string)
77
Start(eventManager EventManager, bindingManager BindingManager)
8+
Shutdown()
89
}

lib/ipc/manager.go

+22-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package ipc
22

33
import (
44
"fmt"
5+
"sync"
6+
"time"
57

68
"github.com/wailsapp/wails/lib/interfaces"
79
"github.com/wailsapp/wails/lib/logger"
@@ -12,18 +14,20 @@ import (
1214
type Manager struct {
1315
renderer interfaces.Renderer // The renderer
1416
messageQueue chan *ipcMessage
15-
// quitChannel chan struct{}
17+
quitChannel chan struct{}
1618
// signals chan os.Signal
1719
log *logger.CustomLogger
1820
eventManager interfaces.EventManager
1921
bindingManager interfaces.BindingManager
22+
running bool
23+
wg sync.WaitGroup
2024
}
2125

2226
// NewManager creates a new IPC Manager
2327
func NewManager() interfaces.IPCManager {
2428
result := &Manager{
2529
messageQueue: make(chan *ipcMessage, 100),
26-
// quitChannel: make(chan struct{}),
30+
quitChannel: make(chan struct{}),
2731
// signals: make(chan os.Signal, 1),
2832
log: logger.NewCustomLogger("IPC"),
2933
}
@@ -44,9 +48,12 @@ func (i *Manager) Start(eventManager interfaces.EventManager, bindingManager int
4448

4549
i.log.Info("Starting")
4650
// signal.Notify(manager.signals, os.Interrupt)
51+
i.running = true
52+
53+
// Keep track of this goroutine
54+
i.wg.Add(1)
4755
go func() {
48-
running := true
49-
for running {
56+
for i.running {
5057
select {
5158
case incomingMessage := <-i.messageQueue:
5259
i.log.DebugFields("Processing message", logger.Fields{
@@ -117,15 +124,12 @@ func (i *Manager) Start(eventManager interfaces.EventManager, bindingManager int
117124
i.log.DebugFields("Finished processing message", logger.Fields{
118125
"1D": &incomingMessage,
119126
})
120-
// case <-manager.quitChannel:
121-
// Debug("[MessageQueue] Quit caught")
122-
// running = false
123-
// case <-manager.signals:
124-
// Debug("[MessageQueue] Signal caught")
125-
// running = false
127+
default:
128+
time.Sleep(1 * time.Millisecond)
126129
}
127130
}
128131
i.log.Debug("Stopping")
132+
i.wg.Done()
129133
}()
130134
}
131135

@@ -167,3 +171,11 @@ func (i *Manager) SendResponse(response *ipcResponse) error {
167171
// Call back to the front end
168172
return i.renderer.Callback(data)
169173
}
174+
175+
// Shutdown is called when exiting the Application
176+
func (i *Manager) Shutdown() {
177+
i.log.Debug("Shutdown called")
178+
i.running = false
179+
i.log.Debug("Waiting of main loop shutdown")
180+
i.wg.Wait()
181+
}

lib/renderer/bridge.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func (h *Bridge) Run() error {
156156
h.log.Info("The frontend will connect automatically.")
157157

158158
err := h.server.ListenAndServe()
159-
if err != nil {
159+
if err != nil && err != http.ErrServerClosed {
160160
h.log.Fatal(err.Error())
161161
}
162162
return err
@@ -250,5 +250,9 @@ func (h *Bridge) SetTitle(title string) {
250250
// Close is unsupported for Bridge but required
251251
// for the Renderer interface
252252
func (h *Bridge) Close() {
253-
h.log.Warn("Close() unsupported in bridge mode")
253+
h.log.Debug("Shutting down")
254+
err := h.server.Close()
255+
if err != nil {
256+
h.log.Errorf(err.Error())
257+
}
254258
}

lib/renderer/webview/webview.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
package webview
1212

1313
/*
14-
#cgo linux openbsd freebsd CFLAGS: -DWEBVIEW_GTK=1
14+
#cgo linux openbsd freebsd CFLAGS: -DWEBVIEW_GTK=1 -Wno-deprecated-declarations
1515
#cgo linux openbsd freebsd pkg-config: gtk+-3.0 webkit2gtk-4.0
1616
1717
#cgo windows CFLAGS: -DWEBVIEW_WINAPI=1

0 commit comments

Comments
 (0)