1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /***************************************************************************
3  *            nativewindow_x11.cc
4  *
5  *  Fri Dec 28 18:45:57 CET 2012
6  *  Copyright 2012 Bent Bisballe Nyeng
7  *  deva@aasimon.org
8  ****************************************************************************/
9 
10 /*
11  *  This file is part of DrumGizmo.
12  *
13  *  DrumGizmo is free software; you can redistribute it and/or modify
14  *  it under the terms of the GNU Lesser General Public License as published by
15  *  the Free Software Foundation; either version 3 of the License, or
16  *  (at your option) any later version.
17  *
18  *  DrumGizmo is distributed in the hope that it will be useful,
19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *  GNU Lesser General Public License for more details.
22  *
23  *  You should have received a copy of the GNU Lesser General Public License
24  *  along with DrumGizmo; if not, write to the Free Software
25  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
26  */
27 #include "nativewindow_x11.h"
28 
29 //http://www.mesa3d.org/brianp/xshm.c
30 
31 #include <X11/Xutil.h>
32 #include <sys/ipc.h>
33 #include <sys/shm.h>
34 #include <cerrno>
35 #include <cstring>
36 #include <cassert>
37 
38 #include <chrono>
39 
40 #include <hugin.hpp>
41 
42 #include "window.h"
43 
44 namespace GUI
45 {
46 
47 #define _NET_WM_STATE_REMOVE        0    // remove/unset property
48 #define _NET_WM_STATE_ADD           1    // add/set property
49 
setWindowFront(Display * disp,::Window wind,bool enable)50 void setWindowFront(Display *disp, ::Window wind, bool enable)
51 {
52 	Atom wm_state, wm_state_above;
53 	XEvent event;
54 
55 	if((wm_state = XInternAtom(disp, "_NET_WM_STATE", False)) == None)
56 	{
57 		return;
58 	}
59 
60 	if((wm_state_above = XInternAtom(disp, "_NET_WM_STATE_ABOVE", False)) == None)
61 	{
62 		return;
63 	}
64 	//
65 	//window  = the respective client window
66 	//message_type = _NET_WM_STATE
67 	//format = 32
68 	//data.l[0] = the action, as listed below
69 	//data.l[1] = first property to alter
70 	//data.l[2] = second property to alter
71 	//data.l[3] = source indication (0-unk,1-normal app,2-pager)
72 	//other data.l[] elements = 0
73 	//
74 
75 	// sending a ClientMessage
76 	event.xclient.type = ClientMessage;
77 
78 	// value unimportant in this case
79 	event.xclient.serial = 0;
80 
81 	// coming from a SendEvent request, so True
82 	event.xclient.send_event = True;
83 
84 	// the event originates from disp
85 	event.xclient.display = disp;
86 
87 	// the window whose state will be modified
88 	event.xclient.window = wind;
89 
90 	// the component Atom being modified in the window
91 	event.xclient.message_type = wm_state;
92 
93 	// specifies that data.l will be used
94 	event.xclient.format = 32;
95 
96 	// 0 is _NET_WM_STATE_REMOVE, 1 is _NET_WM_STATE_ADD
97 	event.xclient.data.l[0] =
98 		enable ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
99 
100 	// the atom being added
101 	event.xclient.data.l[1] = wm_state_above;
102 
103 	// unused
104 	event.xclient.data.l[2] = 0;
105 	event.xclient.data.l[3] = 0;
106 	event.xclient.data.l[4] = 0;
107 
108 	// actually send the event
109 	XSendEvent(disp, DefaultRootWindow(disp), False,
110 	           SubstructureRedirectMask | SubstructureNotifyMask, &event);
111 }
112 
NativeWindowX11(void * native_window,Window & window)113 NativeWindowX11::NativeWindowX11(void* native_window, Window& window)
114 	: window(window)
115 {
116 	display = XOpenDisplay(nullptr);
117 	if(display  == nullptr)
118 	{
119 		ERR(X11, "XOpenDisplay failed");
120 		return;
121 	}
122 
123 	screen = DefaultScreen(display);
124 	visual = DefaultVisual(display, screen);
125 	depth = DefaultDepth(display, screen);
126 
127 	if(native_window)
128 	{
129 		parent_window = (::Window)native_window;
130 
131 		// Track size changes on the parent window
132 		XSelectInput(display, parent_window, StructureNotifyMask);
133 	}
134 	else
135 	{
136 		parent_window = DefaultRootWindow(display);
137 	}
138 
139 	// Create the window
140 	XSetWindowAttributes swa;
141 	swa.backing_store = Always;
142 	xwindow = XCreateWindow(display,
143 	                        parent_window,
144 	                        0, 0, //window.x(), window.y(),
145 	                        1, 1, //window.width(), window.height(),
146 	                        0, // border
147 	                        CopyFromParent, // depth
148 	                        CopyFromParent, // class
149 	                        CopyFromParent, // visual
150 	                        0,//CWBackingStore,
151 	                        &swa);
152 
153 	long mask = (StructureNotifyMask |
154 	             PointerMotionMask |
155 	             ButtonPressMask |
156 	             ButtonReleaseMask |
157 	             KeyPressMask |
158 	             KeyReleaseMask|
159 	             ExposureMask |
160 	             StructureNotifyMask |
161 	             SubstructureNotifyMask |
162 	             EnterWindowMask |
163 	             LeaveWindowMask);
164 	XSelectInput(display, xwindow, mask);
165 
166 	// Register the delete window message:
167 	wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", false);
168 
169 	Atom protocols[] = { wmDeleteMessage };
170 	XSetWMProtocols(display, xwindow, protocols,
171 	                sizeof(protocols) / sizeof(*protocols));
172 
173 	// Create a "Graphics Context"
174 	gc = XCreateGC(display, xwindow, 0, nullptr);
175 }
176 
~NativeWindowX11()177 NativeWindowX11::~NativeWindowX11()
178 {
179 	if(display == nullptr)
180 	{
181 		return;
182 	}
183 
184 	deallocateShmImage();
185 
186 	XFreeGC(display, gc);
187 
188 	XDestroyWindow(display, xwindow);
189 	XCloseDisplay(display);
190 }
191 
setFixedSize(std::size_t width,std::size_t height)192 void NativeWindowX11::setFixedSize(std::size_t width, std::size_t height)
193 {
194 	if(display == nullptr)
195 	{
196 		return;
197 	}
198 
199 	resize(width, height);
200 
201 	XSizeHints size_hints;
202 	memset(&size_hints, 0, sizeof(size_hints));
203 
204 	size_hints.flags = PMinSize|PMaxSize;
205 	size_hints.min_width = size_hints.max_width = (int)width;
206 	size_hints.min_height = size_hints.max_height = (int)height;
207 
208 	XSetNormalHints(display, xwindow, &size_hints);
209 }
210 
setAlwaysOnTop(bool always_on_top)211 void NativeWindowX11::setAlwaysOnTop(bool always_on_top)
212 {
213 	setWindowFront(display, xwindow, always_on_top);
214 }
215 
resize(std::size_t width,std::size_t height)216 void NativeWindowX11::resize(std::size_t width, std::size_t height)
217 {
218 	if(display == nullptr)
219 	{
220 		return;
221 	}
222 
223 	XResizeWindow(display, xwindow, width, height);
224 }
225 
getSize() const226 std::pair<std::size_t, std::size_t> NativeWindowX11::getSize() const
227 {
228 //	XWindowAttributes attributes;
229 //	XGetWindowAttributes(display, xwindow, &attributes);
230 //	return std::make_pair(attributes.width, attributes.height);
231 
232 	::Window root_window;
233 	int x, y;
234 	unsigned int width, height, border, depth;
235 
236 	XGetGeometry(display, xwindow, &root_window,
237 	             &x, &y,
238 	             &width, &height, &border, &depth);
239 
240 	return {width, height};
241 }
242 
move(int x,int y)243 void NativeWindowX11::move(int x, int y)
244 {
245 	if(display == nullptr)
246 	{
247 		return;
248 	}
249 
250 	XMoveWindow(display, xwindow, x, y);
251 }
252 
getPosition() const253 std::pair<int, int> NativeWindowX11::getPosition() const
254 {
255 	::Window root_window;
256 	::Window child_window;
257 	int x, y;
258 	unsigned int width, height, border, depth;
259 
260 	XGetGeometry(display, xwindow, &root_window,
261 	             &x, &y,
262 	             &width, &height, &border, &depth);
263 
264 	XTranslateCoordinates(display, xwindow, root_window,
265 	                      0, 0, &x, &y, &child_window);
266 
267 	return std::make_pair(x, y);
268 }
269 
show()270 void NativeWindowX11::show()
271 {
272 	if(display == nullptr)
273 	{
274 		return;
275 	}
276 
277 	XMapWindow(display, xwindow);
278 }
279 
hide()280 void NativeWindowX11::hide()
281 {
282 	if(display == nullptr)
283 	{
284 		return;
285 	}
286 
287 	XUnmapWindow(display, xwindow);
288 }
289 
visible() const290 bool NativeWindowX11::visible() const
291 {
292 	if(display == nullptr)
293 	{
294 		return false;
295 	}
296 
297 	XWindowAttributes xwa;
298 	XGetWindowAttributes(display, xwindow, &xwa);
299 	return (xwa.map_state == IsViewable);
300 }
301 
redraw(const Rect & dirty_rect)302 void NativeWindowX11::redraw(const Rect& dirty_rect)
303 {
304 	if(display == nullptr)
305 	{
306 		return;
307 	}
308 
309 	auto x1 = dirty_rect.x1;
310 	auto y1 = dirty_rect.y1;
311 	auto x2 = dirty_rect.x2;
312 	auto y2 = dirty_rect.y2;
313 
314 	// Assert that we don't try to paint a backwards rect.
315 	assert(x1 <= x2);
316 	assert(y1 <= y2);
317 
318 	updateImageFromBuffer(x1, y1, x2, y2);
319 
320 	XShmPutImage(display, xwindow, gc, image, x1, y1, x1, y1,
321 	             std::min((std::size_t)image->width, (x2 - x1)),
322 	             std::min((std::size_t)image->height, (y2 - y1)), false);
323 	XFlush(display);
324 }
325 
setCaption(const std::string & caption)326 void NativeWindowX11::setCaption(const std::string &caption)
327 {
328 	if(display == nullptr)
329 	{
330 		return;
331 	}
332 
333 	XStoreName(display, xwindow, caption.c_str());
334 }
335 
grabMouse(bool grab)336 void NativeWindowX11::grabMouse(bool grab)
337 {
338 	(void)grab;
339 	// Don't need to do anything on this platform...
340 }
341 
getEvents()342 EventQueue NativeWindowX11::getEvents()
343 {
344 	while(XPending(display))
345 	{
346 		XEvent xEvent;
347 		XNextEvent(display, &xEvent);
348 		translateXMessage(xEvent);
349 	}
350 
351 	EventQueue events;
352 	std::swap(events, event_queue);
353 	return events;
354 }
355 
getNativeWindowHandle() const356 void* NativeWindowX11::getNativeWindowHandle() const
357 {
358 	return (void*)xwindow;
359 }
360 
translateToScreen(const Point & point)361 Point NativeWindowX11::translateToScreen(const Point& point)
362 {
363 	::Window child_window;
364 	Point p;
365 	XTranslateCoordinates(display, xwindow, DefaultRootWindow(display),
366 	                      point.x, point.y, &p.x, &p.y, &child_window);
367 	return p;
368 }
369 
translateXMessage(XEvent & xevent)370 void NativeWindowX11::translateXMessage(XEvent& xevent)
371 {
372 	switch(xevent.type)
373 	{
374 	case MotionNotify:
375 		//DEBUG(x11, "MotionNotify");
376 		{
377 			auto mouseMoveEvent = std::make_shared<MouseMoveEvent>();
378 			mouseMoveEvent->x = xevent.xmotion.x;
379 			mouseMoveEvent->y = xevent.xmotion.y;
380 			event_queue.push_back(mouseMoveEvent);
381 		}
382 		break;
383 
384 	case Expose:
385 		//DEBUG(x11, "Expose");
386 		if(xevent.xexpose.count == 0)
387 		{
388 			auto repaintEvent = std::make_shared<RepaintEvent>();
389 			repaintEvent->x = xevent.xexpose.x;
390 			repaintEvent->y = xevent.xexpose.y;
391 			repaintEvent->width = xevent.xexpose.width;
392 			repaintEvent->height = xevent.xexpose.height;
393 			event_queue.push_back(repaintEvent);
394 
395 			if(image)
396 			{
397 				// Redraw the entire window.
398 				Rect rect{0, 0, window.wpixbuf.width, window.wpixbuf.height};
399 				redraw(rect);
400 			}
401 		}
402 		break;
403 
404 	case ConfigureNotify:
405 		//DEBUG(x11, "ConfigureNotify");
406 
407 		// The parent window size changed, reflect the new size in our own window.
408 		if(xevent.xconfigure.window == parent_window)
409 		{
410 			resize(xevent.xconfigure.width, xevent.xconfigure.height);
411 			return;
412 		}
413 
414 		{
415 			if((window.width() != (std::size_t)xevent.xconfigure.width) ||
416 			   (window.height() != (std::size_t)xevent.xconfigure.height))
417 			{
418 				auto resizeEvent = std::make_shared<ResizeEvent>();
419 				resizeEvent->width = xevent.xconfigure.width;
420 				resizeEvent->height = xevent.xconfigure.height;
421 				event_queue.push_back(resizeEvent);
422 			}
423 
424 			if((window.x() != xevent.xconfigure.x) ||
425 			   (window.y() != xevent.xconfigure.y))
426 			{
427 				auto moveEvent = std::make_shared<MoveEvent>();
428 				moveEvent->x = xevent.xconfigure.x;
429 				moveEvent->y = xevent.xconfigure.y;
430 				event_queue.push_back(moveEvent);
431 			}
432 		}
433 		break;
434 
435 	case ButtonPress:
436 	case ButtonRelease:
437 		//DEBUG(x11, "ButtonPress");
438 		{
439 			if((xevent.xbutton.button == 4) || (xevent.xbutton.button == 5))
440 			{
441 				if(xevent.type == ButtonPress)
442 				{
443 					int scroll = 1;
444 					auto scrollEvent = std::make_shared<ScrollEvent>();
445 					scrollEvent->x = xevent.xbutton.x;
446 					scrollEvent->y = xevent.xbutton.y;
447 					scrollEvent->delta = scroll * ((xevent.xbutton.button == 4) ? -1 : 1);
448 					event_queue.push_back(scrollEvent);
449 				}
450 			}
451 			else if ((xevent.xbutton.button == 6) || (xevent.xbutton.button == 7))
452 			{
453 				// Horizontal scrolling case
454 				// FIXME Introduce horizontal scrolling event to handle this.
455 			}
456 			else
457 			{
458 				auto buttonEvent = std::make_shared<ButtonEvent>();
459 				buttonEvent->x = xevent.xbutton.x;
460 				buttonEvent->y = xevent.xbutton.y;
461 				switch(xevent.xbutton.button) {
462 				case 1:
463 					buttonEvent->button = MouseButton::left;
464 					break;
465 				case 2:
466 					buttonEvent->button = MouseButton::middle;
467 					break;
468 				case 3:
469 					buttonEvent->button = MouseButton::right;
470 					break;
471 				default:
472 					WARN(X11, "Unknown button %d, setting to MouseButton::left\n",
473 					     xevent.xbutton.button);
474 					buttonEvent->button = MouseButton::left;
475 					break;
476 				}
477 
478 				buttonEvent->direction =
479 					(xevent.type == ButtonPress) ?
480 					Direction::down : Direction::up;
481 
482 				// This is a fix for hosts (e.g. those using JUCE) that set the
483 				// event time to '0'.
484 				if(xevent.xbutton.time == 0)
485 				{
486 					auto now = std::chrono::system_clock::now().time_since_epoch();
487 					xevent.xbutton.time =
488 						std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
489 				}
490 
491 				buttonEvent->doubleClick =
492 					(xevent.type == ButtonPress) &&
493 					((xevent.xbutton.time - last_click) < 200);
494 
495 				if(xevent.type == ButtonPress)
496 				{
497 					last_click = xevent.xbutton.time;
498 				}
499 				event_queue.push_back(buttonEvent);
500 			}
501 		}
502 		break;
503 
504 	case KeyPress:
505 	case KeyRelease:
506 		//DEBUG(x11, "KeyPress");
507 		{
508 			auto keyEvent = std::make_shared<KeyEvent>();
509 
510 			switch(xevent.xkey.keycode) {
511 			case 113: keyEvent->keycode = Key::left; break;
512 			case 114: keyEvent->keycode = Key::right; break;
513 			case 111: keyEvent->keycode = Key::up; break;
514 			case 116: keyEvent->keycode = Key::down; break;
515 			case 119: keyEvent->keycode = Key::deleteKey; break;
516 			case 22:  keyEvent->keycode = Key::backspace; break;
517 			case 110: keyEvent->keycode = Key::home; break;
518 			case 115: keyEvent->keycode = Key::end; break;
519 			case 117: keyEvent->keycode = Key::pageDown; break;
520 			case 112: keyEvent->keycode = Key::pageUp; break;
521 			case 36:  keyEvent->keycode = Key::enter; break;
522 			default:  keyEvent->keycode = Key::unknown; break;
523 			}
524 
525 			char stringBuffer[1024];
526 			int size = XLookupString(&xevent.xkey, stringBuffer,
527 		                         sizeof(stringBuffer), nullptr, nullptr);
528 			if(size && keyEvent->keycode == Key::unknown)
529 			{
530 				keyEvent->keycode = Key::character;
531 			}
532 
533 			keyEvent->text.append(stringBuffer, size);
534 
535 			keyEvent->direction =
536 				(xevent.type == KeyPress) ? Direction::down : Direction::up;
537 
538 			event_queue.push_back(keyEvent);
539 		}
540 		break;
541 
542 	case ClientMessage:
543 		//DEBUG(x11, "ClientMessage");
544 		if(((unsigned int)xevent.xclient.data.l[0] == wmDeleteMessage))
545 		{
546 			auto closeEvent = std::make_shared<CloseEvent>();
547 			event_queue.push_back(closeEvent);
548 		}
549 		break;
550 
551 	case EnterNotify:
552 		//DEBUG(x11, "EnterNotify");
553 		{
554 			auto enterEvent = std::make_shared<MouseEnterEvent>();
555 			enterEvent->x = xevent.xcrossing.x;
556 			enterEvent->y = xevent.xcrossing.y;
557 			event_queue.push_back(enterEvent);
558 		}
559 		break;
560 
561 	case LeaveNotify:
562 		//DEBUG(x11, "LeaveNotify");
563 		{
564 			auto leaveEvent = std::make_shared<MouseLeaveEvent>();
565 			leaveEvent->x = xevent.xcrossing.x;
566 			leaveEvent->y = xevent.xcrossing.y;
567 			event_queue.push_back(leaveEvent);
568 		}
569 		break;
570 
571 	case MapNotify:
572 	case MappingNotify:
573 		//DEBUG(x11, "EnterNotify");
574 		// There's nothing to do here atm.
575 		break;
576 
577 	default:
578 		WARN(X11, "Unhandled xevent.type: %d\n", xevent.type);
579 		break;
580 	}
581 }
582 
allocateShmImage(std::size_t width,std::size_t height)583 void NativeWindowX11::allocateShmImage(std::size_t width, std::size_t height)
584 {
585 	DEBUG(x11, "(Re)alloc XShmImage (%d, %d)", (int)width, (int)height);
586 
587 	if(image)
588 	{
589 		deallocateShmImage();
590 	}
591 
592 	if(!XShmQueryExtension(display))
593 	{
594 		ERR(x11, "XShmExtension not available");
595 		return;
596 	}
597 
598 	image = XShmCreateImage(display, visual, depth,
599 	                        ZPixmap, nullptr, &shm_info,
600 	                        width, height);
601 	if(image == nullptr)
602 	{
603 		ERR(x11, "XShmCreateImage failed!\n");
604 		return;
605 	}
606 
607 	std::size_t byte_size = image->bytes_per_line * image->height;
608 
609 	// Allocate shm buffer
610 	int shm_id = shmget(IPC_PRIVATE, byte_size, IPC_CREAT|0777);
611 	if(shm_id == -1)
612 	{
613 		ERR(x11, "shmget failed: %s", strerror(errno));
614 		return;
615 	}
616 
617 	shm_info.shmid = shm_id;
618 
619 	// Attach share memory bufer
620 	void* shm_addr = shmat(shm_id, nullptr, 0);
621 	if(reinterpret_cast<long int>(shm_addr) == -1)
622 	{
623 		ERR(x11, "shmat failed: %s", strerror(errno));
624 		return;
625 	}
626 
627 	shm_info.shmaddr = reinterpret_cast<char*>(shm_addr);
628 	image->data = shm_info.shmaddr;
629 	shm_info.readOnly = false;
630 
631 	// This may trigger the X protocol error we're ready to catch:
632 	XShmAttach(display, &shm_info);
633 	XSync(display, false);
634 
635 	// Make the shm id unavailable to others
636 	shmctl(shm_id, IPC_RMID, 0);
637 }
638 
deallocateShmImage()639 void NativeWindowX11::deallocateShmImage()
640 {
641 	if(image == nullptr)
642 	{
643 		return;
644 	}
645 
646 	XFlush(display);
647 	XShmDetach(display, &shm_info);
648 	XDestroyImage(image);
649 	image = nullptr;
650 	shmdt(shm_info.shmaddr);
651 }
652 
updateImageFromBuffer(std::size_t x1,std::size_t y1,std::size_t x2,std::size_t y2)653 void NativeWindowX11::updateImageFromBuffer(std::size_t x1, std::size_t y1,
654                                             std::size_t x2, std::size_t y2)
655 {
656 	//DEBUG(x11, "depth: %d", depth);
657 
658 	auto width = window.wpixbuf.width;
659 	auto height = window.wpixbuf.height;
660 
661 	// If image hasn't been allocated yet or if the image backbuffer is
662 	// too small, (re)allocate with a suitable size.
663 	if((image == nullptr) ||
664 	   ((int)width > image->width) ||
665 	   ((int)height > image->height))
666 	{
667 		constexpr std::size_t step_size = 128; // size increments
668 		std::size_t new_width = ((width / step_size) + 1) * step_size;
669 		std::size_t new_height = ((height / step_size) + 1) * step_size;
670 		allocateShmImage(new_width, new_height);
671 		x1 = 0;
672 		y1 = 0;
673 		x2 = width;
674 		y2 = height;
675 	}
676 
677 	auto stride = image->width;
678 
679 	std::uint8_t* pixel_buffer = (std::uint8_t*)window.wpixbuf.buf;
680 	if(depth >= 24) // RGB 888 format
681 	{
682 		std::uint32_t* shm_addr = (std::uint32_t*)shm_info.shmaddr;
683 		for(std::size_t y = y1; y < y2; ++y)
684 		{
685 			for(std::size_t x = x1; x < x2; ++x)
686 			{
687 				const std::size_t pin = y * width + x;
688 				const std::size_t pout = y * stride + x;
689 				const std::uint8_t red = pixel_buffer[pin * 3];
690 				const std::uint8_t green = pixel_buffer[pin * 3 + 1];
691 				const std::uint8_t blue = pixel_buffer[pin * 3 + 2];
692 				shm_addr[pout] = (red << 16) | (green << 8) | blue;
693 			}
694 		}
695 	}
696 	else if(depth >= 15) // RGB 565 format
697 	{
698 		std::uint16_t* shm_addr = (std::uint16_t*)shm_info.shmaddr;
699 
700 		for(std::size_t y = y1; y < y2; ++y)
701 		{
702 			for(std::size_t x = x1; x < x2; ++x)
703 			{
704 				const std::size_t pin = y * width + x;
705 				const std::size_t pout = y * stride + x;
706 				const std::uint8_t red = pixel_buffer[pin * 3];
707 				const std::uint8_t green = pixel_buffer[pin * 3 + 1];
708 				const std::uint8_t blue = pixel_buffer[pin * 3 + 2];
709 				shm_addr[pout] = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
710 			}
711 		}
712 	}
713 }
714 
715 } // GUI::
716