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