Lugdunum  0.1.0
WindowImplX11.cpp
Go to the documentation of this file.
2 #include <cstring>
3 #include <string>
4 #include <queue>
7 #include <X11/Xlib.h>
8 #include <lug/Window/Event.hpp>
9 
10 namespace lug {
11 namespace Window {
12 namespace priv {
13 
14 WindowImpl::WindowImpl(Window* win) : _parent{win} {}
15 
16 bool WindowImpl::init(const Window::InitInfo& initInfo) {
17  _display = XOpenDisplay(nullptr);
18 
19  if (_display == nullptr) {
20  return false;
21  }
22 
23  int screen = DefaultScreen(_display);
24  ::Window parent = RootWindow(_display, screen);
25 
26  // The default color
27  uint32_t blackColor = BlackPixel(_display, DefaultScreen(_display));
28 
29  _window = XCreateSimpleWindow(_display, parent, 0, 0, initInfo.width, initInfo.height, 90, 2, blackColor);
30 
31  if (!_window) {
32  XCloseDisplay(_display);
33  _display = nullptr;
34  return false;
35  }
36 
37  XSelectInput(_display, _window, ExposureMask |
38  StructureNotifyMask |
39  KeyPressMask |
40  KeyReleaseMask |
41  ButtonPressMask |
42  ButtonReleaseMask |
43  EnterWindowMask |
44  LeaveWindowMask |
45  PointerMotionMask);
46  XStoreName(_display, _window, initInfo.title.c_str());
47  XMapWindow(_display, _window);
48 
49  _wmProtocols = XInternAtom(_display, "WM_PROTOCOLS", false);
50  _wmDeleteWindow = XInternAtom(_display, "WM_DELETE_WINDOW", false);
51  _wmHints = XInternAtom(_display, "_MOTIF_WM_HINTS", false);
52 
53  XSetWMProtocols(_display, _window, &_wmDeleteWindow, 1);
54  setWindowDecorations(initInfo.style);
55 
56  return true;
57 }
58 
59 void WindowImpl::close() {
60  if (_display != nullptr) {
61  setMouseCursorVisible(true); // In case it was hidden before
62  XDestroyWindow(_display, _window);
63  XCloseDisplay(_display);
64  _display = nullptr;
65  _window = 0;
66  }
67 }
68 
69 static Mouse::Button buttonCodeToLugButton(unsigned int buttonCode) {
70  switch (buttonCode) {
71  case Button1: return Mouse::Button::Left;
72  case Button2: return Mouse::Button::Middle;
73  case Button3: return Mouse::Button::Right;
74  default: return Mouse::Button::Unknown;
75  }
76 }
77 
78 static Keyboard::Key keysymToLugKey(KeySym key) {
79  switch (key) {
80  case XK_F1:
81  return Keyboard::Key::F1; // The F1 key
82  case XK_F2:
83  return Keyboard::Key::F2; // The F2 key
84  case XK_F3:
85  return Keyboard::Key::F3; // The F3 key
86  case XK_F4:
87  return Keyboard::Key::F4; // The F4 key
88  case XK_F5:
89  return Keyboard::Key::F5; // The F5 key
90  case XK_F6:
91  return Keyboard::Key::F6; // The F6 key
92  case XK_F7:
93  return Keyboard::Key::F7; // The F7 key
94  case XK_F8:
95  return Keyboard::Key::F8; // The F8 key
96  case XK_F9:
97  return Keyboard::Key::F9; // The F9 key
98  case XK_F10:
99  return Keyboard::Key::F10; // The F10 key
100  case XK_F11:
101  return Keyboard::Key::F11; // The F11 key
102  case XK_F12:
103  return Keyboard::Key::F12; // The F12 key
104  case XK_F13:
105  return Keyboard::Key::F13; // The F13 key
106  case XK_F14:
107  return Keyboard::Key::F14; // The F14 key
108  case XK_F15:
109  return Keyboard::Key::F15; // The F15 key
110 
111  case XK_a:
112  return Keyboard::Key::A; // The A key
113  case XK_b:
114  return Keyboard::Key::B; // The B key
115  case XK_c:
116  return Keyboard::Key::C; // The C key
117  case XK_d:
118  return Keyboard::Key::D; // The D key
119  case XK_e:
120  return Keyboard::Key::E; // The E key
121  case XK_f:
122  return Keyboard::Key::F; // The F key
123  case XK_g:
124  return Keyboard::Key::G; // The G key
125  case XK_h:
126  return Keyboard::Key::H; // The H key
127  case XK_i:
128  return Keyboard::Key::I; // The I key
129  case XK_j:
130  return Keyboard::Key::J; // The J key
131  case XK_k:
132  return Keyboard::Key::K; // The K key
133  case XK_l:
134  return Keyboard::Key::L; // The L key
135  case XK_m:
136  return Keyboard::Key::M; // The M key
137  case XK_n:
138  return Keyboard::Key::N; // The N key
139  case XK_o:
140  return Keyboard::Key::O; // The O key
141  case XK_p:
142  return Keyboard::Key::P; // The P key
143  case XK_q:
144  return Keyboard::Key::Q; // The Q key
145  case XK_r:
146  return Keyboard::Key::R; // The R key
147  case XK_s:
148  return Keyboard::Key::S; // The S key
149  case XK_t:
150  return Keyboard::Key::T; // The T key
151  case XK_u:
152  return Keyboard::Key::U; // The U key
153  case XK_v:
154  return Keyboard::Key::V; // The V key
155  case XK_w:
156  return Keyboard::Key::W; // The W key
157  case XK_x:
158  return Keyboard::Key::X; // The X key
159  case XK_y:
160  return Keyboard::Key::Y; // The Y key
161  case XK_z:
162  return Keyboard::Key::Z; // The Z key
163 
164  case XK_0:
165  return Keyboard::Key::Num0; // The 0 key
166  case XK_1:
167  return Keyboard::Key::Num1; // The 1 key
168  case XK_2:
169  return Keyboard::Key::Num2; // The 2 key
170  case XK_3:
171  return Keyboard::Key::Num3; // The 3 key
172  case XK_4:
173  return Keyboard::Key::Num4; // The 4 key
174  case XK_5:
175  return Keyboard::Key::Num5; // The 5 key
176  case XK_6:
177  return Keyboard::Key::Num6; // The 6 key
178  case XK_7:
179  return Keyboard::Key::Num7; // The 7 key
180  case XK_8:
181  return Keyboard::Key::Num8; // The 8 key
182  case XK_9:
183  return Keyboard::Key::Num9; // The 9 key
184 
185  case XK_Escape:
186  return Keyboard::Key::Escape; // The Escape key
187 
188  case XK_Control_L:
189  return Keyboard::Key::LControl; // The left Control key
190  case XK_Shift_L:
191  return Keyboard::Key::LShift; // The left Shift key
192  case XK_Alt_L:
193  return Keyboard::Key::LAlt; // The left Alt key
194  case XK_Super_L:
195  return Keyboard::Key::LSystem; // The left OS specific key: window (Windows and Linux), apple (MacOS X), ...
196 
197  case XK_Control_R:
198  return Keyboard::Key::RControl; // The right Control key
199  case XK_Shift_R:
200  return Keyboard::Key::RShift; // The right Shift key
201  case XK_Alt_R:
202  return Keyboard::Key::RAlt; // The right Alt key
203  case XK_Super_R:
204  return Keyboard::Key::RSystem; // The right OS specific key: window (Windows and Linux), apple (MacOS X), ...
205 
206  case XK_Menu:
207  return Keyboard::Key::Menu; // The Menu key
208  case XK_bracketleft:
209  return Keyboard::Key::LBracket; // The [ key
210  case XK_bracketright:
211  return Keyboard::Key::RBracket; // The ] key
212  case XK_semicolon:
213  return Keyboard::Key::SemiColon; // The , key
214  case XK_comma:
215  return Keyboard::Key::Comma; // The , key
216  case XK_period:
217  return Keyboard::Key::Period; // The . key
218  case XK_apostrophe:
219  return Keyboard::Key::Quote; // The ' key
220  case XK_slash:
221  return Keyboard::Key::Slash; // The / key
222  case XK_backslash:
223  return Keyboard::Key::BackSlash; // The \ key
224  case XK_grave:
225  return Keyboard::Key::Tilde; // The ~ key
226  case XK_equal:
227  return Keyboard::Key::Equal; // The = key
228  case XK_minus:
229  return Keyboard::Key::Dash; // The - key
230  case XK_space:
231  return Keyboard::Key::Space; // The Space key
232  case XK_Return:
233  return Keyboard::Key::Return; // The Return key
234 
235  case XK_twosuperior:
236  return Keyboard::Key::Twosuperior; // The ² key
237  case XK_ampersand:
238  return Keyboard::Key::Ampersand; // The & key
239  case XK_eacute:
240  return Keyboard::Key::Eacute; // The é key
241  case XK_quotedbl:
242  return Keyboard::Key::QuoteDouble; // The " key
243  case XK_parenleft:
244  return Keyboard::Key::LParen; // The ( key
245  case XK_egrave:
246  return Keyboard::Key::Egrave; // The è key
247  case XK_underscore:
248  return Keyboard::Key::Underscore; // The _ key
249  case XK_ccedilla:
250  return Keyboard::Key::Ccedilla; // The ç key
251  case XK_agrave:
252  return Keyboard::Key::Agrave; // The à key
253  case XK_parenright:
254  return Keyboard::Key::RParen; // The ) key
255  case XK_dead_circumflex:
256  return Keyboard::Key::DeadCircumflex; // The ^ key (dead variant)
257  case XK_ugrave:
258  return Keyboard::Key::Ugrave; // The ù key
259  case XK_asterisk:
260  return Keyboard::Key::Asterisk; // The * key
261  case XK_dollar:
262  return Keyboard::Key::Dollar; // The $ key
263  case XK_colon:
264  return Keyboard::Key::Colon; // The : key
265  case XK_exclam:
266  return Keyboard::Key::Exclam; // The ! key
267  case XK_less:
268  return Keyboard::Key::Less; // The < key
269  case XK_greater:
270  return Keyboard::Key::Greater; // The > key
271 
272  // Note: the keys for the keypad below are in the numlock off state, i.e. 8 => KP_Up
273  // TODO: It would be nice to actually find a good solution to this problem.
274  case XK_KP_Enter:
275  return Keyboard::Key::Return; // The Return key (from the keypad)
276  case XK_KP_Add:
277  return Keyboard::Key::Add; // The + key
278  case XK_KP_Subtract:
279  return Keyboard::Key::Subtract; // The - key
280  case XK_KP_Multiply:
281  return Keyboard::Key::Multiply; // The * key
282  case XK_KP_Divide:
283  return Keyboard::Key::Divide; // The / key
284  case XK_KP_Delete:
285  return Keyboard::Key::Period; // The . key (from the keypad)
286 
287  case XK_KP_Insert:
288  return Keyboard::Key::Numpad0; // The numpad 0 key
289  case XK_KP_End:
290  return Keyboard::Key::Numpad1; // The numpad 1 key
291  case XK_KP_Down:
292  return Keyboard::Key::Numpad2; // The numpad 2 key
293  case XK_KP_Page_Down:
294  return Keyboard::Key::Numpad3; // The numpad 3 key
295  case XK_KP_Left:
296  return Keyboard::Key::Numpad4; // The numpad 4 key
297  case XK_KP_Begin:
298  return Keyboard::Key::Numpad5; // The numpad 5 key
299  case XK_KP_Right:
300  return Keyboard::Key::Numpad6; // The numpad 6 key
301  case XK_KP_Home:
302  return Keyboard::Key::Numpad7; // The numpad 7 key
303  case XK_KP_Up:
304  return Keyboard::Key::Numpad8; // The numpad 8 key
305  case XK_KP_Page_Up:
306  return Keyboard::Key::Numpad9; // The numpad 9 key
307 
308  case XK_BackSpace:
309  return Keyboard::Key::BackSpace; // The Backspace key
310  case XK_Tab:
311  return Keyboard::Key::Tab; // The Tabulation key
312  case XK_Prior:
313  return Keyboard::Key::PageUp; // The Page up key
314  case XK_Next:
315  return Keyboard::Key::PageDown; // The Page down key
316  case XK_End:
317  return Keyboard::Key::End; // The End key
318  case XK_Home:
319  return Keyboard::Key::Home; // The Home key
320  case XK_Insert:
321  return Keyboard::Key::Insert; // The Insert key
322  case XK_Delete:
323  return Keyboard::Key::Delete; // The Delete key
324 
325  case XK_Left:
326  return Keyboard::Key::Left; // Left arrow
327  case XK_Right:
328  return Keyboard::Key::Right; // Right arrow
329  case XK_Up:
330  return Keyboard::Key::Up; // Up arrow
331  case XK_Down:
332  return Keyboard::Key::Down; // Down arrow
333 
334  case XK_Pause:
335  return Keyboard::Key::Pause; // The Pause key
336  case XK_Caps_Lock:
337  return Keyboard::Key::CapsLock; // The Caps Lock key
338  default:
339  return Keyboard::Key::Unknown;
340  }
341 }
342 
343 static Bool selectEvents(Display*, XEvent* event, XPointer) {
344  return (event->type == ClientMessage ||
345  event->type == DestroyNotify ||
346  event->type == KeyPress ||
347  event->type == KeyRelease ||
348  event->type == ButtonPress ||
349  event->type == ButtonRelease ||
350  event->type == MotionNotify ||
351  event->type == ConfigureNotify ||
352  event->type == LeaveNotify ||
353  event->type == EnterNotify);
354 }
355 
363 bool WindowImpl::shouldIgnoreRepeated(XEvent& xEvent) {
364  // (code shamelessly taken from the SFML, which took it from SDL) ;)
365 
366  // We are only interested in filtering KeyRelease events, i.e. from now on we only
367  // have KeyRelease events
368  if (xEvent.type != KeyRelease) {
369  return false;
370  }
371 
372  // Check if there's a matching KeyPress event in the queue, else we can return
373  if (XPending(_display) == 0) {
374  return false;
375  }
376 
377  // Grab it but don't remove it from the queue, it still needs to be processed
378  XEvent nextEvent;
379  XPeekEvent(_display, &nextEvent);
380 
381  // Again, we're only interested in a corresponding KeyPress
382  if (nextEvent.type != KeyPress) {
383  return false;
384  }
385 
386  // Check if it is a duplicated event (same timestamp as the KeyRelease event)
387  if ((nextEvent.xkey.keycode == xEvent.xkey.keycode) &&
388  (nextEvent.xkey.time - xEvent.xkey.time < 2)) {
389  // If we don't want repeated events, remove the next KeyPress from the queue
390  if (!_keyRepeat) {
391  XNextEvent(_display, &nextEvent);
392  }
393 
394  // This KeyRelease is a repeated event and we don't want it
395  return true;
396  }
397 
398  return false;
399 }
400 
401 bool WindowImpl::pollEvent(Event& event) {
402  XEvent xEvent;
403 
404  if (XCheckIfEvent(_display, &xEvent, selectEvents, nullptr) == False) {
405  return false;
406  }
407 
408  if (shouldIgnoreRepeated(xEvent)) {
409  return false;
410  }
411 
412  switch (xEvent.type) {
413  case ClientMessage:
414  if (xEvent.xclient.message_type == _wmProtocols && static_cast<Atom>(xEvent.xclient.data.l[0]) == _wmDeleteWindow) {
415  event.type = Event::Type::Close;
416  } else {
417  return false;
418  }
419 
420  break;
421 
422  case DestroyNotify:
423  event.type = Event::Type::Close;
424  break;
425 
426  case KeyPress:
427  event.type = Event::Type::KeyPressed;
428  event.key.code = keysymToLugKey(XLookupKeysym((&xEvent.xkey), 0)); // TODO: indexes?
429  event.key.ctrl = (xEvent.xkey.state & ControlMask) != 0;
430  event.key.alt = (xEvent.xkey.state & Mod1Mask) != 0;
431  event.key.shift = (xEvent.xkey.state & ShiftMask) != 0;
432  event.key.system = (xEvent.xkey.state & Mod4Mask) != 0;
433  break;
434 
435  case KeyRelease:
436  event.type = Event::Type::KeyReleased;
437  event.key.code = keysymToLugKey(XLookupKeysym((&xEvent.xkey), 0)); // TODO: indexes?
438  event.key.ctrl = (xEvent.xkey.state & ControlMask) != 0;
439  event.key.alt = (xEvent.xkey.state & Mod1Mask) != 0;
440  event.key.shift = (xEvent.xkey.state & ShiftMask) != 0;
441  event.key.system = (xEvent.xkey.state & Mod4Mask) != 0;
442  break;
443 
444  case ConfigureNotify:
445  if (xEvent.xconfigure.width != _parent->_mode.width || xEvent.xconfigure.height != _parent->_mode.height) {
446  _parent->_mode.width = xEvent.xconfigure.width;
447  _parent->_mode.height = xEvent.xconfigure.height;
448 
449  event.type = Event::Type::Resize;
450  } else {
451  return false;
452  }
453  break;
454 
455  case ButtonPress:
456  switch (xEvent.xbutton.button) {
457  case Button4:
458  event.type = Event::Type::MouseWheel;
459  event.mouse.scrollOffset.xOffset = 0;
460  event.mouse.scrollOffset.yOffset = 1;
461  break;
462  case Button5:
463  event.type = Event::Type::MouseWheel;
464  event.mouse.scrollOffset.xOffset = 0;
465  event.mouse.scrollOffset.yOffset = -1;
466  break;
467  case 6:
468  event.type = Event::Type::MouseWheel;
469  event.mouse.scrollOffset.xOffset = 1;
470  event.mouse.scrollOffset.yOffset = 0;
471  break;
472  case 7:
473  event.type = Event::Type::MouseWheel;
474  event.mouse.scrollOffset.xOffset = -1;
475  event.mouse.scrollOffset.yOffset = 0;
476  break;
477  default:
478  event.type = Event::Type::ButtonPressed;
479  event.mouse.code = buttonCodeToLugButton(xEvent.xbutton.button);
480  }
481  event.mouse.coord.x = xEvent.xbutton.x;
482  event.mouse.coord.y = xEvent.xbutton.y;
483  event.mouse.ctrl = (xEvent.xbutton.state & ControlMask) != 0;
484  event.mouse.shift = (xEvent.xbutton.state & ShiftMask) != 0;
485  break;
486 
487  case ButtonRelease:
488  if (xEvent.xbutton.button != Button4 && xEvent.xbutton.button != Button5 &&
489  xEvent.xbutton.button != 6 && xEvent.xbutton.button != 7) {
490  event.type = Event::Type::ButtonReleased;
491  event.mouse.code = buttonCodeToLugButton(xEvent.xbutton.button);
492  event.mouse.coord.x = xEvent.xbutton.x;
493  event.mouse.coord.y = xEvent.xbutton.y;
494  } else {
495  return false;
496  }
497  break;
498 
499  case MotionNotify:
500  event.type = Event::Type::MouseMoved;
501  event.mouse.coord.x = xEvent.xbutton.x;
502  event.mouse.coord.y = xEvent.xbutton.y;
503  break;
504 
505  case LeaveNotify:
506  event.type = Event::Type::MouseLeave;
507  event.mouse.coord.x = xEvent.xbutton.x;
508  event.mouse.coord.y = xEvent.xbutton.y;
509  break;
510 
511  case EnterNotify:
512  event.type = Event::Type::MouseEnter;
513  event.mouse.coord.x = xEvent.xbutton.x;
514  event.mouse.coord.y = xEvent.xbutton.y;
515  break;
516 
517  default:
518  return false;
519  }
520 
521  return true;
522 }
523 
524 Display* WindowImpl::getDisplay() const {
525  return _display;
526 }
527 
529  return _window;
530 }
531 
532 void WindowImpl::setKeyRepeat(bool state) {
533  _keyRepeat = state;
534 }
535 
536 static Cursor createHiddenCursor(::Display* display, ::Window window) {
537  // Create the cursor's pixmap (1x1 pixels)
538  Pixmap cursorPixmap = XCreatePixmap(display, window, 1, 1, 1);
539  GC graphicsContext = XCreateGC(display, cursorPixmap, 0, NULL);
540  XDrawPoint(display, cursorPixmap, graphicsContext, 0, 0);
541  XFreeGC(display, graphicsContext);
542 
543  // Create the cursor, using the pixmap as both the shape and the mask of the cursor
544  XColor color;
545  color.flags = DoRed | DoGreen | DoBlue;
546  color.red = color.blue = color.green = 0;
547  Cursor cursor = XCreatePixmapCursor(display, cursorPixmap, cursorPixmap, &color, &color, 0, 0);
548 
549  // We don't need the pixmap any longer, free it
550  XFreePixmap(display, cursorPixmap);
551 
552  // Return the cursor
553  return cursor;
554 }
555 
556 void WindowImpl::setMouseCursorVisible(bool visible) {
557  if (!visible && !_hiddenCursor) {
559  }
560  XDefineCursor(_display, _window, visible ? None : _hiddenCursor);
561  XFlush(_display);
562 }
563 
564 void WindowImpl::setMousePos(const Math::Vec2i& mousePosition) {
565  XWarpPointer(_display, None, _window, 0, 0, 0, 0, mousePosition.x(), mousePosition.y());
566  XFlush(_display);
567 }
568 
570  WMHints hints;
571 
572  std::memset(&hints, 0, sizeof(hints));
574 
575  if (static_cast<bool>(style & Style::Titlebar)) {
578  }
579 
580  if (static_cast<bool>(style & Style::Resize)) {
583  }
584 
585  if (static_cast<bool>(style & Style::Close)) {
586  hints.decorations |= 0;
587  hints.functions |= MWMFuncClose;
588  }
589 
590  XChangeProperty(
591  _display,
592  _window,
593  _wmHints,
594  _wmHints,
595  32, // 32 bits
596  PropModeReplace, // Replace existing
597  reinterpret_cast<const unsigned char*>(&hints),
598  5); // 5 elements
599 }
600 
601 } // priv
602 } // Window
603 } // lug
constexpr uint8_t MWMHintsDecorations
Definition: WmHints.hpp:10
Represents an event.
Definition: Event.hpp:89
static Cursor createHiddenCursor(::Display *display, ::Window window)
constexpr uint8_t MWMDecorTitle
Definition: WmHints.hpp:14
constexpr uint8_t MWMDecorResizeh
Definition: WmHints.hpp:13
constexpr uint8_t MWMFuncMove
Definition: WmHints.hpp:20
unsigned long decorations
Definition: WmHints.hpp:29
bool init(const Window::InitInfo &initInfo)
bool shouldIgnoreRepeated(XEvent &xEvent)
This functions filters event depending on the value of _keyRepeat.
constexpr uint8_t MWMDecorMinimize
Definition: WmHints.hpp:16
Class for window.
Definition: Window.hpp:59
Key
Abstraction of keyboard keys.
Definition: Keyboard.hpp:23
Button
Abstraction of Mouse buttons.
Definition: Mouse.hpp:26
void setMousePos(const Math::Vec2i &mousePosition)
constexpr uint8_t MWMDecorMaximize
Definition: WmHints.hpp:17
unsigned long functions
Definition: WmHints.hpp:28
MouseWheelRotated event.
void setWindowDecorations(Style style)
void setMouseCursorVisible(bool visible)
Mouse left window event.
constexpr uint8_t MWMFuncClose
Definition: WmHints.hpp:24
constexpr uint8_t MWMDecorBorder
Definition: WmHints.hpp:12
Mouse entered window event.
bool pollEvent(lug::Window::Event &event)
static Keyboard::Key keysymToLugKey(KeySym key)
static Bool selectEvents(Display *, XEvent *event, XPointer)
constexpr uint8_t MWMFuncResize
Definition: WmHints.hpp:19
constexpr uint8_t MWMHintsFunctions
Definition: WmHints.hpp:9
constexpr uint8_t MWMFuncMaximize
Definition: WmHints.hpp:22
VideoMode _mode
Definition: Window.hpp:185
constexpr uint8_t MWMFuncMinimize
Definition: WmHints.hpp:21
Cursor _hiddenCursor
Invisible cursor used to hide the pointer.
constexpr uint8_t MWMDecorMenu
Definition: WmHints.hpp:15
static Mouse::Button buttonCodeToLugButton(unsigned int buttonCode)