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