github.com/secoba/wails/v2@v2.6.4/internal/frontend/runtime/events.go (about) 1 package runtime 2 3 import ( 4 "sync" 5 6 "github.com/samber/lo" 7 "github.com/secoba/wails/v2/internal/frontend" 8 ) 9 10 type Logger interface { 11 Trace(format string, v ...interface{}) 12 } 13 14 // eventListener holds a callback function which is invoked when 15 // the event listened for is emitted. It has a counter which indicates 16 // how the total number of events it is interested in. A value of zero 17 // means it does not expire (default). 18 type eventListener struct { 19 callback func(...interface{}) // Function to call with emitted event data 20 counter int // The number of times this callback may be called. -1 = infinite 21 delete bool // Flag to indicate that this listener should be deleted 22 } 23 24 // Events handles eventing 25 type Events struct { 26 log Logger 27 frontend []frontend.Frontend 28 29 // Go event listeners 30 listeners map[string][]*eventListener 31 notifyLock sync.RWMutex 32 } 33 34 func (e *Events) Notify(sender frontend.Frontend, name string, data ...interface{}) { 35 e.notifyBackend(name, data...) 36 for _, thisFrontend := range e.frontend { 37 if thisFrontend == sender { 38 continue 39 } 40 thisFrontend.Notify(name, data...) 41 } 42 } 43 44 func (e *Events) On(eventName string, callback func(...interface{})) func() { 45 return e.registerListener(eventName, callback, -1) 46 } 47 48 func (e *Events) OnMultiple(eventName string, callback func(...interface{}), counter int) func() { 49 return e.registerListener(eventName, callback, counter) 50 } 51 52 func (e *Events) Once(eventName string, callback func(...interface{})) func() { 53 return e.registerListener(eventName, callback, 1) 54 } 55 56 func (e *Events) Emit(eventName string, data ...interface{}) { 57 e.notifyBackend(eventName, data...) 58 for _, thisFrontend := range e.frontend { 59 thisFrontend.Notify(eventName, data...) 60 } 61 } 62 63 func (e *Events) Off(eventName string) { 64 e.unRegisterListener(eventName) 65 } 66 67 func (e *Events) OffAll() { 68 e.notifyLock.Lock() 69 for eventName := range e.listeners { 70 delete(e.listeners, eventName) 71 } 72 e.notifyLock.Unlock() 73 } 74 75 // NewEvents creates a new log subsystem 76 func NewEvents(log Logger) *Events { 77 result := &Events{ 78 log: log, 79 listeners: make(map[string][]*eventListener), 80 } 81 return result 82 } 83 84 // registerListener provides a means of subscribing to events of type "eventName" 85 func (e *Events) registerListener(eventName string, callback func(...interface{}), counter int) func() { 86 // Create new eventListener 87 thisListener := &eventListener{ 88 callback: callback, 89 counter: counter, 90 delete: false, 91 } 92 e.notifyLock.Lock() 93 // Append the new listener to the listeners slice 94 e.listeners[eventName] = append(e.listeners[eventName], thisListener) 95 e.notifyLock.Unlock() 96 return func() { 97 e.notifyLock.Lock() 98 defer e.notifyLock.Unlock() 99 100 if _, ok := e.listeners[eventName]; !ok { 101 return 102 } 103 e.listeners[eventName] = lo.Filter(e.listeners[eventName], func(l *eventListener, i int) bool { 104 return l != thisListener 105 }) 106 } 107 } 108 109 // unRegisterListener provides a means of unsubscribing to events of type "eventName" 110 func (e *Events) unRegisterListener(eventName string) { 111 e.notifyLock.Lock() 112 // Clear the listeners 113 delete(e.listeners, eventName) 114 e.notifyLock.Unlock() 115 } 116 117 // Notify backend for the given event name 118 func (e *Events) notifyBackend(eventName string, data ...interface{}) { 119 e.notifyLock.Lock() 120 defer e.notifyLock.Unlock() 121 122 // Get list of event listeners 123 listeners := e.listeners[eventName] 124 if listeners == nil { 125 e.log.Trace("No listeners for event '%s'", eventName) 126 return 127 } 128 129 // We have a dirty flag to indicate that there are items to delete 130 itemsToDelete := false 131 132 // Callback in goroutine 133 for _, listener := range listeners { 134 if listener.counter > 0 { 135 listener.counter-- 136 } 137 go listener.callback(data...) 138 139 if listener.counter == 0 { 140 listener.delete = true 141 itemsToDelete = true 142 } 143 } 144 145 // Do we have items to delete? 146 if itemsToDelete { 147 148 // Create a new Listeners slice 149 var newListeners []*eventListener 150 151 // Iterate over current listeners 152 for _, listener := range listeners { 153 // If we aren't deleting the listener, add it to the new list 154 if !listener.delete { 155 newListeners = append(newListeners, listener) 156 } 157 } 158 159 // Save new listeners or remove entry 160 if len(newListeners) > 0 { 161 e.listeners[eventName] = newListeners 162 } else { 163 delete(e.listeners, eventName) 164 } 165 } 166 } 167 168 func (e *Events) AddFrontend(appFrontend frontend.Frontend) { 169 e.frontend = append(e.frontend, appFrontend) 170 }