1 //-----------------------------------------------------------------------------
2 // Flags       : clang-format auto
3 // Project     : VST SDK
4 //
5 // Category    : EditorHost
6 // Filename    : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.cpp
7 // Created by  : Steinberg 09.2016
8 // Description : Example of opening a plug-in editor
9 //
10 //-----------------------------------------------------------------------------
11 // LICENSE
12 // (c) 2020, Steinberg Media Technologies GmbH, All Rights Reserved
13 //-----------------------------------------------------------------------------
14 // Redistribution and use in source and binary forms, with or without modification,
15 // are permitted provided that the following conditions are met:
16 //
17 //   * Redistributions of source code must retain the above copyright notice,
18 //     this list of conditions and the following disclaimer.
19 //   * Redistributions in binary form must reproduce the above copyright notice,
20 //     this list of conditions and the following disclaimer in the documentation
21 //     and/or other materials provided with the distribution.
22 //   * Neither the name of the Steinberg Media Technologies nor the names of its
23 //     contributors may be used to endorse or promote products derived from this
24 //     software without specific prior written permission.
25 //
26 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
27 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29 // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
33 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
34 // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  OF THIS SOFTWARE, EVEN IF ADVISED
35 // OF THE POSSIBILITY OF SUCH DAMAGE.
36 //-----------------------------------------------------------------------------
37 
38 #include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.h"
39 #include <algorithm>
40 #include <iostream>
41 
42 //------------------------------------------------------------------------
43 namespace Steinberg {
44 namespace Vst {
45 namespace EditorHost {
46 
47 using LockGuard = std::lock_guard<std::recursive_mutex>;
48 
49 //------------------------------------------------------------------------
instance()50 RunLoop& RunLoop::instance ()
51 {
52 	static RunLoop gInstance;
53 	return gInstance;
54 }
55 
56 //------------------------------------------------------------------------
setDisplay(Display * display)57 void RunLoop::setDisplay (Display* display)
58 {
59 	this->display = display;
60 }
61 
62 //------------------------------------------------------------------------
registerWindow(XID window,const EventCallback & callback)63 void RunLoop::registerWindow (XID window, const EventCallback& callback)
64 {
65 	map.emplace (window, callback);
66 }
67 
68 //------------------------------------------------------------------------
unregisterWindow(XID window)69 void RunLoop::unregisterWindow (XID window)
70 {
71 	auto it = map.find (window);
72 	if (it == map.end ())
73 		return;
74 	map.erase (it);
75 }
76 
77 //------------------------------------------------------------------------
registerFileDescriptor(int fd,const FileDescriptorCallback & callback)78 void RunLoop::registerFileDescriptor (int fd, const FileDescriptorCallback& callback)
79 {
80 	fileDescriptors.emplace (fd, callback);
81 }
82 
83 //------------------------------------------------------------------------
unregisterFileDescriptor(int fd)84 void RunLoop::unregisterFileDescriptor (int fd)
85 {
86 	auto it = fileDescriptors.find (fd);
87 	if (it == fileDescriptors.end ())
88 		return;
89 	fileDescriptors.erase (it);
90 }
91 
92 //------------------------------------------------------------------------
select(timeval * timeout)93 void RunLoop::select (timeval* timeout)
94 {
95 	int nfds = 0;
96 	fd_set readFDs = {}, writeFDs = {}, exceptFDs = {};
97 
98 	for (auto& e : fileDescriptors)
99 	{
100 		int fd = e.first;
101 		FD_SET (fd, &readFDs);
102 		FD_SET (fd, &writeFDs);
103 		FD_SET (fd, &exceptFDs);
104 		nfds = std::max (nfds, fd);
105 	}
106 
107 	int result = ::select (nfds, &readFDs, &writeFDs, nullptr, timeout);
108 
109 	if (result > 0)
110 	{
111 		for (auto& e : fileDescriptors)
112 		{
113 			if (FD_ISSET (e.first, &readFDs) || FD_ISSET (e.first, &writeFDs) ||
114 			    FD_ISSET (e.first, &exceptFDs))
115 				e.second (e.first);
116 		}
117 	}
118 }
119 
120 //------------------------------------------------------------------------
handleEvents()121 bool RunLoop::handleEvents ()
122 {
123 	auto count = XPending (display);
124 	if (count == 0)
125 		return false;
126 	for (auto i = 0; i < count; ++i)
127 	{
128 		XEvent event {};
129 		XNextEvent (display, &event);
130 		auto it = map.find (event.xany.window);
131 		if (it != map.end ())
132 		{
133 			it->second (event);
134 			if (event.type == DestroyNotify)
135 			{
136 				map.erase (it);
137 			}
138 		}
139 		else
140 		{
141 			XPutBackEvent (display, &event);
142 			break;
143 		}
144 	}
145 	return true;
146 }
147 
148 //------------------------------------------------------------------------
registerTimer(TimerInterval interval,const TimerCallback & callback)149 TimerID RunLoop::registerTimer (TimerInterval interval, const TimerCallback& callback)
150 {
151 	return timerProcessor.registerTimer (interval, callback);
152 }
153 
154 //------------------------------------------------------------------------
unregisterTimer(TimerID id)155 void RunLoop::unregisterTimer (TimerID id)
156 {
157 	timerProcessor.unregisterTimer (id);
158 }
159 
160 //------------------------------------------------------------------------
timeValEmpty(timeval & val)161 bool timeValEmpty (timeval& val)
162 {
163 	return val.tv_sec == 0 && val.tv_usec == 0;
164 }
165 
166 //------------------------------------------------------------------------
start()167 void RunLoop::start ()
168 {
169 	using namespace std::chrono;
170 	using clock = high_resolution_clock;
171 
172 	running = true;
173 
174 	auto fd = XConnectionNumber (display);
175 	registerFileDescriptor (fd, [this] (int) { handleEvents (); });
176 
177 	XSync (display, false);
178 	handleEvents ();
179 	timeval selectTimeout {};
180 	while (running && !map.empty ())
181 	{
182 		select (timeValEmpty (selectTimeout) ? nullptr : &selectTimeout);
183 		auto nextFireTime = timerProcessor.handleTimersAndReturnNextFireTimeInMs ();
184 		if (nextFireTime == TimerProcessor::noTimers)
185 		{
186 			selectTimeout = {};
187 		}
188 		else
189 		{
190 			selectTimeout.tv_sec = nextFireTime / 1000;
191 			selectTimeout.tv_usec = (nextFireTime - (selectTimeout.tv_sec * 1000)) * 1000;
192 		}
193 	}
194 }
195 
196 //------------------------------------------------------------------------
stop()197 void RunLoop::stop ()
198 {
199 	running = false;
200 }
201 
202 //------------------------------------------------------------------------
203 //------------------------------------------------------------------------
204 //------------------------------------------------------------------------
handleTimersAndReturnNextFireTimeInMs()205 uint64_t TimerProcessor::handleTimersAndReturnNextFireTimeInMs ()
206 {
207 	using std::chrono::time_point_cast;
208 
209 	if (timers.empty ())
210 		return noTimers;
211 
212 	auto current = time_point_cast<Millisecond> (Clock::now ());
213 
214 	std::vector<TimerID> timersToFire;
215 	for (auto& timer : timers)
216 	{
217 		if (timer.nextFireTime > current)
218 			break;
219 		timersToFire.push_back (timer.id);
220 		updateTimerNextFireTime (timer, current);
221 	}
222 
223 	for (auto id : timersToFire)
224 	{
225 		for (auto& timer : timers)
226 		{
227 			if (timer.id == id)
228 			{
229 				timer.callback (timer.id);
230 				break;
231 			}
232 		}
233 	}
234 	if (timersToFire.empty ())
235 		return noTimers;
236 
237 	sortTimers ();
238 
239 	auto nextFireTime = timers.front ().nextFireTime;
240 	current = now ();
241 	if (nextFireTime < current)
242 		return 0;
243 	return (nextFireTime - current).count ();
244 }
245 
246 //------------------------------------------------------------------------
updateTimerNextFireTime(Timer & timer,TimePoint current)247 void TimerProcessor::updateTimerNextFireTime (Timer& timer, TimePoint current)
248 {
249 	timer.nextFireTime = current + Millisecond (timer.interval);
250 }
251 
252 //------------------------------------------------------------------------
sortTimers()253 void TimerProcessor::sortTimers ()
254 {
255 	std::sort (timers.begin (), timers.end (),
256 	           [] (const Timer& t1, const Timer& t2) { return t1.nextFireTime < t2.nextFireTime; });
257 }
258 
259 //------------------------------------------------------------------------
now()260 auto TimerProcessor::now () -> TimePoint
261 {
262 	using std::chrono::time_point_cast;
263 
264 	return time_point_cast<Millisecond> (Clock::now ());
265 }
266 
267 //------------------------------------------------------------------------
registerTimer(TimerInterval interval,const TimerCallback & callback)268 auto TimerProcessor::registerTimer (TimerInterval interval, const TimerCallback& callback)
269     -> TimerID
270 {
271 	auto timerId = ++timerIdCounter;
272 	Timer timer;
273 	timer.id = timerId;
274 	timer.callback = callback;
275 	timer.interval = interval;
276 	updateTimerNextFireTime (timer, now ());
277 
278 	timers.emplace_back (std::move (timer));
279 	sortTimers ();
280 
281 	return timerId;
282 }
283 
284 //------------------------------------------------------------------------
unregisterTimer(TimerID id)285 void TimerProcessor::unregisterTimer (TimerID id)
286 {
287 	for (auto it = timers.begin (), end = timers.end (); it != end; ++it)
288 	{
289 		if (it->id == id)
290 		{
291 			timers.erase (it);
292 			break;
293 		}
294 	}
295 }
296 
297 //------------------------------------------------------------------------
298 } // EditorHost
299 } // Vst
300 } // Steinberg
301