1 | // LAF OS Library |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2017-2018 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "os/x11/window.h" |
13 | |
14 | #include "base/debug.h" |
15 | #include "base/hex.h" |
16 | #include "base/split_string.h" |
17 | #include "base/string.h" |
18 | #include "base/thread.h" |
19 | #include "base/trim_string.h" |
20 | #include "gfx/border.h" |
21 | #include "gfx/rect.h" |
22 | #include "gfx/region.h" |
23 | #include "os/event.h" |
24 | #include "os/event_queue.h" |
25 | #include "os/surface.h" |
26 | #include "os/system.h" |
27 | #include "os/window_spec.h" |
28 | #include "os/x11/cursor.h" |
29 | #include "os/x11/keys.h" |
30 | #include "os/x11/screen.h" |
31 | #include "os/x11/system.h" |
32 | #include "os/x11/x11.h" |
33 | #include "os/x11/xinput.h" |
34 | |
35 | #include <array> |
36 | #include <map> |
37 | |
38 | #define KEY_TRACE(...) |
39 | #define EVENT_TRACE(...) |
40 | |
41 | #define LAF_X11_DOUBLE_CLICK_TIMEOUT 250 |
42 | |
43 | // TODO the window name should be customized from the CMakeLists.txt |
44 | // properties (see OS_WND_CLASS_NAME too) |
45 | #define LAF_X11_WM_CLASS "Aseprite" |
46 | |
47 | const int _NET_WM_STATE_REMOVE = 0; |
48 | const int _NET_WM_STATE_ADD = 1; |
49 | |
50 | const int _NET_WM_MOVERESIZE_SIZE_TOPLEFT = 0; |
51 | const int _NET_WM_MOVERESIZE_SIZE_TOP = 1; |
52 | const int _NET_WM_MOVERESIZE_SIZE_TOPRIGHT = 2; |
53 | const int _NET_WM_MOVERESIZE_SIZE_RIGHT = 3; |
54 | const int _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT = 4; |
55 | const int _NET_WM_MOVERESIZE_SIZE_BOTTOM = 5; |
56 | const int _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT = 6; |
57 | const int _NET_WM_MOVERESIZE_SIZE_LEFT = 7; |
58 | const int _NET_WM_MOVERESIZE_MOVE = 8; |
59 | const int _NET_WM_MOVERESIZE_SIZE_KEYBOARD = 9; |
60 | const int _NET_WM_MOVERESIZE_MOVE_KEYBOARD = 10; |
61 | const int _NET_WM_MOVERESIZE_CANCEL = 11; |
62 | |
63 | namespace os { |
64 | |
65 | namespace { |
66 | |
67 | // This is the expected data for the _MOTIF_WM_HINTS property. Each |
68 | // field must be long/unsigned long as they are passed as 5 |
69 | // XA_CARDINAL values through XChangeProperty() function. |
70 | struct MotifHints { |
71 | enum class Flags : long { |
72 | kNone = 0, |
73 | kDecorations = 2, |
74 | }; |
75 | enum class Decorations : long { |
76 | kNone = 0, |
77 | kAll = 1, |
78 | }; |
79 | Flags flags = Flags::kNone; |
80 | long functions = 0; |
81 | Decorations decorations = Decorations::kNone; |
82 | long inputMode = 0; |
83 | long status = 0; |
84 | }; |
85 | |
86 | // Event generated by the window manager when the close button on the |
87 | // window is pressed by the userh. |
88 | Atom WM_PROTOCOLS = 0; |
89 | Atom WM_DELETE_WINDOW = 0; |
90 | Atom _NET_FRAME_EXTENTS = 0; |
91 | Atom _NET_REQUEST_FRAME_EXTENTS = 0; |
92 | Atom _NET_WM_STATE = 0; |
93 | Atom _NET_WM_STATE_MAXIMIZED_VERT; |
94 | Atom _NET_WM_STATE_MAXIMIZED_HORZ; |
95 | Atom _NET_WM_ALLOWED_ACTIONS = 0; |
96 | |
97 | // Atoms used for the XDND protocol |
98 | Atom XdndAware = 0; |
99 | Atom XdndPosition = 0; |
100 | Atom XdndStatus = 0; |
101 | Atom XdndActionCopy = 0; |
102 | Atom XdndDrop = 0; |
103 | Atom XdndFinished = 0; |
104 | Atom XdndSelection = 0; |
105 | Atom URI_LIST = 0; |
106 | ::Window g_dndSource = 0; |
107 | |
108 | // See https://bugs.freedesktop.org/show_bug.cgi?id=12871 for more |
109 | // information, it looks like the official way to convert a X Window |
110 | // into our own user data pointer (WindowX11 instance) is using a map. |
111 | std::map<::Window, WindowX11*> g_activeWindows; |
112 | |
113 | // Last time an XInput event was received, it's used to avoid |
114 | // processing mouse motion events that are generated at the same time |
115 | // for the XInput devices. |
116 | Time g_lastXInputEventTime = 0; |
117 | |
118 | bool is_mouse_wheel_button(int button) |
119 | { |
120 | return (button == Button4 || button == Button5 || |
121 | button == 6 || button == 7); |
122 | } |
123 | |
124 | gfx::Point get_mouse_wheel_delta(int button) |
125 | { |
126 | gfx::Point delta(0, 0); |
127 | switch (button) { |
128 | // Vertical wheel |
129 | case Button4: delta.y = -1; break; |
130 | case Button5: delta.y = +1; break; |
131 | // Horizontal wheel |
132 | case 6: delta.x = -1; break; |
133 | case 7: delta.x = +1; break; |
134 | } |
135 | return delta; |
136 | } |
137 | |
138 | std::string decode_url(const std::string& in) |
139 | { |
140 | std::string out; |
141 | out.reserve(in.size()); |
142 | |
143 | int i; |
144 | if (std::strncmp(in.c_str(), "file://" , 7) == 0) |
145 | i = 7; |
146 | else |
147 | i = 0; |
148 | |
149 | for (; i<in.size(); ++i) { |
150 | auto c = in[i]; |
151 | if (c == '%' && i+2 < in.size()) { |
152 | c = ((base::hex_to_int(in[i+1]) << 4) | |
153 | (base::hex_to_int(in[i+2]))); |
154 | i += 2; |
155 | } |
156 | out.push_back(c); |
157 | } |
158 | |
159 | base::trim_string(out, out); |
160 | return out; |
161 | } |
162 | |
163 | } // anonymous namespace |
164 | |
165 | // static |
166 | bool WindowX11::g_translateDeadKeys = false; |
167 | |
168 | // static |
169 | WindowX11* WindowX11::getPointerFromHandle(::Window handle) |
170 | { |
171 | auto it = g_activeWindows.find(handle); |
172 | if (it != g_activeWindows.end()) |
173 | return it->second; |
174 | else |
175 | return nullptr; |
176 | } |
177 | |
178 | // static |
179 | size_t WindowX11::countActiveWindows() |
180 | { |
181 | return g_activeWindows.size(); |
182 | } |
183 | |
184 | // static |
185 | void WindowX11::addWindow(WindowX11* window) |
186 | { |
187 | ASSERT(g_activeWindows.find(window->x11window()) == g_activeWindows.end()); |
188 | g_activeWindows[window->x11window()] = window; |
189 | } |
190 | |
191 | // static |
192 | void WindowX11::removeWindow(WindowX11* window) |
193 | { |
194 | auto it = g_activeWindows.find(window->x11window()); |
195 | ASSERT(it != g_activeWindows.end()); |
196 | if (it != g_activeWindows.end()) { |
197 | ASSERT(it->second == window); |
198 | g_activeWindows.erase(it); |
199 | } |
200 | } |
201 | |
202 | WindowX11::WindowX11(::Display* display, const WindowSpec& spec) |
203 | : m_display(display) |
204 | , m_gc(nullptr) |
205 | , m_xic(nullptr) |
206 | , m_scale(spec.scale()) |
207 | , m_lastMousePos(-1, -1) |
208 | , m_lastClientSize(0, 0) |
209 | , m_doubleClickButton(Event::NoneButton) |
210 | , m_borderless(spec.borderless()) |
211 | , m_closable(spec.closable()) |
212 | , m_maximizable(spec.maximizable()) |
213 | , m_minimizable(spec.minimizable()) |
214 | , m_resizable(spec.resizable()) |
215 | , m_transparent(spec.transparent()) |
216 | { |
217 | // Cache some atoms (TODO improve this to cache more atoms) |
218 | if (!_NET_FRAME_EXTENTS) { |
219 | _NET_FRAME_EXTENTS = XInternAtom(m_display, "_NET_FRAME_EXTENTS" , False); |
220 | _NET_REQUEST_FRAME_EXTENTS = XInternAtom(m_display, "_NET_REQUEST_FRAME_EXTENTS" , False); |
221 | } |
222 | if (!_NET_WM_STATE) { |
223 | _NET_WM_STATE = XInternAtom(m_display, "_NET_WM_STATE" , False); |
224 | _NET_WM_STATE_MAXIMIZED_VERT = XInternAtom(m_display, "_NET_WM_STATE_MAXIMIZED_VERT" , False); |
225 | _NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom(m_display, "_NET_WM_STATE_MAXIMIZED_HORZ" , False); |
226 | } |
227 | if (!_NET_WM_ALLOWED_ACTIONS) |
228 | _NET_WM_ALLOWED_ACTIONS = XInternAtom(m_display, "_NET_WM_ALLOWED_ACTIONS" , False); |
229 | |
230 | // Initialize special messages (just the first time a WindowX11 is |
231 | // created) |
232 | if (!WM_PROTOCOLS) |
233 | WM_PROTOCOLS = XInternAtom(m_display, "WM_PROTOCOLS" , False); |
234 | if (!WM_DELETE_WINDOW) |
235 | WM_DELETE_WINDOW = XInternAtom(m_display, "WM_DELETE_WINDOW" , False); |
236 | |
237 | // Get a 32 bpp visual information for transparent windows. |
238 | XVisualInfo vi; |
239 | if (m_transparent) { |
240 | Status s = |
241 | XMatchVisualInfo(m_display, |
242 | DefaultScreen(m_display), |
243 | 32, TrueColor, &vi); |
244 | if (s == 0) { |
245 | // This X11 server doesn't support transparent windows/RGBA |
246 | // images. |
247 | m_transparent = false; |
248 | } |
249 | } |
250 | |
251 | ::Window root = XDefaultRootWindow(m_display); |
252 | |
253 | XSetWindowAttributes swa; |
254 | int swa_mask = CWEventMask; |
255 | swa.event_mask = (StructureNotifyMask | ExposureMask | PropertyChangeMask | |
256 | EnterWindowMask | LeaveWindowMask | FocusChangeMask | |
257 | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | |
258 | KeyPressMask | KeyReleaseMask); |
259 | if (m_transparent) { |
260 | // If one of these attributes is not specified, XCreateWindow() |
261 | // will crash/fail with a BadMatch error. |
262 | swa.background_pixmap = None; |
263 | swa.border_pixel = 0; |
264 | swa.colormap = XCreateColormap(m_display, root, vi.visual, AllocNone); |
265 | swa_mask |= CWBackPixmap | CWBorderPixel | CWColormap; |
266 | } |
267 | |
268 | // We cannot use the override-redirect state because it removes too |
269 | // much behavior of the WM (cannot resize the custom frame as other |
270 | // regular windows in the WM, etc.) |
271 | //swa.override_redirect = (spec.borderless() ? True: False); |
272 | |
273 | gfx::Rect rc; |
274 | |
275 | if (!spec.frame().isEmpty()) |
276 | rc = spec.frame(); |
277 | else |
278 | rc = spec.contentRect(); |
279 | |
280 | m_window = XCreateWindow( |
281 | m_display, root, |
282 | rc.x, rc.y, rc.w, rc.h, 0, |
283 | (m_transparent ? vi.depth: CopyFromParent), |
284 | InputOutput, |
285 | (m_transparent ? vi.visual: CopyFromParent), |
286 | swa_mask, // Do not use CWOverrideRedirect |
287 | &swa); |
288 | |
289 | if (!m_window) |
290 | throw std::runtime_error("Cannot create X11 window" ); |
291 | |
292 | setWMClass(LAF_X11_WM_CLASS); |
293 | |
294 | // Special frame for this window |
295 | if (spec.floating()) { |
296 | // We use _NET_WM_WINDOW_TYPE_UTILITY for floating windows |
297 | Atom _NET_WM_WINDOW_TYPE = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE" , False); |
298 | Atom _NET_WM_WINDOW_TYPE_UTILITY = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE_UTILITY" , False); |
299 | Atom _NET_WM_WINDOW_TYPE_NORMAL = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE_NORMAL" , False); |
300 | if (_NET_WM_WINDOW_TYPE && |
301 | _NET_WM_WINDOW_TYPE_UTILITY && |
302 | _NET_WM_WINDOW_TYPE_NORMAL) { |
303 | // We've to specify the window types in order of preference (but |
304 | // must include at least one of the basic window type atoms). |
305 | std::vector<Atom> data = { _NET_WM_WINDOW_TYPE_UTILITY, |
306 | _NET_WM_WINDOW_TYPE_NORMAL }; |
307 | XChangeProperty( |
308 | m_display, m_window, _NET_WM_WINDOW_TYPE, |
309 | XA_ATOM, 32, PropModeReplace, |
310 | (const unsigned char*)&data[0], data.size()); |
311 | } |
312 | } |
313 | |
314 | // To remove the borders and keep the window behavior of the Window |
315 | // Manager (e.g. Super key + mouse to resize/move the window), we |
316 | // can set the _MOTIF_WM_HINTS decorations to 0 (without |
317 | // decorations). |
318 | // |
319 | // The alternatives (using _NET_WM_WINDOW_TYPE or override-redirect) |
320 | // are useless because they remove the default behavior of the |
321 | // operating system (making a complete "naked" window without |
322 | // behavior at all). |
323 | { |
324 | MotifHints hints; |
325 | hints.flags = MotifHints::Flags::kDecorations; |
326 | hints.decorations = (spec.borderless() ? MotifHints::Decorations::kNone: |
327 | MotifHints::Decorations::kAll); |
328 | |
329 | Atom _MOTIF_WM_HINTS = XInternAtom(m_display, "_MOTIF_WM_HINTS" , False); |
330 | XChangeProperty( |
331 | m_display, m_window, |
332 | _MOTIF_WM_HINTS, |
333 | _MOTIF_WM_HINTS, // Instead of XA_CARDINAL here goes _MOTIF_WM_HINTS too |
334 | 32, PropModeReplace, |
335 | (const unsigned char*)&hints, |
336 | sizeof(hints) / sizeof(long)); |
337 | |
338 | static_assert(sizeof(hints) / sizeof(long) == 5, "Invalid MotifHints struct" ); |
339 | } |
340 | |
341 | // Receive stylus/eraser events |
342 | X11::instance()->xinput()->selectExtensionEvents(m_display, m_window); |
343 | |
344 | // Change preferred origin/size for the window (this should be used by the WM) |
345 | { |
346 | XSizeHints* hints = XAllocSizeHints(); |
347 | hints->flags = |
348 | PPosition | PSize | |
349 | PResizeInc | PWinGravity; |
350 | hints->x = rc.x; |
351 | hints->y = rc.y; |
352 | hints->width = rc.w; |
353 | hints->height = rc.h; |
354 | hints->width_inc = m_scale; |
355 | hints->height_inc = m_scale; |
356 | hints->win_gravity = SouthGravity; |
357 | XSetWMNormalHints(m_display, m_window, hints); |
358 | XFree(hints); |
359 | } |
360 | |
361 | XMapWindow(m_display, m_window); |
362 | |
363 | // In case the user wants to set the initial window bounds as the |
364 | // frame bounds, and as X11 expects the content bounds in |
365 | // XMoveResizeWindow(), we've to remove the frame extents from the |
366 | // specified spec.frame() rectangle. Anyway the frame extents is not |
367 | // yet available here, so we have to send a |
368 | // _NET_REQUEST_FRAME_EXTENTS event to request the frame extents. |
369 | if (!spec.borderless() && !spec.frame().isEmpty()) { |
370 | if (requestX11FrameExtents()) { |
371 | getX11FrameExtents(); |
372 | rc.shrink(m_frameExtents); |
373 | } |
374 | } |
375 | |
376 | // Set the window position and size as the position is not correctly |
377 | // used from the XCreateWindow() or XSizeHints. |
378 | XMoveResizeWindow(m_display, m_window, |
379 | rc.x, rc.y, rc.w, rc.h); |
380 | |
381 | XSetWMProtocols(m_display, m_window, &WM_DELETE_WINDOW, 1); |
382 | |
383 | if (spec.floating() && spec.parent()) { |
384 | ASSERT(static_cast<WindowX11*>(spec.parent())->m_window); |
385 | XSetTransientForHint( |
386 | m_display, |
387 | m_window, |
388 | static_cast<WindowX11*>(spec.parent())->m_window); |
389 | } |
390 | |
391 | m_gc = XCreateGC(m_display, m_window, 0, nullptr); |
392 | |
393 | XIM xim = X11::instance()->xim(); |
394 | if (xim) { |
395 | m_xic = XCreateIC(xim, |
396 | XNInputStyle, XIMPreeditNothing | XIMStatusNothing, |
397 | XNClientWindow, m_window, |
398 | XNFocusWindow, m_window, |
399 | nullptr); |
400 | } |
401 | |
402 | // Adding the XdndAware property to the window we specify that we |
403 | // support drag-and-drop operations (so we can drop files to |
404 | // generate Event::Type::DropFiles events from this specific |
405 | // window). |
406 | // |
407 | // TODO add support for other formats (e.g. dropping images?) |
408 | if (!XdndAware) { |
409 | XdndAware = XInternAtom(m_display, "XdndAware" , False); |
410 | XdndPosition = XInternAtom(m_display, "XdndPosition" , False); |
411 | XdndStatus = XInternAtom(m_display, "XdndStatus" , False); |
412 | XdndActionCopy = XInternAtom(m_display, "XdndActionCopy" , False); |
413 | XdndDrop = XInternAtom(m_display, "XdndDrop" , False); |
414 | XdndFinished = XInternAtom(m_display, "XdndFinished" , False); |
415 | XdndSelection = XInternAtom(m_display, "XdndSelection" , False); |
416 | URI_LIST = XInternAtom(m_display, "text/uri-list" , False); |
417 | } |
418 | |
419 | Atom protocolVersion = 5; |
420 | XChangeProperty( |
421 | m_display, m_window, |
422 | XdndAware, XA_ATOM, 32, |
423 | PropModeReplace, |
424 | (const unsigned char*)&protocolVersion, 1); |
425 | |
426 | WindowX11::addWindow(this); |
427 | } |
428 | |
429 | WindowX11::~WindowX11() |
430 | { |
431 | if (m_xic) |
432 | XDestroyIC(m_xic); |
433 | XFreeGC(m_display, m_gc); |
434 | XDestroyWindow(m_display, m_window); |
435 | |
436 | WindowX11::removeWindow(this); |
437 | } |
438 | |
439 | os::ScreenRef WindowX11::screen() const |
440 | { |
441 | return os::make_ref<ScreenX11>(DefaultScreen(m_display)); |
442 | } |
443 | |
444 | os::ColorSpaceRef WindowX11::colorSpace() const |
445 | { |
446 | if (auto defaultCS = os::instance()->windowsColorSpace()) |
447 | return defaultCS; |
448 | |
449 | // TODO get the window color space |
450 | return os::instance()->makeColorSpace(gfx::ColorSpace::MakeSRGB()); |
451 | } |
452 | |
453 | void WindowX11::setScale(const int scale) |
454 | { |
455 | m_scale = scale; |
456 | |
457 | // Adjust increment/decrement of the window to be multiplies of m_scale |
458 | XSizeHints* hints = XAllocSizeHints(); |
459 | hints->flags = PResizeInc; |
460 | hints->width_inc = m_scale; |
461 | hints->height_inc = m_scale; |
462 | XSetWMNormalHints(m_display, m_window, hints); |
463 | XFree(hints); |
464 | |
465 | onResize(clientSize()); |
466 | } |
467 | |
468 | bool WindowX11::isVisible() const |
469 | { |
470 | // TODO |
471 | return true; |
472 | } |
473 | |
474 | void WindowX11::setVisible(bool visible) |
475 | { |
476 | // TODO |
477 | } |
478 | |
479 | void WindowX11::activate() |
480 | { |
481 | Atom _NET_ACTIVE_WINDOW = XInternAtom(m_display, "_NET_ACTIVE_WINDOW" , False); |
482 | if (!_NET_ACTIVE_WINDOW) |
483 | return; // No atoms? |
484 | |
485 | ::Window root = XDefaultRootWindow(m_display); |
486 | XEvent event; |
487 | memset(&event, 0, sizeof(event)); |
488 | event.xany.type = ClientMessage; |
489 | event.xclient.window = m_window; |
490 | event.xclient.message_type = _NET_ACTIVE_WINDOW; |
491 | event.xclient.format = 32; |
492 | event.xclient.data.l[0] = 1; // 1 when the request comes from an application |
493 | event.xclient.data.l[1] = CurrentTime; |
494 | event.xclient.data.l[2] = 0; |
495 | event.xclient.data.l[3] = 0; |
496 | |
497 | XSendEvent(m_display, root, 0, |
498 | SubstructureNotifyMask | SubstructureRedirectMask, &event); |
499 | } |
500 | |
501 | void WindowX11::maximize() |
502 | { |
503 | ::Window root = XDefaultRootWindow(m_display); |
504 | XEvent event; |
505 | memset(&event, 0, sizeof(event)); |
506 | event.xany.type = ClientMessage; |
507 | event.xclient.window = m_window; |
508 | event.xclient.message_type = _NET_WM_STATE; |
509 | event.xclient.format = 32; |
510 | event.xclient.data.l[0] = (isMaximized() ? _NET_WM_STATE_REMOVE: |
511 | _NET_WM_STATE_ADD); |
512 | event.xclient.data.l[1] = _NET_WM_STATE_MAXIMIZED_VERT; |
513 | event.xclient.data.l[2] = _NET_WM_STATE_MAXIMIZED_HORZ; |
514 | |
515 | XSendEvent(m_display, root, 0, |
516 | SubstructureNotifyMask | SubstructureRedirectMask, &event); |
517 | } |
518 | |
519 | void WindowX11::minimize() |
520 | { |
521 | XIconifyWindow(m_display, m_window, DefaultScreen(m_display)); |
522 | } |
523 | |
524 | bool WindowX11::isMaximized() const |
525 | { |
526 | bool result = false; |
527 | Atom actual_type; |
528 | int actual_format; |
529 | unsigned long nitems; |
530 | unsigned long bytes_after; |
531 | Atom* prop = nullptr; |
532 | int res = XGetWindowProperty(m_display, m_window, |
533 | _NET_WM_STATE, |
534 | // TODO is 256 enough? |
535 | 0, 256, |
536 | False, XA_ATOM, |
537 | &actual_type, &actual_format, |
538 | &nitems, &bytes_after, |
539 | (unsigned char**)&prop); |
540 | |
541 | if (res == Success) { |
542 | for (int i=0; i<nitems; ++i) { |
543 | if (prop[i] == _NET_WM_STATE_MAXIMIZED_VERT || |
544 | prop[i] == _NET_WM_STATE_MAXIMIZED_HORZ) { |
545 | result = true; |
546 | } |
547 | } |
548 | XFree(prop); |
549 | } |
550 | return result; |
551 | } |
552 | |
553 | bool WindowX11::isMinimized() const |
554 | { |
555 | return false; |
556 | } |
557 | |
558 | bool WindowX11::isTransparent() const |
559 | { |
560 | return m_transparent; |
561 | } |
562 | |
563 | bool WindowX11::isFullscreen() const |
564 | { |
565 | // TODO ask _NET_WM_STATE_FULLSCREEN atom in _NET_WM_STATE window property |
566 | return m_fullscreen; |
567 | } |
568 | |
569 | void WindowX11::setFullscreen(bool state) |
570 | { |
571 | if (isFullscreen() == state) |
572 | return; |
573 | |
574 | Atom _NET_WM_STATE_FULLSCREEN = XInternAtom(m_display, "_NET_WM_STATE_FULLSCREEN" , False); |
575 | if (!_NET_WM_STATE || !_NET_WM_STATE_FULLSCREEN) |
576 | return; // No atoms? |
577 | |
578 | // From _NET_WM_STATE section in https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html#idm46018259875952 |
579 | // |
580 | // "Client wishing to change the state of a window MUST send a |
581 | // _NET_WM_STATE client message to the root window. The Window |
582 | // Manager MUST keep this property updated to reflect the |
583 | // current state of the window." |
584 | // |
585 | ::Window root = XDefaultRootWindow(m_display); |
586 | XEvent event; |
587 | memset(&event, 0, sizeof(event)); |
588 | event.xany.type = ClientMessage; |
589 | event.xclient.window = m_window; |
590 | event.xclient.message_type = _NET_WM_STATE; |
591 | event.xclient.format = 32; |
592 | // The action |
593 | event.xclient.data.l[0] = (state ? _NET_WM_STATE_ADD: |
594 | _NET_WM_STATE_REMOVE); |
595 | event.xclient.data.l[1] = _NET_WM_STATE_FULLSCREEN; // First property to alter |
596 | event.xclient.data.l[2] = 0; // Second property to alter |
597 | event.xclient.data.l[3] = 0; // Source indication |
598 | |
599 | XSendEvent(m_display, root, 0, |
600 | SubstructureNotifyMask | SubstructureRedirectMask, &event); |
601 | |
602 | m_fullscreen = state; |
603 | } |
604 | |
605 | void WindowX11::setTitle(const std::string& title) |
606 | { |
607 | XTextProperty prop; |
608 | prop.value = (unsigned char*)title.c_str(); |
609 | prop.encoding = XA_STRING; |
610 | prop.format = 8; |
611 | prop.nitems = std::strlen((char*)title.c_str()); |
612 | XSetWMName(m_display, m_window, &prop); |
613 | } |
614 | |
615 | void WindowX11::setIcons(const SurfaceList& icons) |
616 | { |
617 | if (!m_display || !m_window) |
618 | return; |
619 | |
620 | bool first = true; |
621 | for (auto& icon : icons) { |
622 | const int w = icon->width(); |
623 | const int h = icon->height(); |
624 | |
625 | SurfaceFormatData format; |
626 | icon->getFormat(&format); |
627 | |
628 | std::vector<unsigned long> data(w*h+2); |
629 | int i = 0; |
630 | data[i++] = w; |
631 | data[i++] = h; |
632 | for (int y=0; y<h; ++y) { |
633 | const uint32_t* p = (const uint32_t*)icon->getData(0, y); |
634 | for (int x=0; x<w; ++x, ++p) { |
635 | uint32_t c = *p; |
636 | data[i++] = |
637 | (((c & format.blueMask ) >> format.blueShift ) ) | |
638 | (((c & format.greenMask) >> format.greenShift) << 8) | |
639 | (((c & format.redMask ) >> format.redShift ) << 16) | |
640 | (((c & format.alphaMask) >> format.alphaShift) << 24); |
641 | } |
642 | } |
643 | |
644 | Atom _NET_WM_ICON = XInternAtom(m_display, "_NET_WM_ICON" , False); |
645 | XChangeProperty( |
646 | m_display, m_window, _NET_WM_ICON, XA_CARDINAL, 32, |
647 | first ? PropModeReplace: |
648 | PropModeAppend, |
649 | (const unsigned char*)&data[0], data.size()); |
650 | |
651 | first = false; |
652 | } |
653 | } |
654 | |
655 | gfx::Rect WindowX11::frame() const |
656 | { |
657 | gfx::Rect rc = contentRect(); |
658 | if (!m_borderless) |
659 | rc.enlarge(m_frameExtents); |
660 | return rc; |
661 | } |
662 | |
663 | void WindowX11::setFrame(const gfx::Rect& bounds) |
664 | { |
665 | gfx::Rect rc = bounds; |
666 | if (!m_borderless) { |
667 | rc.shrink(m_frameExtents); |
668 | rc.y -= m_frameExtents.top(); // Shrink from top |
669 | } |
670 | |
671 | XMoveResizeWindow( |
672 | m_display, |
673 | m_window, |
674 | rc.x, rc.y, |
675 | rc.w, rc.h); |
676 | } |
677 | |
678 | gfx::Rect WindowX11::contentRect() const |
679 | { |
680 | ::Window root; |
681 | int x, y; |
682 | unsigned int width, height, border, depth; |
683 | XGetGeometry(m_display, m_window, &root, |
684 | &x, &y, &width, &height, &border, &depth); |
685 | |
686 | ::Window child_return; |
687 | XTranslateCoordinates(m_display, m_window, root, |
688 | 0, 0, &x, &y, &child_return); |
689 | |
690 | return gfx::Rect(x, y, int(width), int(height)); |
691 | } |
692 | |
693 | std::string WindowX11::title() const |
694 | { |
695 | XTextProperty prop; |
696 | if (!XGetWMName(m_display, m_window, &prop) || !prop.value) |
697 | return std::string(); |
698 | |
699 | std::string value = (const char*)prop.value; |
700 | XFree(prop.value); |
701 | return value; |
702 | } |
703 | |
704 | gfx::Size WindowX11::clientSize() const |
705 | { |
706 | ::Window root; |
707 | int x, y; |
708 | unsigned int width, height, border, depth; |
709 | XGetGeometry(m_display, m_window, &root, |
710 | &x, &y, &width, &height, &border, &depth); |
711 | return gfx::Size(int(width), int(height)); |
712 | } |
713 | |
714 | gfx::Rect WindowX11::restoredFrame() const |
715 | { |
716 | ::Window root; |
717 | int x, y; |
718 | unsigned int width, height, border, depth; |
719 | XGetGeometry(m_display, m_window, &root, |
720 | &x, &y, &width, &height, &border, &depth); |
721 | return gfx::Rect(x, y, int(width), int(height)); |
722 | } |
723 | |
724 | void WindowX11::captureMouse() |
725 | { |
726 | XGrabPointer(m_display, m_window, False, |
727 | PointerMotionMask | ButtonPressMask | ButtonReleaseMask, |
728 | GrabModeAsync, GrabModeAsync, |
729 | None, None, CurrentTime); |
730 | } |
731 | |
732 | void WindowX11::releaseMouse() |
733 | { |
734 | XUngrabPointer(m_display, CurrentTime); |
735 | } |
736 | |
737 | void WindowX11::setMousePosition(const gfx::Point& position) |
738 | { |
739 | ::Window root; |
740 | int x, y; |
741 | unsigned int w, h, border, depth; |
742 | XGetGeometry(m_display, m_window, &root, |
743 | &x, &y, &w, &h, &border, &depth); |
744 | XWarpPointer(m_display, m_window, m_window, 0, 0, w, h, |
745 | position.x*m_scale, position.y*m_scale); |
746 | } |
747 | |
748 | void WindowX11::invalidateRegion(const gfx::Region& rgn) |
749 | { |
750 | gfx::Rect bounds = rgn.bounds(); |
751 | onPaint(gfx::Rect(bounds.x*m_scale, |
752 | bounds.y*m_scale, |
753 | bounds.w*m_scale, |
754 | bounds.h*m_scale)); |
755 | } |
756 | |
757 | bool WindowX11::setCursor(NativeCursor nativeCursor) |
758 | { |
759 | CursorRef cursor = ((SystemX11*)os::instance())->getNativeCursor(nativeCursor); |
760 | if (cursor) |
761 | return setX11Cursor((::Cursor)cursor->nativeHandle()); |
762 | else |
763 | return false; |
764 | } |
765 | |
766 | bool WindowX11::setCursor(const CursorRef& cursor) |
767 | { |
768 | ASSERT(cursor); |
769 | if (!cursor) |
770 | return false; |
771 | |
772 | if (cursor->nativeHandle()) |
773 | return setX11Cursor((::Cursor)cursor->nativeHandle()); |
774 | else |
775 | return setCursor(NativeCursor::Hidden); |
776 | } |
777 | |
778 | void WindowX11::performWindowAction(const WindowAction action, |
779 | const Event* ev) |
780 | { |
781 | Atom _NET_WM_MOVERESIZE = XInternAtom(m_display, "_NET_WM_MOVERESIZE" , False); |
782 | if (!_NET_WM_MOVERESIZE) |
783 | return; // No atoms? |
784 | |
785 | int x, y; |
786 | if (ev) { |
787 | x = ev->position().x; |
788 | y = ev->position().y; |
789 | } |
790 | else { |
791 | int rootx, rooty; |
792 | unsigned int mask; |
793 | ::Window root, child; |
794 | if (!XQueryPointer(m_display, m_window, &root, &child, &rootx, &rooty, &x, &y, &mask)) { |
795 | x = 0; |
796 | y = 0; |
797 | } |
798 | } |
799 | |
800 | int button = (ev ? get_x_mouse_button_from_event(ev->button()): 0); |
801 | Atom direction = 0; |
802 | switch (action) { |
803 | case WindowAction::Cancel: direction = _NET_WM_MOVERESIZE_CANCEL; break; |
804 | case WindowAction::Move: direction = _NET_WM_MOVERESIZE_MOVE; break; |
805 | case WindowAction::ResizeFromTopLeft: direction = _NET_WM_MOVERESIZE_SIZE_TOPLEFT; break; |
806 | case WindowAction::ResizeFromTop: direction = _NET_WM_MOVERESIZE_SIZE_TOP; break; |
807 | case WindowAction::ResizeFromTopRight: direction = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT; break; |
808 | case WindowAction::ResizeFromLeft: direction = _NET_WM_MOVERESIZE_SIZE_LEFT; break; |
809 | case WindowAction::ResizeFromRight: direction = _NET_WM_MOVERESIZE_SIZE_RIGHT; break; |
810 | case WindowAction::ResizeFromBottomLeft: direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; break; |
811 | case WindowAction::ResizeFromBottom: direction = _NET_WM_MOVERESIZE_SIZE_BOTTOM; break; |
812 | case WindowAction::ResizeFromBottomRight: direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; break; |
813 | } |
814 | |
815 | // From: |
816 | // https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248 |
817 | // "The Client MUST release all grabs prior to sending such |
818 | // message (except for the _NET_WM_MOVERESIZE_CANCEL message)." |
819 | if (direction != _NET_WM_MOVERESIZE_CANCEL) |
820 | releaseMouse(); |
821 | |
822 | ::Window root = XDefaultRootWindow(m_display); |
823 | ::Window child; |
824 | XTranslateCoordinates(m_display, m_window, root, |
825 | x, y, &x, &y, &child); |
826 | |
827 | XEvent event; |
828 | memset(&event, 0, sizeof(event)); |
829 | event.xany.type = ClientMessage; |
830 | event.xclient.window = m_window; |
831 | event.xclient.message_type = _NET_WM_MOVERESIZE; |
832 | event.xclient.format = 32; |
833 | event.xclient.data.l[0] = x; |
834 | event.xclient.data.l[1] = y; |
835 | event.xclient.data.l[2] = direction; |
836 | event.xclient.data.l[3] = button; |
837 | event.xclient.data.l[4] = 0; |
838 | |
839 | XSendEvent(m_display, root, 0, |
840 | SubstructureNotifyMask | SubstructureRedirectMask, &event); |
841 | } |
842 | |
843 | void WindowX11::setWMClass(const std::string& res_class) |
844 | { |
845 | std::string res_name = base::string_to_lower(res_class); |
846 | XClassHint ch; |
847 | ch.res_name = (char*)res_name.c_str(); |
848 | ch.res_class = (char*)res_class.c_str(); |
849 | XSetClassHint(m_display, m_window, &ch); |
850 | } |
851 | |
852 | // TODO this doesn't work on GNOME 3, so still some work is required |
853 | void WindowX11::setAllowedActions() |
854 | { |
855 | Atom actual_type; |
856 | int actual_format; |
857 | unsigned long nitems; |
858 | unsigned long bytes_after; |
859 | Atom* prop = nullptr; |
860 | int res = XGetWindowProperty(m_display, m_window, |
861 | _NET_WM_ALLOWED_ACTIONS, |
862 | 0, 256, |
863 | False, XA_ATOM, |
864 | &actual_type, &actual_format, |
865 | &nitems, &bytes_after, |
866 | (unsigned char**)&prop); |
867 | if (res != Success) |
868 | return; |
869 | |
870 | std::vector<Atom> allowed; |
871 | for (int i=0; i<nitems; ++i) |
872 | allowed.push_back(prop[i]); |
873 | XFree(prop); |
874 | |
875 | // Auxiliary function to match one allowed action from the "spec" |
876 | // with the specific Atom required in the _NET_WM_ALLOWED_ACTIONS |
877 | // property. |
878 | auto set_allowed_action = |
879 | [&allowed, this](const bool expected, const char* atomName) { |
880 | Atom atom = XInternAtom(m_display, atomName, False); |
881 | if (!atom) |
882 | return; |
883 | auto it = std::find(allowed.begin(), allowed.end(), atom); |
884 | if (expected) { |
885 | // Add missing atom/action |
886 | if (it == allowed.end()) { |
887 | allowed.push_back(atom); |
888 | } |
889 | } |
890 | else { |
891 | // Remove disallowed atom/action |
892 | if (it != allowed.end()) { |
893 | allowed.erase(it); |
894 | } |
895 | } |
896 | }; |
897 | |
898 | set_allowed_action(m_resizable, "_NET_WM_ACTION_RESIZE" ); |
899 | set_allowed_action(m_resizable, "_NET_WM_ACTION_FULLSCREEN" ); |
900 | set_allowed_action(m_minimizable, "_NET_WM_ACTION_MINIMIZE" ); |
901 | set_allowed_action(m_maximizable, "_NET_WM_ACTION_MAXIMIZE_VERT" ); |
902 | set_allowed_action(m_maximizable, "_NET_WM_ACTION_MAXIMIZE_HORZ" ); |
903 | set_allowed_action(m_closable, "_NET_WM_ACTION_CLOSE" ); |
904 | |
905 | XChangeProperty( |
906 | m_display, m_window, _NET_WM_ALLOWED_ACTIONS, |
907 | XA_ATOM, 32, (nitems == 0 ? PropModeAppend: |
908 | PropModeReplace), |
909 | (const unsigned char*)&allowed[0], allowed.size()); |
910 | } |
911 | |
912 | bool WindowX11::setX11Cursor(::Cursor xcursor) |
913 | { |
914 | if (xcursor != None) { |
915 | XDefineCursor(m_display, m_window, xcursor); |
916 | return true; |
917 | } |
918 | else |
919 | return false; |
920 | } |
921 | |
922 | bool WindowX11::requestX11FrameExtents() |
923 | { |
924 | ::Window root = XDefaultRootWindow(m_display); |
925 | |
926 | // Send a _NET_REQUEST_FRAME_EXTENTS to the root window to ask for |
927 | // the frame extents of this window. |
928 | XEvent event; |
929 | memset(&event, 0, sizeof(event)); |
930 | event.xany.type = ClientMessage; |
931 | event.xclient.window = m_window; |
932 | event.xclient.message_type = _NET_REQUEST_FRAME_EXTENTS; |
933 | event.xclient.format = 32; |
934 | XSendEvent(m_display, root, 0, |
935 | SubstructureNotifyMask | SubstructureRedirectMask, &event); |
936 | |
937 | // Now we have to wait the _NET_FRAME_EXTENTS property modification |
938 | // event. |
939 | auto isFrameExtentsEvent = |
940 | [](Display* d, XEvent* e, XPointer w) -> Bool { |
941 | return (e->xany.type == PropertyNotify && |
942 | e->xproperty.window == (::Window)w && |
943 | e->xproperty.atom == _NET_FRAME_EXTENTS); |
944 | }; |
945 | |
946 | XEvent event2; |
947 | int wait = 100; // We're going to wait 100 milliseconds |
948 | while (wait > 0) { |
949 | if (XCheckIfEvent(m_display, |
950 | &event2, |
951 | isFrameExtentsEvent, |
952 | (XPointer)m_window)) { |
953 | // Event in queue (the event is not removed from the queue). |
954 | return true; |
955 | } |
956 | base::this_thread::sleep_for(0.01); |
957 | wait -= 10; |
958 | } |
959 | |
960 | // We're not sure if we're going to receive the _NET_FRAME_EXTENTS |
961 | // notification in the future |
962 | return false; |
963 | } |
964 | |
965 | void WindowX11::getX11FrameExtents() |
966 | { |
967 | Atom actual_type; |
968 | int actual_format; |
969 | unsigned long nitems; |
970 | unsigned long bytes_after; |
971 | unsigned long* prop = nullptr; |
972 | int res = XGetWindowProperty(m_display, m_window, |
973 | _NET_FRAME_EXTENTS, |
974 | 0, 4, |
975 | False, XA_CARDINAL, |
976 | &actual_type, &actual_format, |
977 | &nitems, &bytes_after, |
978 | (unsigned char**)&prop); |
979 | |
980 | if (res == Success && nitems == 4) { |
981 | // Get the dimension of the title bar + borders (WM decorators) |
982 | m_frameExtents.left(prop[0]); |
983 | m_frameExtents.right(prop[1]); |
984 | m_frameExtents.top(prop[2]); |
985 | m_frameExtents.bottom(prop[3]); |
986 | XFree(prop); |
987 | } |
988 | } |
989 | |
990 | void WindowX11::processX11Event(XEvent& event) |
991 | { |
992 | auto xinput = X11::instance()->xinput(); |
993 | if (xinput->handleExtensionEvent(event)) { |
994 | Event ev; |
995 | xinput->convertExtensionEvent(event, ev, m_scale, |
996 | g_lastXInputEventTime); |
997 | queueEvent(ev); |
998 | return; |
999 | } |
1000 | |
1001 | switch (event.type) { |
1002 | |
1003 | case ConfigureNotify: { |
1004 | gfx::Rect rc(event.xconfigure.x, |
1005 | event.xconfigure.y, |
1006 | event.xconfigure.width, |
1007 | event.xconfigure.height); |
1008 | |
1009 | if (rc.w > 0 && rc.h > 0 && rc.size() != m_lastClientSize) { |
1010 | m_lastClientSize = rc.size(); |
1011 | onResize(rc.size()); |
1012 | } |
1013 | break; |
1014 | } |
1015 | |
1016 | case Expose: { |
1017 | gfx::Rect rc(event.xexpose.x, event.xexpose.y, |
1018 | event.xexpose.width, event.xexpose.height); |
1019 | onPaint(rc); |
1020 | break; |
1021 | } |
1022 | |
1023 | case KeyPress: |
1024 | case KeyRelease: { |
1025 | Event ev; |
1026 | ev.setType(event.type == KeyPress ? Event::KeyDown: Event::KeyUp); |
1027 | |
1028 | KeySym keysym = XLookupKeysym(&event.xkey, 0); |
1029 | ev.setScancode(x11_keysym_to_scancode(keysym)); |
1030 | |
1031 | if (m_xic) { |
1032 | std::vector<char> buf(16); |
1033 | size_t len = Xutf8LookupString(m_xic, &event.xkey, |
1034 | &buf[0], buf.size(), |
1035 | nullptr, nullptr); |
1036 | if (len < buf.size()) |
1037 | buf[len] = 0; |
1038 | std::wstring wideChars = base::from_utf8(std::string(&buf[0])); |
1039 | if (!wideChars.empty()) |
1040 | ev.setUnicodeChar(wideChars[0]); |
1041 | KEY_TRACE("Xutf8LookupString %s\n" , &buf[0]); |
1042 | } |
1043 | |
1044 | // Key event used by the input method (e.g. when the user |
1045 | // presses a dead key). |
1046 | if (XFilterEvent(&event, m_window)) |
1047 | break; |
1048 | |
1049 | int modifiers = (int)get_modifiers_from_x(event.xkey.state); |
1050 | switch (keysym) { |
1051 | case XK_space: { |
1052 | switch (event.type) { |
1053 | case KeyPress: |
1054 | g_spaceBarIsPressed = true; |
1055 | break; |
1056 | case KeyRelease: |
1057 | g_spaceBarIsPressed = false; |
1058 | |
1059 | // If the next event after a KeyRelease is a KeyPress of |
1060 | // the same keycode (the space bar in this case), it |
1061 | // means that this KeyRelease is just a repetition of a |
1062 | // the same keycode. |
1063 | if (XEventsQueued(m_display, QueuedAfterReading)) { |
1064 | XEvent nextEvent; |
1065 | XPeekEvent(m_display, &nextEvent); |
1066 | if (nextEvent.type == KeyPress && |
1067 | nextEvent.xkey.time == event.xkey.time && |
1068 | nextEvent.xkey.keycode == event.xkey.keycode) { |
1069 | g_spaceBarIsPressed = true; |
1070 | } |
1071 | } |
1072 | break; |
1073 | } |
1074 | break; |
1075 | } |
1076 | case XK_Shift_L: |
1077 | case XK_Shift_R: |
1078 | modifiers |= kKeyShiftModifier; |
1079 | break; |
1080 | case XK_Control_L: |
1081 | case XK_Control_R: |
1082 | modifiers |= kKeyCtrlModifier; |
1083 | break; |
1084 | case XK_Alt_L: |
1085 | case XK_Alt_R: |
1086 | modifiers |= kKeyAltModifier; |
1087 | break; |
1088 | case XK_Meta_L: |
1089 | case XK_Super_L: |
1090 | case XK_Meta_R: |
1091 | case XK_Super_R: |
1092 | modifiers |= kKeyWinModifier; |
1093 | break; |
1094 | } |
1095 | ev.setModifiers((KeyModifiers)modifiers); |
1096 | KEY_TRACE("%s state=%04x keycode=%04x\n" , |
1097 | (event.type == KeyPress ? "KeyPress" : "KeyRelease" ), |
1098 | event.xkey.state, |
1099 | event.xkey.keycode); |
1100 | KEY_TRACE(" > %s\n" , XKeysymToString(keysym)); |
1101 | |
1102 | queueEvent(ev); |
1103 | break; |
1104 | } |
1105 | |
1106 | case ButtonPress: |
1107 | case ButtonRelease: { |
1108 | // This can happen when the button press/release events are |
1109 | // handled in XInput |
1110 | if (event.xmotion.time == g_lastXInputEventTime) |
1111 | break; |
1112 | |
1113 | Event ev; |
1114 | if (is_mouse_wheel_button(event.xbutton.button)) { |
1115 | if (event.type == ButtonPress) { |
1116 | ev.setType(Event::MouseWheel); |
1117 | ev.setWheelDelta(get_mouse_wheel_delta(event.xbutton.button)); |
1118 | } |
1119 | else { |
1120 | // Ignore ButtonRelese for the mouse wheel to avoid |
1121 | // duplicating MouseWheel event effects. |
1122 | break; |
1123 | } |
1124 | } |
1125 | else { |
1126 | ev.setType(event.type == ButtonPress ? Event::MouseDown: |
1127 | Event::MouseUp); |
1128 | |
1129 | Event::MouseButton button = |
1130 | get_mouse_button_from_x(event.xbutton.button); |
1131 | ev.setButton(button); |
1132 | |
1133 | if (event.type == ButtonPress) { |
1134 | if (m_doubleClickButton == button && |
1135 | base::current_tick() - m_doubleClickTick < LAF_X11_DOUBLE_CLICK_TIMEOUT) { |
1136 | ev.setType(Event::MouseDoubleClick); |
1137 | m_doubleClickButton = Event::NoneButton; |
1138 | } |
1139 | else { |
1140 | m_doubleClickButton = button; |
1141 | m_doubleClickTick = base::current_tick(); |
1142 | } |
1143 | } |
1144 | } |
1145 | ev.setModifiers(get_modifiers_from_x(event.xbutton.state)); |
1146 | ev.setPosition(gfx::Point(event.xbutton.x / m_scale, |
1147 | event.xbutton.y / m_scale)); |
1148 | |
1149 | queueEvent(ev); |
1150 | break; |
1151 | } |
1152 | |
1153 | case MotionNotify: { |
1154 | // This can happen when the motion event are handled in XInput |
1155 | if (event.xmotion.time == g_lastXInputEventTime) |
1156 | break; |
1157 | |
1158 | // Reset double-click state |
1159 | m_doubleClickButton = Event::NoneButton; |
1160 | |
1161 | gfx::Point pos(event.xmotion.x / m_scale, |
1162 | event.xmotion.y / m_scale); |
1163 | |
1164 | if (m_lastMousePos == pos) |
1165 | break; |
1166 | m_lastMousePos = pos; |
1167 | |
1168 | Event ev; |
1169 | ev.setType(Event::MouseMove); |
1170 | ev.setModifiers(get_modifiers_from_x(event.xmotion.state)); |
1171 | ev.setPosition(pos); |
1172 | queueEvent(ev); |
1173 | break; |
1174 | } |
1175 | |
1176 | case EnterNotify: |
1177 | case LeaveNotify: |
1178 | g_spaceBarIsPressed = false; |
1179 | |
1180 | // "mode" can be NotifyGrab or NotifyUngrab when middle mouse |
1181 | // button is pressed/released. We must not generated |
1182 | // MouseEnter/Leave events on those cases, only on NotifyNormal |
1183 | // (when mouse leaves/enter the X11 window). |
1184 | if (event.xcrossing.mode == NotifyNormal) { |
1185 | Event ev; |
1186 | ev.setType(event.type == EnterNotify ? Event::MouseEnter: |
1187 | Event::MouseLeave); |
1188 | ev.setModifiers(get_modifiers_from_x(event.xcrossing.state)); |
1189 | ev.setPosition(gfx::Point(event.xcrossing.x / m_scale, |
1190 | event.xcrossing.y / m_scale)); |
1191 | queueEvent(ev); |
1192 | } |
1193 | break; |
1194 | |
1195 | case ClientMessage: |
1196 | // When the close button is pressed |
1197 | if (event.xclient.message_type == WM_PROTOCOLS && |
1198 | Atom(event.xclient.data.l[0]) == WM_DELETE_WINDOW) { |
1199 | Event ev; |
1200 | ev.setType(Event::CloseWindow); |
1201 | queueEvent(ev); |
1202 | } |
1203 | else if (event.xclient.message_type == XdndPosition) { |
1204 | auto sourceWindow = (::Window)event.xclient.data.l[0]; |
1205 | |
1206 | // TODO Ask to the library user if we can drop and the action |
1207 | // that will take place |
1208 | XEvent event2; |
1209 | memset(&event2, 0, sizeof(event2)); |
1210 | event2.xany.type = ClientMessage; |
1211 | event2.xclient.window = sourceWindow; |
1212 | event2.xclient.message_type = XdndStatus; |
1213 | event2.xclient.format = 32; |
1214 | event2.xclient.data.l[0] = m_window; |
1215 | // Bit 0 = this window accept the drop |
1216 | event2.xclient.data.l[1] = 1; |
1217 | event2.xclient.data.l[4] = XdndActionCopy; |
1218 | XSendEvent(m_display, sourceWindow, 0, 0, &event2); |
1219 | } |
1220 | else if (event.xclient.message_type == XdndDrop) { |
1221 | // Save the X11 window from where this XdndDrop message came |
1222 | // from, so then we can send a XdndFinished later. |
1223 | g_dndSource = (::Window)event.xclient.data.l[0]; |
1224 | |
1225 | // Ask for the XdndSelection, we're going to receive the |
1226 | // dropped items in the SelectionNotify. |
1227 | XConvertSelection(m_display, XdndSelection, |
1228 | URI_LIST, XdndSelection, |
1229 | m_window, CurrentTime); |
1230 | } |
1231 | break; |
1232 | |
1233 | case SelectionNotify: |
1234 | if (event.xselection.property == XdndSelection) { |
1235 | bool successful = false; |
1236 | Atom actual_type; |
1237 | int actual_format; |
1238 | unsigned long nitems; |
1239 | unsigned long bytes_after; |
1240 | char* prop = nullptr; |
1241 | int res = XGetWindowProperty( |
1242 | m_display, |
1243 | m_window, |
1244 | XdndSelection, |
1245 | 0, 256, |
1246 | False, URI_LIST, |
1247 | &actual_type, &actual_format, |
1248 | &nitems, &bytes_after, |
1249 | (unsigned char**)&prop); |
1250 | |
1251 | if (prop) { |
1252 | if (actual_type == URI_LIST) { |
1253 | std::vector<std::string> files; |
1254 | base::split_string(std::string(prop), files, "\n" ); |
1255 | for (auto it=files.begin(); it!=files.end(); ) { |
1256 | std::string f = decode_url(*it); |
1257 | if (f.empty()) |
1258 | it = files.erase(it); |
1259 | else { |
1260 | *it = f; |
1261 | ++it; |
1262 | } |
1263 | } |
1264 | |
1265 | if (!files.empty()) { |
1266 | os::Event ev; |
1267 | ev.setType(os::Event::DropFiles); |
1268 | ev.setFiles(files); |
1269 | queueEvent(ev); |
1270 | |
1271 | successful = true; |
1272 | } |
1273 | } |
1274 | |
1275 | XFree(prop); |
1276 | } |
1277 | |
1278 | ::Window root = XDefaultRootWindow(m_display); |
1279 | XEvent event2; |
1280 | memset(&event2, 0, sizeof(event2)); |
1281 | event2.xany.type = ClientMessage; |
1282 | event2.xclient.window = g_dndSource; |
1283 | event2.xclient.message_type = XdndFinished; |
1284 | event2.xclient.format = 32; |
1285 | event2.xclient.data.l[0] = m_window; |
1286 | // Set bit 0 when the drop operation was accepted. |
1287 | event2.xclient.data.l[1] = (successful ? 1: 0); |
1288 | event2.xclient.data.l[2] = 0; |
1289 | event2.xclient.data.l[3] = 0; |
1290 | XSendEvent(m_display, root, 0, 0, &event2); |
1291 | } |
1292 | break; |
1293 | |
1294 | case PropertyNotify: |
1295 | if (event.xproperty.atom == _NET_FRAME_EXTENTS) { |
1296 | getX11FrameExtents(); |
1297 | |
1298 | if (m_borderless && m_frameExtents != gfx::Border(0, 0, 0, 0)) { |
1299 | std::vector<unsigned long> data(4, 0); |
1300 | XChangeProperty( |
1301 | m_display, m_window, _NET_FRAME_EXTENTS, XA_CARDINAL, 32, |
1302 | PropModeReplace, (const unsigned char*)&data[0], data.size()); |
1303 | } |
1304 | } |
1305 | else if (event.xproperty.atom == _NET_WM_ALLOWED_ACTIONS) { |
1306 | // Set allowed actions (resize, maximize, etc.) |
1307 | if (!m_initializingActions) { |
1308 | m_initializingActions = true; |
1309 | setAllowedActions(); |
1310 | } |
1311 | } |
1312 | break; |
1313 | } |
1314 | } |
1315 | |
1316 | } // namespace os |
1317 | |