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/window.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 "window.h"
39 
40 #ifdef EDITORHOST_GTK
41 #include <gtkmm.h>
42 #endif
43 
44 #include "public.sdk/source/vst/utility/stringconvert.h"
45 #include <X11/Xutil.h>
46 #include <cassert>
47 #include <iostream>
48 #include <unordered_map>
49 
50 #ifdef EDITORHOST_GTK
51 #include <gdk/gdkx.h>
52 #include <gtkmm/socket.h>
53 #else
54 #include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.h"
55 #include <X11/Xlib.h>
56 #endif
57 
58 //------------------------------------------------------------------------
59 namespace Steinberg {
60 namespace Vst {
61 namespace EditorHost {
62 
63 #ifdef EDITORHOST_GTK
64 
65 struct ContentView : Gtk::Fixed
66 {
on_size_allocateSteinberg::Vst::EditorHost::ContentView67 	void on_size_allocate (Gtk::Allocation& allocation) override
68 	{
69 		Gtk::Widget::on_size_allocate (allocation);
70 		for (auto child : get_children ())
71 		{
72 			child->size_allocate (allocation);
73 		}
74 	}
75 };
76 
77 struct WindowGtk : Gtk::Window
78 {
WindowGtkSteinberg::Vst::EditorHost::WindowGtk79 	WindowGtk ()
80 	{
81 		add (container);
82 		container.put (label, 0, 0);
83 		container.add (socket);
84 		show_all_children ();
85 	}
86 
on_showSteinberg::Vst::EditorHost::WindowGtk87 	void on_show () override
88 	{
89 		Gtk::Window::on_show ();
90 		if (controller)
91 			controller->onShow (*window);
92 	}
93 
on_unmapSteinberg::Vst::EditorHost::WindowGtk94 	void on_unmap () override
95 	{
96 		Gtk::Window::on_unmap ();
97 		if (controller)
98 			controller->onClose (*window);
99 		if (windowClosedFunc)
100 			windowClosedFunc (window);
101 	}
102 
on_configure_eventSteinberg::Vst::EditorHost::WindowGtk103 	bool on_configure_event (GdkEventConfigure* event) override
104 	{
105 		// TODO: does not work yet
106 		Gtk::Window::on_configure_event (event);
107 		return false;
108 		if (controller)
109 		{
110 			if (currentSize.width != event->width || currentSize.height != event->height)
111 			{
112 				Size newSize {event->width, event->height};
113 				auto size = controller->constrainSize (*window, newSize);
114 				if (size != newSize)
115 				{
116 					resize (size.width, size.height);
117 				}
118 			}
119 		}
120 		return true;
121 	}
122 
on_size_allocateSteinberg::Vst::EditorHost::WindowGtk123 	void on_size_allocate (Gtk::Allocation& allocation) override
124 	{
125 		auto size = Size {allocation.get_width (), allocation.get_height ()};
126 		if (size != currentSize)
127 		{
128 			if (controller)
129 			{
130 				size = controller->constrainSize (*window, size);
131 				controller->onResize (*window, size);
132 			}
133 			currentSize = size;
134 			allocation.set_width (currentSize.width);
135 			allocation.set_height (currentSize.height);
136 			resize (currentSize.width, currentSize.height);
137 		}
138 		Gtk::Window::on_size_allocate (allocation);
139 	}
140 
checkSizeSteinberg::Vst::EditorHost::WindowGtk141 	void checkSize ()
142 	{
143 		int width, height;
144 		get_size (width, height);
145 		if (currentSize.width != width || currentSize.height != height)
146 		{
147 			resize (currentSize.width, currentSize.height);
148 		}
149 	}
150 
getIdSteinberg::Vst::EditorHost::WindowGtk151 	::Window getId ()
152 	{
153 		if (!socket.is_visible ())
154 			return 0;
155 		std::cout << "Socket ID: " << socket.get_id () << "\n";
156 		std::cout.flush ();
157 		return socket.get_id ();
158 	}
159 
160 	X11Window::WindowClosedFunc windowClosedFunc;
161 	WindowControllerPtr controller {nullptr};
162 	X11Window* window {nullptr};
163 	Size currentSize;
164 	ContentView container;
165 	Gtk::Label label {"HostLabel"};
166 	Gtk::Socket socket;
167 };
168 #else
169 
170 #endif
171 
172 //------------------------------------------------------------------------
173 struct X11Window::Impl : public Linux::IRunLoop
174 {
175 	Impl (X11Window* x11Window);
176 	bool init (const std::string& name, Size size, bool resizeable,
177 	           const WindowControllerPtr& controller, Display* display,
178 	           const WindowClosedFunc& windowClosedFunc);
179 
180 	void show ();
181 	void close ();
182 	Size getSize () const;
183 	void resize (Size newSize, bool force);
184 
185 #ifdef EDITORHOST_GTK
186 	WindowGtk window;
187 
188 //------------------------------------------------------------------------
189 	struct EventHandler
190 	{
191 		IPtr<Linux::IEventHandler> handler;
192 		GIOChannel* ioChannel;
193 	};
194 
195 //------------------------------------------------------------------------
196 	using TimerHandler = IPtr<Linux::ITimerHandler>;
197 	using TimerID = guint;
198 #else
199 //------------------------------------------------------------------------
200 	struct XEmbedInfo
201 	{
202 		uint32_t version;
203 		uint32_t flags;
204 	};
205 
206 	~Impl ();
207 	void onClose ();
208 	bool handleMainWindowEvent (const XEvent& event);
209 	bool handlePlugEvent (const XEvent& event);
210 	XEmbedInfo* getXEmbedInfo ();
211 	void checkSize ();
212 	void callPlugEventHandlers ();
213 
214 	WindowControllerPtr controller {nullptr};
215 	WindowClosedFunc windowClosedFunc;
216 	Display* xDisplay {nullptr};
217 	XEmbedInfo* xembedInfo {nullptr};
218 	Window xWindow {};
219 	Window plugParentWindow {};
220 	Window plugWindow {};
221 	GC xGraphicContext {};
222 	Atom xEmbedInfoAtom {None};
223 	Atom xEmbedAtom {None};
224 	bool isMapped {false};
225 
226 	using EventHandler = IPtr<Linux::IEventHandler>;
227 	using TimerHandler = IPtr<Linux::ITimerHandler>;
228 #endif
229 	Size mCurrentSize {};
230 	X11Window* x11Window {nullptr};
231 
232 	using EventHandlers = std::unordered_map<Linux::FileDescriptor, EventHandler>;
233 	EventHandlers eventHandlers;
234 
235 	using TimerHandlers = std::unordered_map<TimerID, TimerHandler>;
236 	TimerHandlers timerHandlers;
237 
238 	tresult PLUGIN_API registerEventHandler (Linux::IEventHandler* handler,
239 	                                         Linux::FileDescriptor fd) override;
240 	tresult PLUGIN_API unregisterEventHandler (Linux::IEventHandler* handler) override;
241 	tresult PLUGIN_API registerTimer (Linux::ITimerHandler* handler,
242 	                                  Linux::TimerInterval milliseconds) override;
243 	tresult PLUGIN_API unregisterTimer (Linux::ITimerHandler* handler) override;
244 
addRefSteinberg::Vst::EditorHost::X11Window::Impl245 	uint32 PLUGIN_API addRef () override { return 1000; }
releaseSteinberg::Vst::EditorHost::X11Window::Impl246 	uint32 PLUGIN_API release () override { return 1000; }
queryInterfaceSteinberg::Vst::EditorHost::X11Window::Impl247 	tresult PLUGIN_API queryInterface (const TUID, void**) override { return kNoInterface; }
248 };
249 
250 //------------------------------------------------------------------------
make(const std::string & name,Size size,bool resizeable,const WindowControllerPtr & controller,Display * display,const WindowClosedFunc & windowClosedFunc)251 auto X11Window::make (const std::string& name, Size size, bool resizeable,
252                       const WindowControllerPtr& controller, Display* display,
253                       const WindowClosedFunc& windowClosedFunc) -> Ptr
254 {
255 	auto window = std::make_shared<X11Window> ();
256 	if (window->init (name, size, resizeable, controller, display, windowClosedFunc))
257 	{
258 		return window;
259 	}
260 	return nullptr;
261 }
262 
263 //------------------------------------------------------------------------
X11Window()264 X11Window::X11Window ()
265 {
266 	impl = std::unique_ptr<Impl> (new Impl (this));
267 }
268 
269 //------------------------------------------------------------------------
~X11Window()270 X11Window::~X11Window ()
271 {
272 }
273 
274 //------------------------------------------------------------------------
init(const std::string & name,Size size,bool resizeable,const WindowControllerPtr & controller,Display * display,const WindowClosedFunc & windowClosedFunc)275 bool X11Window::init (const std::string& name, Size size, bool resizeable,
276                       const WindowControllerPtr& controller, Display* display,
277                       const WindowClosedFunc& windowClosedFunc)
278 {
279 	return impl->init (name, size, resizeable, controller, display, windowClosedFunc);
280 }
281 
282 //------------------------------------------------------------------------
getSize() const283 Size X11Window::getSize () const
284 {
285 	return impl->getSize ();
286 }
287 
288 //------------------------------------------------------------------------
show()289 void X11Window::show ()
290 {
291 	impl->show ();
292 }
293 
294 //------------------------------------------------------------------------
close()295 void X11Window::close ()
296 {
297 	impl->close ();
298 }
299 
300 //------------------------------------------------------------------------
resize(Size newSize)301 void X11Window::resize (Size newSize)
302 {
303 	impl->resize (newSize, false);
304 }
305 
306 //------------------------------------------------------------------------
getContentSize()307 Size X11Window::getContentSize ()
308 {
309 	return {};
310 }
311 
312 //------------------------------------------------------------------------
getNativePlatformWindow() const313 NativePlatformWindow X11Window::getNativePlatformWindow () const
314 {
315 #ifdef EDITORHOST_GTK
316 	return {kPlatformTypeX11EmbedWindowID, reinterpret_cast<void*> (impl->window.getId ())};
317 #else
318 	return {kPlatformTypeX11EmbedWindowID, reinterpret_cast<void*> (impl->plugParentWindow)};
319 #endif
320 }
321 
322 //------------------------------------------------------------------------
queryInterface(const TUID iid,void ** obj)323 tresult X11Window::queryInterface (const TUID iid, void** obj)
324 {
325 	if (FUnknownPrivate::iidEqual (iid, Linux::IRunLoop::iid))
326 	{
327 		*obj = impl.get ();
328 		return kResultTrue;
329 	}
330 	return kNoInterface;
331 }
332 
333 //------------------------------------------------------------------------
onIdle()334 void X11Window::onIdle ()
335 {
336 #ifdef EDITORHOST_GTK
337 	impl->window.checkSize ();
338 #endif
339 }
340 
341 #ifdef EDITORHOST_GTK
342 //------------------------------------------------------------------------
Impl(X11Window * x11Window)343 X11Window::Impl::Impl (X11Window* x11Window) : x11Window (x11Window)
344 {
345 }
346 
347 //------------------------------------------------------------------------
init(const std::string & name,Size size,bool resizeable,const WindowControllerPtr & controller,Display * display,const WindowClosedFunc & windowClosedFunc)348 bool X11Window::Impl::init (const std::string& name, Size size, bool resizeable,
349                             const WindowControllerPtr& controller, Display* display,
350                             const WindowClosedFunc& windowClosedFunc)
351 {
352 	window.set_title (name.data ());
353 	window.set_default_size (size.width, size.height);
354 	window.set_resizable (resizeable);
355 	window.show ();
356 	window.set_visible (false);
357 
358 	window.window = x11Window;
359 	window.controller = controller;
360 	window.windowClosedFunc = windowClosedFunc;
361 
362 	return true;
363 }
364 
365 //------------------------------------------------------------------------
show()366 void X11Window::Impl::show ()
367 {
368 	window.set_visible (true);
369 	window.show ();
370 }
371 
372 //------------------------------------------------------------------------
close()373 void X11Window::Impl::close ()
374 {
375 	window.close ();
376 }
377 
378 //------------------------------------------------------------------------
getSize() const379 Size X11Window::Impl::getSize () const
380 {
381 	int width, height;
382 	window.get_size (width, height);
383 	return {width, height};
384 }
385 
386 //------------------------------------------------------------------------
resize(Size newSize,bool force)387 void X11Window::Impl::resize (Size newSize, bool force)
388 {
389 	if (!force && mCurrentSize == newSize)
390 		return;
391 	window.resize (newSize.width, newSize.height);
392 	mCurrentSize = newSize;
393 }
394 
395 //------------------------------------------------------------------------
registerEventHandler(Linux::IEventHandler * handler,Linux::FileDescriptor fd)396 tresult PLUGIN_API X11Window::Impl::registerEventHandler (Linux::IEventHandler* handler,
397                                                           Linux::FileDescriptor fd)
398 {
399 	if (!handler)
400 		return kInvalidArgument;
401 
402 	if (eventHandlers.find (fd) != eventHandlers.end ())
403 		return kInvalidArgument;
404 
405 	auto display = gdk_window_get_display (window.get_window ()->gobj ());
406 	auto xDisplay = gdk_x11_display_get_xdisplay (display);
407 	if (XConnectionNumber (xDisplay) == fd)
408 	{
409 		// the plug-in is using GTK as well, so no need in registering it as event handler
410 		return kResultTrue;
411 	}
412 
413 	auto ioChannel = g_io_channel_unix_new (fd);
414 	g_io_add_watch (ioChannel, (GIOCondition) (G_IO_IN | G_IO_OUT | G_IO_ERR | G_IO_HUP),
415 	                [] (auto* source, auto condition, auto data) {
416 		                (void)condition;
417 		                auto handler = reinterpret_cast<Linux::IEventHandler*> (data);
418 		                handler->onFDIsSet (g_io_channel_unix_get_fd (source));
419 		                return static_cast<int> (0);
420 	                },
421 	                handler);
422 
423 	eventHandlers[fd] = {handler, ioChannel};
424 
425 	return kResultTrue;
426 }
427 
428 //------------------------------------------------------------------------
unregisterEventHandler(Linux::IEventHandler * handler)429 tresult PLUGIN_API X11Window::Impl::unregisterEventHandler (Linux::IEventHandler* handler)
430 {
431 	if (!handler)
432 		return kInvalidArgument;
433 
434 	for (auto it = eventHandlers.begin (), end = eventHandlers.end (); it != end; ++it)
435 	{
436 		if (it->second.handler == handler)
437 		{
438 			g_io_channel_unref (it->second.ioChannel);
439 			eventHandlers.erase (it);
440 			return kResultTrue;
441 		}
442 	}
443 
444 	return kResultFalse;
445 }
446 
447 //------------------------------------------------------------------------
registerTimer(Linux::ITimerHandler * handler,Linux::TimerInterval milliseconds)448 tresult PLUGIN_API X11Window::Impl::registerTimer (Linux::ITimerHandler* handler,
449                                                    Linux::TimerInterval milliseconds)
450 {
451 	if (!handler || milliseconds == 0)
452 		return kInvalidArgument;
453 
454 	auto id = g_timeout_add (milliseconds,
455 	                         [] (auto data) {
456 		                         auto handler = reinterpret_cast<Linux::ITimerHandler*> (data);
457 		                         handler->onTimer ();
458 		                         return static_cast<gboolean> (true);
459 	                         },
460 	                         handler);
461 	timerHandlers.emplace (id, handler);
462 	return kResultTrue;
463 }
464 
465 //------------------------------------------------------------------------
unregisterTimer(Linux::ITimerHandler * handler)466 tresult PLUGIN_API X11Window::Impl::unregisterTimer (Linux::ITimerHandler* handler)
467 {
468 	if (!handler)
469 		return kInvalidArgument;
470 
471 	for (auto it = timerHandlers.begin (), end = timerHandlers.end (); it != end; ++it)
472 	{
473 		if (it->second == handler)
474 		{
475 			if (auto source = g_main_context_find_source_by_id (nullptr, it->first))
476 			{
477 				g_source_destroy (source);
478 			}
479 			timerHandlers.erase (it);
480 			return kResultTrue;
481 		}
482 	}
483 
484 	return kNotImplemented;
485 }
486 
487 #else
488 
489 /* XEMBED messages */
490 #define XEMBED_EMBEDDED_NOTIFY 0
491 #define XEMBED_WINDOW_ACTIVATE 1
492 #define XEMBED_WINDOW_DEACTIVATE 2
493 #define XEMBED_REQUEST_FOCUS 3
494 #define XEMBED_FOCUS_IN 4
495 #define XEMBED_FOCUS_OUT 5
496 #define XEMBED_FOCUS_NEXT 6
497 #define XEMBED_FOCUS_PREV 7
498 /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
499 #define XEMBED_MODALITY_ON 10
500 #define XEMBED_MODALITY_OFF 11
501 #define XEMBED_REGISTER_ACCELERATOR 12
502 #define XEMBED_UNREGISTER_ACCELERATOR 13
503 #define XEMBED_ACTIVATE_ACCELERATOR 14
504 
send_xembed_message(Display * dpy,Window w,Atom messageType,long message,long detail,long data1,long data2)505 void send_xembed_message (Display* dpy, /* display */
506                           Window w, /* receiver */
507                           Atom messageType, long message, /* message opcode */
508                           long detail, /* message detail */
509                           long data1, /* message data 1 */
510                           long data2 /* message data 2 */
511                           )
512 {
513 	XEvent ev;
514 	memset (&ev, 0, sizeof (ev));
515 	ev.xclient.type = ClientMessage;
516 	ev.xclient.window = w;
517 	ev.xclient.message_type = messageType;
518 	ev.xclient.format = 32;
519 	ev.xclient.data.l[0] = CurrentTime;
520 	ev.xclient.data.l[1] = message;
521 	ev.xclient.data.l[2] = detail;
522 	ev.xclient.data.l[3] = data1;
523 	ev.xclient.data.l[4] = data2;
524 	XSendEvent (dpy, w, False, NoEventMask, &ev);
525 	XSync (dpy, False);
526 }
527 
528 #define XEMBED_MAPPED (1 << 0)
529 
530 //------------------------------------------------------------------------
Impl(X11Window * x11Window)531 X11Window::Impl::Impl (X11Window* x11Window) : x11Window (x11Window)
532 {
533 }
534 
535 //------------------------------------------------------------------------
~Impl()536 X11Window::Impl::~Impl ()
537 {
538 }
539 
540 //------------------------------------------------------------------------
init(const std::string & name,Size size,bool resizeable,const WindowControllerPtr & controller,Display * display,const WindowClosedFunc & windowClosedFunc)541 bool X11Window::Impl::init (const std::string& name, Size size, bool resizeable,
542                             const WindowControllerPtr& controller, Display* display,
543                             const WindowClosedFunc& windowClosedFunc)
544 {
545 	this->windowClosedFunc = windowClosedFunc;
546 	this->controller = controller;
547 	xDisplay = display;
548 
549 	xEmbedInfoAtom = XInternAtom (xDisplay, "_XEMBED_INFO", true);
550 	if (xEmbedInfoAtom == None)
551 	{
552 		std::cerr << "_XEMBED_INFO does not exist" << std::endl;
553 		return false;
554 	}
555 
556 	// Get screen size from display
557 	auto screen_num = DefaultScreen (xDisplay);
558 	auto displayWidth = DisplayWidth (xDisplay, screen_num);
559 	auto displayHeight = DisplayHeight (xDisplay, screen_num);
560 	unsigned int border_width = 1;
561 
562 	XVisualInfo vInfo;
563 	if (!XMatchVisualInfo (xDisplay, screen_num, 24, TrueColor, &vInfo))
564 	{
565 		exit (-1);
566 	}
567 
568 	XSetWindowAttributes winAttr {};
569 	winAttr.border_pixel = BlackPixel (xDisplay, screen_num);
570 	winAttr.background_pixel = WhitePixel (xDisplay, screen_num);
571 	winAttr.colormap =
572 	    XCreateColormap (xDisplay, XDefaultRootWindow (xDisplay), vInfo.visual, AllocNone);
573 	uint32_t winAttrMask = CWBackPixel | CWColormap | CWBorderPixel;
574 	xWindow = XCreateWindow (xDisplay, RootWindow (xDisplay, screen_num), 0, 0, displayWidth,
575 	                         displayHeight, border_width, vInfo.depth, InputOutput, vInfo.visual,
576 	                         winAttrMask, &winAttr);
577 	XFlush (xDisplay);
578 
579 	resize (size, true);
580 
581 	XSelectInput (xDisplay, xWindow, /*  KeyPressMask | ButtonPressMask |*/
582 	              ExposureMask | /*ResizeRedirectMask |*/ StructureNotifyMask |
583 	                  SubstructureNotifyMask | FocusChangeMask);
584 
585 	auto sizeHints = XAllocSizeHints ();
586 	sizeHints->flags = PMinSize;
587 	if (!resizeable)
588 	{
589 		sizeHints->flags |= PMaxSize;
590 		sizeHints->min_width = sizeHints->max_width = size.width;
591 		sizeHints->min_height = sizeHints->max_height = size.height;
592 	}
593 	else
594 	{
595 		sizeHints->min_width = sizeHints->min_height = 80;
596 	}
597 	XSetWMNormalHints (xDisplay, xWindow, sizeHints);
598 	XFree (sizeHints);
599 
600 	// set a title
601 	XStoreName (xDisplay, xWindow, name.data ());
602 
603 	XTextProperty iconName;
604 	auto icon_name = const_cast<char*> (name.data ());
605 	XStringListToTextProperty (&icon_name, 1, &iconName);
606 	XSetWMIconName (xDisplay, xWindow, &iconName);
607 
608 	Atom wm_delete_window;
609 	wm_delete_window = XInternAtom (xDisplay, "WM_DELETE_WINDOW", False);
610 	XSetWMProtocols (xDisplay, xWindow, &wm_delete_window, 1);
611 
612 	xGraphicContext = XCreateGC (xDisplay, xWindow, 0, 0);
613 	XSetForeground (xDisplay, xGraphicContext, WhitePixel (xDisplay, screen_num));
614 	XSetBackground (xDisplay, xGraphicContext, BlackPixel (xDisplay, screen_num));
615 
616 	winAttr = {};
617 	winAttr.override_redirect = true;
618 	winAttr.event_mask =
619 	    ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask;
620 	plugParentWindow =
621 	    XCreateWindow (xDisplay, xWindow, 0, 0, size.width, size.height, border_width, vInfo.depth,
622 	                   InputOutput, CopyFromParent, winAttrMask, &winAttr);
623 
624 	XSelectInput (xDisplay, plugParentWindow, SubstructureNotifyMask | PropertyChangeMask);
625 
626 	XMapWindow (xDisplay, plugParentWindow);
627 
628 	RunLoop::instance ().registerWindow (plugParentWindow,
629 	                                     [this] (const XEvent& e) { return handlePlugEvent (e); });
630 
631 	RunLoop::instance ().registerWindow (
632 	    xWindow, [this] (const XEvent& e) { return handleMainWindowEvent (e); });
633 
634 	return true;
635 }
636 
637 //------------------------------------------------------------------------
registerEventHandler(Linux::IEventHandler * handler,Linux::FileDescriptor fd)638 tresult PLUGIN_API X11Window::Impl::registerEventHandler (Linux::IEventHandler* handler,
639                                                           Linux::FileDescriptor fd)
640 {
641 	if (!handler || eventHandlers.find (fd) != eventHandlers.end ())
642 		return kInvalidArgument;
643 
644 	RunLoop::instance ().registerFileDescriptor (fd,
645 	                                             [handler] (int fd) { handler->onFDIsSet (fd); });
646 	eventHandlers.emplace (fd, handler);
647 	return kResultTrue;
648 }
649 
650 //------------------------------------------------------------------------
unregisterEventHandler(Linux::IEventHandler * handler)651 tresult PLUGIN_API X11Window::Impl::unregisterEventHandler (Linux::IEventHandler* handler)
652 {
653 	if (!handler)
654 		return kInvalidArgument;
655 
656 	for (auto it = eventHandlers.begin (), end = eventHandlers.end (); it != end; ++it)
657 	{
658 		if (it->second == handler)
659 		{
660 			RunLoop::instance ().unregisterFileDescriptor (it->first);
661 			eventHandlers.erase (it);
662 			return kResultTrue;
663 		}
664 	}
665 
666 	return kResultFalse;
667 }
668 
669 //------------------------------------------------------------------------
registerTimer(Linux::ITimerHandler * handler,Linux::TimerInterval milliseconds)670 tresult PLUGIN_API X11Window::Impl::registerTimer (Linux::ITimerHandler* handler,
671                                                    Linux::TimerInterval milliseconds)
672 {
673 	if (!handler || milliseconds == 0)
674 		return kInvalidArgument;
675 
676 	auto id = RunLoop::instance ().registerTimer (milliseconds,
677 	                                              [handler] (auto) { handler->onTimer (); });
678 	timerHandlers.emplace (id, handler);
679 	return kResultTrue;
680 }
681 
682 //------------------------------------------------------------------------
unregisterTimer(Linux::ITimerHandler * handler)683 tresult PLUGIN_API X11Window::Impl::unregisterTimer (Linux::ITimerHandler* handler)
684 {
685 	if (!handler)
686 		return kInvalidArgument;
687 
688 	for (auto it = timerHandlers.begin (), end = timerHandlers.end (); it != end; ++it)
689 	{
690 		if (it->second == handler)
691 		{
692 			RunLoop::instance ().unregisterTimer (it->first);
693 			timerHandlers.erase (it);
694 			return kResultTrue;
695 		}
696 	}
697 
698 	return kNotImplemented;
699 }
700 
701 //------------------------------------------------------------------------
callPlugEventHandlers()702 void X11Window::Impl::callPlugEventHandlers ()
703 {
704 	for (auto& e : eventHandlers)
705 	{
706 		e.second->onFDIsSet (e.first);
707 	}
708 }
709 
710 //------------------------------------------------------------------------
show()711 void X11Window::Impl::show ()
712 {
713 	XMapWindow (xDisplay, xWindow);
714 }
715 
716 //------------------------------------------------------------------------
close()717 void X11Window::Impl::close ()
718 {
719 	XUnmapWindow (xDisplay, xWindow);
720 }
721 
722 //------------------------------------------------------------------------
onClose()723 void X11Window::Impl::onClose ()
724 {
725 	XFreeGC (xDisplay, xGraphicContext);
726 	XDestroyWindow (xDisplay, xWindow);
727 
728 	xDisplay = nullptr;
729 	xWindow = 0;
730 
731 	isMapped = false;
732 	if (windowClosedFunc)
733 		windowClosedFunc (x11Window);
734 }
735 
736 //------------------------------------------------------------------------
resize(Size newSize,bool force)737 void X11Window::Impl::resize (Size newSize, bool force)
738 {
739 	if (!force && mCurrentSize == newSize)
740 		return;
741 	if (xWindow)
742 		XResizeWindow (xDisplay, xWindow, newSize.width, newSize.height);
743 	if (plugParentWindow)
744 		XResizeWindow (xDisplay, plugParentWindow, newSize.width, newSize.height);
745 	mCurrentSize = newSize;
746 }
747 
748 //------------------------------------------------------------------------
getSize() const749 Size X11Window::Impl::getSize () const
750 {
751 	::Window root;
752 	int x, y;
753 	unsigned int width, height;
754 	unsigned int border_width;
755 	unsigned int depth;
756 
757 	XGetGeometry (xDisplay, xWindow, &root, &x, &y, &width, &height, &border_width, &depth);
758 
759 	return {static_cast<int> (width), static_cast<int> (height)};
760 }
761 
762 //------------------------------------------------------------------------
checkSize()763 void X11Window::Impl::checkSize ()
764 {
765 	if (getSize () != mCurrentSize)
766 	{
767 		resize (mCurrentSize, true);
768 	}
769 }
770 
771 //------------------------------------------------------------------------
handleMainWindowEvent(const XEvent & event)772 bool X11Window::Impl::handleMainWindowEvent (const XEvent& event)
773 {
774 #if LOG_EVENTS
775 	std::cout << "event " << event.type << "\n";
776 #endif
777 	bool res = false;
778 
779 	switch (event.type)
780 	{
781 		case Expose:
782 			if (event.xexpose.count == 0)
783 			{
784 				XClearWindow (xDisplay, xWindow);
785 				XFillRectangle (xDisplay, xWindow, xGraphicContext, 0, 0, mCurrentSize.width,
786 				                mCurrentSize.height);
787 			}
788 			res = true;
789 			break;
790 
791 		//--- StructureNotifyMask ------------------------------
792 		// Window has been resized
793 		case ConfigureNotify:
794 		{
795 			if (event.xconfigure.window != xWindow)
796 				break;
797 
798 			auto width = event.xconfigure.width;
799 			auto height = event.xconfigure.height;
800 
801 			Size size {width, height};
802 			if (mCurrentSize != size)
803 			{
804 				auto constraintSize = controller->constrainSize (*x11Window, size);
805 				if (constraintSize != mCurrentSize)
806 				{
807 					mCurrentSize = size;
808 					controller->onResize (*x11Window, size);
809 				}
810 				if (constraintSize != size)
811 					resize (constraintSize, true);
812 				else
813 				{
814 					if (plugParentWindow)
815 						XResizeWindow (xDisplay, plugParentWindow, size.width, size.height);
816 				}
817 
818 #if LOG_EVENTS
819 				std::cout << "new size " << width << " x " << height << "\n";
820 #endif
821 			}
822 			res = true;
823 		}
824 		break;
825 
826 		// Window has been map to the screen
827 		case MapNotify:
828 		{
829 			if (event.xany.window == xWindow && !isMapped)
830 			{
831 				controller->onShow (*x11Window);
832 				isMapped = true;
833 				res = true;
834 			}
835 		}
836 		break;
837 
838 		case UnmapNotify:
839 		{
840 			if (event.xunmap.window == xWindow)
841 			{
842 				controller->onClose (*x11Window);
843 				onClose ();
844 				res = true;
845 			}
846 			break;
847 		}
848 		case DestroyNotify: break;
849 
850 		case ClientMessage:
851 		{
852 			if (event.xany.window == xWindow)
853 			{
854 				controller->onClose (*x11Window);
855 				onClose ();
856 				res = true;
857 			}
858 			break;
859 		}
860 
861 		case FocusIn:
862 		{
863 			if (xembedInfo)
864 				send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_ACTIVATE, 0,
865 				                     plugParentWindow, xembedInfo->version);
866 			break;
867 		}
868 		case FocusOut:
869 		{
870 			if (xembedInfo)
871 				send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_DEACTIVATE, 0,
872 				                     plugParentWindow, xembedInfo->version);
873 			break;
874 		}
875 
876 		//--- ResizeRedirectMask --------------------------------
877 		case ResizeRequest:
878 		{
879 			if (event.xany.window == xWindow)
880 			{
881 				auto width = event.xresizerequest.width;
882 				auto height = event.xresizerequest.height;
883 				Size request {width, height};
884 				if (mCurrentSize != request)
885 				{
886 #if LOG_EVENTS
887 					std::cout << "requested Size " << width << " x " << height << "\n";
888 #endif
889 
890 					auto constraintSize = controller->constrainSize (*x11Window, request);
891 					if (constraintSize != request)
892 					{
893 #if LOG_EVENTS
894 						std::cout << "constraint Size " << constraintSize.width << " x "
895 						          << constraintSize.height << "\n";
896 #endif
897 					}
898 					resize (constraintSize, true);
899 				}
900 				res = true;
901 			}
902 		}
903 		break;
904 	}
905 	return res;
906 }
907 
908 //------------------------------------------------------------------------
getXEmbedInfo()909 auto X11Window::Impl::getXEmbedInfo () -> XEmbedInfo*
910 {
911 	int actualFormat;
912 	unsigned long itemsReturned;
913 	unsigned long bytesAfterReturn;
914 	Atom actualType;
915 	XEmbedInfo* xembedInfo = NULL;
916 	if (xEmbedInfoAtom == None)
917 		xEmbedInfoAtom = XInternAtom (xDisplay, "_XEMBED_INFO", true);
918 	auto err =
919 	    XGetWindowProperty (xDisplay, plugWindow, xEmbedInfoAtom, 0, sizeof (xembedInfo), false,
920 	                        xEmbedInfoAtom, &actualType, &actualFormat, &itemsReturned,
921 	                        &bytesAfterReturn, reinterpret_cast<unsigned char**> (&xembedInfo));
922 	if (err != Success)
923 		return nullptr;
924 	return xembedInfo;
925 }
926 
927 //------------------------------------------------------------------------
handlePlugEvent(const XEvent & event)928 bool X11Window::Impl::handlePlugEvent (const XEvent& event)
929 {
930 	bool res = false;
931 
932 	switch (event.type)
933 	{
934 		// XEMBED specific
935 		case ClientMessage:
936 		{
937 			auto name = XGetAtomName (xDisplay, event.xclient.message_type);
938 			std::cout << name << std::endl;
939 			if (event.xclient.message_type == xEmbedAtom)
940 			{
941 				switch (event.xclient.data.l[1])
942 				{
943 					case XEMBED_REQUEST_FOCUS:
944 					{
945 						send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_FOCUS_IN, 0,
946 						                     plugParentWindow, xembedInfo->version);
947 						break;
948 					}
949 				}
950 			}
951 			break;
952 		}
953 		case PropertyNotify:
954 		{
955 			auto name = XGetAtomName (xDisplay, event.xproperty.atom);
956 			std::cout << name << std::endl;
957 			if (event.xany.window == plugWindow)
958 			{
959 				if (event.xproperty.atom == xEmbedInfoAtom)
960 				{
961 					if (auto embedInfo = getXEmbedInfo ())
962 					{
963 					}
964 				}
965 				else
966 				{
967 				}
968 			}
969 			break;
970 		}
971 		case CreateNotify:
972 		{
973 			if (event.xcreatewindow.parent != plugParentWindow)
974 			{
975 				res = true;
976 				break;
977 			}
978 
979 			plugWindow = event.xcreatewindow.window;
980 
981 			xembedInfo = getXEmbedInfo ();
982 			if (!xembedInfo)
983 			{
984 				std::cerr << "XGetWindowProperty for _XEMBED_INFO failed" << std::endl;
985 				exit (-1);
986 			}
987 			if (xembedInfo->flags & XEMBED_MAPPED)
988 			{
989 				std::cerr << "Window already mapped error" << std::endl;
990 				exit (-1);
991 			}
992 			RunLoop::instance ().registerWindow (
993 			    plugWindow, [this] (const XEvent& e) { return handlePlugEvent (e); });
994 
995 			// XSelectInput (xDisplay, plugWindow, PropertyChangeMask);
996 
997 			if (xEmbedAtom == None)
998 				xEmbedAtom = XInternAtom (xDisplay, "_XEMBED", true);
999 			assert (xEmbedAtom != None);
1000 
1001 			send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_EMBEDDED_NOTIFY, 0,
1002 			                     plugParentWindow, xembedInfo->version);
1003 			XMapWindow (xDisplay, plugWindow);
1004 			XResizeWindow (xDisplay, plugWindow, mCurrentSize.width, mCurrentSize.height);
1005 			// XSetInputFocus (xDisplay, plugWindow, RevertToParent, CurrentTime);
1006 			send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_ACTIVATE, 0,
1007 			                     plugParentWindow, xembedInfo->version);
1008 			send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_FOCUS_IN, 0,
1009 			                     plugParentWindow, xembedInfo->version);
1010 			XSync (xDisplay, False);
1011 			res = true;
1012 			break;
1013 		}
1014 	}
1015 
1016 	return res;
1017 }
1018 #endif
1019 
1020 //------------------------------------------------------------------------
1021 } // EditorHost
1022 } // Vst
1023 } // Steinberg
1024