File: mainloop_callbacks.go

package info (click to toggle)
go-dlib 5.6.0.9%2Bdfsg-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,212 kB
  • sloc: ansic: 4,664; xml: 1,456; makefile: 20; sh: 15
file content (177 lines) | stat: -rw-r--r-- 3,903 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package pulse

//#include "dde-pulse.h"
import "C"

import (
	"fmt"
	"runtime"
	"unsafe"
)

// ONLY C file and below function can use C.pa_threaded_mainloop_lock/unlock

type safeDoCtx struct {
	fn   func()
	loop *C.pa_threaded_mainloop
}

var pendingSafeDo = make(chan safeDoCtx)

func startHandleSafeDo() {
	// Move all safeDo fn to here to avoid creating too many OS Thread.
	// Because GOMAXPROC only apply to go-runtime.
	for c := range pendingSafeDo {
		if c.fn != nil {
			runtime.LockOSThread()
			C.pa_threaded_mainloop_lock(c.loop)
			c.fn()
			C.pa_threaded_mainloop_unlock(c.loop)
			runtime.UnlockOSThread()
		}
	}
}

func init() {
	// TODO: encapsulate the logic to Context and adding Start/Stop logic.
	go startHandleSafeDo()
}

func freeContext(ctx *Context) {
	runtime.LockOSThread()
	C.pa_threaded_mainloop_lock(ctx.loop)
	// The pa_context_unref must be protected.
	// This operation will cancel all pending operations which will touch  mainloop object.
	C.pa_context_unref(ctx.ctx)
	C.pa_threaded_mainloop_unlock(ctx.loop)
	// There no need to call pa_threaded_mainloop_stop.
	C.pa_threaded_mainloop_free(ctx.loop)
	runtime.UnlockOSThread()

	ctx.loop = nil
	ctx.ctx = nil
}

// safeDo invoke an function with lock
func (c *Context) safeDo(fn func()) {
	pendingSafeDo <- safeDoCtx{fn, c.loop}
}

func (c *Context) AddEventChan(ch chan<- *Event) {
	c.mu.Lock()
	c.eventChanList = append(c.eventChanList, ch)
	c.mu.Unlock()
}

func (c *Context) RemoveEventChan(ch chan<- *Event) {
	c.mu.Lock()
	var newList []chan<- *Event
	for _, ch0 := range c.eventChanList {
		if ch0 != ch {
			newList = append(newList, ch0)
		}
	}
	c.eventChanList = newList
	c.mu.Unlock()
}

func (c *Context) AddStateChan(ch chan<- int) {
	c.mu.Lock()
	c.stateChanList = append(c.stateChanList, ch)
	c.mu.Unlock()
}

func (c *Context) RemoveStateChan(ch chan<- int) {
	c.mu.Lock()
	var newList []chan<- int
	for _, ch0 := range c.stateChanList {
		if ch0 != ch {
			newList = append(newList, ch0)
		}
	}
	c.stateChanList = newList
	c.mu.Unlock()
}

// ALL of below functions are invoked from pulse's mainloop thread.
// So
//   1. Don't hold go-runtime lock in current thread.
//   2. Don't hold pa_threaded_mainloop in current thread.
//
// NOTE:
// In pa_threaded_mainloop, _prepare_ and _dispatch_ is running
// under lock. Only phrase _poll_ is release the lock. So callback
// must not try to hold lock.

//export go_handle_changed
func go_handle_changed(facility int, event_type int, idx uint32) {

	event := &Event{
		Facility: facility,
		Type:     event_type,
		Index:    idx,
	}

	ctx := GetContext()
	ctx.mu.Lock()

	for _, eventChan := range ctx.eventChanList {
		select {
		case eventChan <- event:
		default:
		}
	}

	ctx.mu.Unlock()
}

//export go_handle_state_changed
func go_handle_state_changed(state int) {
	ctx := GetContext()
	ctx.mu.Lock()

	for _, stateChan := range ctx.stateChanList {
		select {
		case stateChan <- state:
		default:
		}
	}

	ctx.mu.Unlock()
}

//export go_update_volume_meter
func go_update_volume_meter(source_index uint32, sink_index uint32, v float64) {
	sourceMeterLock.RLock()
	cb, ok := sourceMeterCBs[source_index]
	sourceMeterLock.RUnlock()

	if ok && cb != nil {
		cb(v)
	}
}

//export go_receive_some_info
func go_receive_some_info(cookie int64, infoType int, info unsafe.Pointer, eol int) {
	ck := fetchCookie(cookie)
	if ck == nil {
		if eol == 0 {
			// Sometimes pulseaudio will send eol=1 to pa_context_get_*_by_index/name.
			fmt.Println("Warning: recieve_some_info with nil cookie", cookie, infoType, info)
		}
		return
	}

	switch {
	case eol == 1:
		ck.EndOfList()
	case eol == 0:
		// 1. the Feed will be blocked until the ck creator received.
		// 2. the creator of ck may be invoked under PendingCallback
		// so this must not be moved to PendingCallback.
		paInfo := NewPaInfo(info, infoType)
		ck.Feed(paInfo)
	case eol < 0:
		ck.Failed()
	}
}