Line data Source code
1 : /**
2 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "XLPlatformLinuxXcbView.h"
24 : #include <X11/keysym.h>
25 :
26 : uint32_t _glfwKeySym2Unicode(unsigned int keysym);
27 :
28 : namespace STAPPLER_VERSIONIZED stappler::xenolith::platform {
29 :
30 : #if XL_X11_DEBUG
31 : #define XL_X11_LOG(...) log::debug("Wayland", __VA_ARGS__)
32 : #else
33 : #define XL_X11_LOG(...)
34 : #endif
35 :
36 0 : void XcbView::ReportError(int error) {
37 0 : switch (error) {
38 0 : case XCB_CONN_ERROR:
39 0 : stappler::log::error("XcbView", "XCB_CONN_ERROR: socket error, pipe error or other stream error");
40 0 : break;
41 0 : case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
42 0 : stappler::log::error("XcbView", "XCB_CONN_CLOSED_EXT_NOTSUPPORTED: extension is not supported");
43 0 : break;
44 0 : case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
45 0 : stappler::log::error("XcbView", "XCB_CONN_CLOSED_MEM_INSUFFICIENT: out of memory");
46 0 : break;
47 0 : case XCB_CONN_CLOSED_REQ_LEN_EXCEED:
48 0 : stappler::log::error("XcbView", "XCB_CONN_CLOSED_REQ_LEN_EXCEED: too large request");
49 0 : break;
50 0 : case XCB_CONN_CLOSED_PARSE_ERR:
51 0 : stappler::log::error("XcbView", "XCB_CONN_CLOSED_PARSE_ERR: error during parsing display string");
52 0 : break;
53 0 : case XCB_CONN_CLOSED_INVALID_SCREEN:
54 0 : stappler::log::error("XcbView", "XCB_CONN_CLOSED_INVALID_SCREEN: server does not have a screen matching the display");
55 0 : break;
56 0 : case XCB_CONN_CLOSED_FDPASSING_FAILED:
57 0 : stappler::log::error("XcbView", "XCB_CONN_CLOSED_FDPASSING_FAILED: fail to pass some FD");
58 0 : break;
59 : }
60 0 : }
61 :
62 25 : XcbView::XcbView(XcbLibrary *lib, ViewInterface *view, StringView name, StringView bundleId, URect rect) {
63 25 : _xcb = lib;
64 25 : _xkb = XkbLibrary::getInstance();
65 25 : _view = view;
66 : #if DEBUG
67 25 : auto d = getenv("DISPLAY");
68 25 : if (!d) {
69 0 : stappler::log::warn("XcbView-Info", "DISPLAY is not defined");
70 : }
71 : #endif
72 :
73 25 : auto connection = lib->acquireConnection();
74 :
75 25 : _connection = connection.connection;
76 :
77 25 : auto err = _xcb->xcb_connection_has_error(_connection);
78 25 : if (err != 0) {
79 0 : ReportError(err);
80 0 : return;
81 : }
82 :
83 25 : _defaultScreen = connection.screen;
84 :
85 25 : if (_xcb->hasRandr()) {
86 25 : auto ext = _xcb->xcb_get_extension_data(_connection, _xcb->xcb_randr_id);
87 :
88 25 : _randrEnabled = true;
89 25 : _randrFirstEvent = ext->first_event;
90 :
91 25 : _screenInfo = getScreenInfo();
92 25 : _rate = _screenInfo.primaryMode.rate;
93 : }
94 :
95 25 : if (_xkb && _xkb->hasX11() && _xcb->hasXkb()) {
96 25 : initXkb();
97 : }
98 :
99 25 : _socket = _xcb->xcb_get_file_descriptor(_connection); // assume it's non-blocking
100 :
101 25 : uint32_t mask = /*XCB_CW_BACK_PIXEL | */ XCB_CW_EVENT_MASK;
102 : uint32_t values[1];
103 : //values[0] = _defaultScreen->white_pixel;
104 25 : values[0] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION
105 : | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE
106 : | XCB_EVENT_MASK_VISIBILITY_CHANGE | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY
107 : | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_COLOR_MAP_CHANGE
108 : | XCB_EVENT_MASK_OWNER_GRAB_BUTTON;
109 :
110 : /* Ask for our window's Id */
111 25 : _window = _xcb->xcb_generate_id(_connection);
112 :
113 25 : _width = rect.width;
114 25 : _height = rect.height;
115 :
116 25 : _xcb->xcb_create_window(_connection,
117 : XCB_COPY_FROM_PARENT, // depth (same as root)
118 : _window, // window Id
119 25 : _defaultScreen->root, // parent window
120 25 : rect.x, rect.y, rect.width, rect.height,
121 : 0, // border_width
122 : XCB_WINDOW_CLASS_INPUT_OUTPUT, // class
123 25 : _defaultScreen->root_visual, // visual
124 : mask, values);
125 :
126 : xcb_intern_atom_cookie_t atomCookies[sizeof(s_atomRequests) / sizeof(XcbAtomRequest)];
127 :
128 25 : size_t i = 0;
129 325 : for (auto &it : s_atomRequests) {
130 300 : atomCookies[i] = _xcb->xcb_intern_atom(_connection, it.onlyIfExists ? 1 : 0, it.name.size(), it.name.data());
131 300 : ++i;
132 : }
133 :
134 25 : _xcb->xcb_flush(_connection);
135 :
136 25 : i = 0;
137 325 : for (auto &it : atomCookies) {
138 300 : auto reply = _xcb->xcb_intern_atom_reply(_connection, it, nullptr);
139 300 : if (reply) {
140 300 : _atoms[i] = reply->atom;
141 300 : free(reply);
142 : } else {
143 0 : _atoms[i] = 0;
144 : }
145 300 : ++i;
146 : }
147 :
148 25 : _wmClass.resize(name.size() + bundleId.size() + 1, char(0));
149 25 : memcpy(_wmClass.data(), name.data(), name.size());
150 25 : memcpy(_wmClass.data() + name.size() + 1, bundleId.data(), bundleId.size());
151 :
152 25 : _xcb->xcb_change_property( _connection, XCB_PROP_MODE_REPLACE, _window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, name.size(), name.data());
153 25 : _xcb->xcb_change_property( _connection, XCB_PROP_MODE_REPLACE, _window, XCB_ATOM_WM_ICON_NAME, XCB_ATOM_STRING, 8, name.size(), name.data());
154 25 : if (_atoms[0]) {
155 25 : _xcb->xcb_change_property( _connection, XCB_PROP_MODE_REPLACE, _window, _atoms[0], 4, 32, 1, &_atoms[1] );
156 : }
157 25 : _xcb->xcb_change_property( _connection, XCB_PROP_MODE_REPLACE, _window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, _wmClass.size(), _wmClass.data());
158 :
159 25 : updateKeysymMapping();
160 :
161 25 : _xcb->xcb_flush(_connection);
162 0 : }
163 :
164 50 : XcbView::~XcbView() {
165 25 : _defaultScreen = nullptr;
166 25 : if (_xkbKeymap) {
167 25 : _xkb->xkb_keymap_unref(_xkbKeymap);
168 25 : _xkbKeymap = nullptr;
169 : }
170 25 : if (_xkbState) {
171 25 : _xkb->xkb_state_unref(_xkbState);
172 25 : _xkbState = nullptr;
173 : }
174 25 : if (_xkbCompose) {
175 25 : _xkb->xkb_compose_state_unref(_xkbCompose);
176 25 : _xkbCompose = nullptr;
177 : }
178 25 : if (_keysyms) {
179 25 : _xcb->xcb_key_symbols_free(_keysyms);
180 25 : _keysyms = nullptr;
181 : }
182 25 : if (_connection) {
183 25 : _xcb->xcb_disconnect(_connection);
184 25 : _connection = nullptr;
185 : }
186 50 : }
187 :
188 0 : bool XcbView::valid() const {
189 0 : return _xcb->xcb_connection_has_error(_connection) == 0;
190 : }
191 :
192 2123 : static core::InputModifier getModifiers(uint32_t mask) {
193 2123 : core::InputModifier ret = core::InputModifier::None;
194 2123 : core::InputModifier *mod, mods[] = { core::InputModifier::Shift, core::InputModifier::CapsLock, core::InputModifier::Ctrl,
195 : core::InputModifier::Alt, core::InputModifier::NumLock, core::InputModifier::Mod3, core::InputModifier::Mod4,
196 : core::InputModifier::Mod5, core::InputModifier::Button1, core::InputModifier::Button2, core::InputModifier::Button3,
197 : core::InputModifier::Button4, core::InputModifier::Button5, core::InputModifier::LayoutAlternative };
198 2187 : for (mod = mods; mask; mask >>= 1, mod++) {
199 64 : if (mask & 1) {
200 6 : ret |= *mod;
201 : }
202 : }
203 2123 : return ret;
204 : }
205 :
206 10 : static core::InputMouseButton getButton(xcb_button_t btn) {
207 10 : return core::InputMouseButton(btn);
208 : }
209 :
210 13013 : bool XcbView::poll(bool frameReady) {
211 13013 : bool ret = true;
212 13013 : bool deprecateSwapchain = false;
213 :
214 13013 : Vector<core::InputEventData> inputEvents;
215 :
216 17310 : auto dispatchEvents = [&, this] {
217 15149 : if (!inputEvents.empty()) {
218 2161 : _view->handleInputEvents(move(inputEvents));
219 : }
220 15149 : inputEvents.clear();
221 28162 : };
222 :
223 13013 : xcb_timestamp_t lastInputTime = 0;
224 : xcb_generic_event_t *e;
225 15882 : while ((e = _xcb->xcb_poll_for_event(_connection))) {
226 2869 : auto et = e->response_type & 0x7f;
227 2869 : switch (et) {
228 470 : case XCB_PROPERTY_NOTIFY: {
229 : /* Ignore */
230 : //xcb_property_notify_event_t *ev = (xcb_property_notify_event_t*) e;
231 : //printf("XCB_PROPERTY_NOTIFY: %d of property %d\n", ev->window, ev->atom);
232 470 : break;
233 : }
234 0 : case XCB_SELECTION_NOTIFY: {
235 : xcb_get_property_reply_t *reply;
236 0 : xcb_selection_notify_event_t *sel_event = (xcb_selection_notify_event_t*) e;
237 :
238 : /* Since we have only a single 'thing' we request, we do not
239 : * have to inspect the values of the event.
240 : */
241 0 : if (sel_event->property == _atoms[toInt(XcbAtomIndex::XENOLITH_CLIPBOARD)]) {
242 0 : reply = _xcb->xcb_get_property_reply(_connection,
243 0 : _xcb->xcb_get_property(_connection, 1, _window,
244 : _atoms[toInt(XcbAtomIndex::XENOLITH_CLIPBOARD)],
245 : _atoms[toInt(XcbAtomIndex::STRING)], 0, 300), NULL);
246 0 : notifyClipboard(BytesView((const uint8_t *)_xcb->xcb_get_property_value(reply), _xcb->xcb_get_property_value_length(reply)));
247 0 : free(reply);
248 : }
249 0 : break;
250 : }
251 0 : case XCB_SELECTION_REQUEST: {
252 0 : handleSelectionRequest((xcb_selection_request_event_t *) e);
253 0 : break;
254 : }
255 25 : case XCB_EXPOSE: {
256 : // xcb_expose_event_t *ev = (xcb_expose_event_t*) e;
257 : // printf("XCB_EXPOSE: Window %d exposed. Region to be redrawn at location (%d,%d), with dimension (%d,%d)\n",
258 : // ev->window, ev->x, ev->y, ev->width, ev->height);
259 25 : break;
260 : }
261 5 : case XCB_BUTTON_PRESS:
262 5 : if (_window == ((xcb_button_press_event_t *)e)->event) {
263 5 : auto ev = (xcb_button_press_event_t *)e;
264 5 : if (lastInputTime != ev->time) {
265 5 : dispatchEvents();
266 5 : lastInputTime = ev->time;
267 : }
268 :
269 5 : auto ext = _view->getExtent();
270 5 : auto mod = getModifiers(ev->state);
271 5 : auto btn = getButton(ev->detail);
272 :
273 5 : core::InputEventData event({
274 5 : ev->detail,
275 : core::InputEventName::Begin,
276 : btn,
277 : mod,
278 5 : float(ev->event_x),
279 5 : float(ext.height - ev->event_y)
280 5 : });
281 :
282 : switch (btn) {
283 2 : case core::InputMouseButton::MouseScrollUp:
284 2 : event.event = core::InputEventName::Scroll;
285 2 : event.point.valueX = 0.0f; event.point.valueY = 10.0f;
286 2 : break;
287 3 : case core::InputMouseButton::MouseScrollDown:
288 3 : event.event = core::InputEventName::Scroll;
289 3 : event.point.valueX = 0.0f; event.point.valueY = -10.0f;
290 3 : break;
291 0 : case core::InputMouseButton::MouseScrollLeft:
292 0 : event.event = core::InputEventName::Scroll;
293 0 : event.point.valueX = 10.0f; event.point.valueY = 0.0f;
294 0 : break;
295 0 : case core::InputMouseButton::MouseScrollRight:
296 0 : event.event = core::InputEventName::Scroll;
297 0 : event.point.valueX = -10.0f; event.point.valueY = 0.0f;
298 0 : break;
299 0 : default:
300 0 : break;
301 : }
302 :
303 5 : inputEvents.emplace_back(event);
304 : }
305 5 : break;
306 5 : case XCB_BUTTON_RELEASE:
307 5 : if (_window == ((xcb_button_release_event_t *)e)->event) {
308 5 : auto ev = (xcb_button_release_event_t *)e;
309 5 : if (lastInputTime != ev->time) {
310 3 : dispatchEvents();
311 3 : lastInputTime = ev->time;
312 : }
313 :
314 5 : auto ext = _view->getExtent();
315 5 : auto mod = getModifiers(ev->state);
316 5 : auto btn = getButton(ev->detail);
317 :
318 5 : core::InputEventData event({
319 5 : ev->detail,
320 : core::InputEventName::End,
321 : btn,
322 : mod,
323 5 : float(ev->event_x),
324 5 : float(ext.height - ev->event_y)
325 5 : });
326 :
327 : switch (btn) {
328 5 : case core::InputMouseButton::MouseScrollUp:
329 : case core::InputMouseButton::MouseScrollDown:
330 : case core::InputMouseButton::MouseScrollLeft:
331 : case core::InputMouseButton::MouseScrollRight:
332 5 : break;
333 0 : default:
334 0 : inputEvents.emplace_back(event);
335 0 : break;
336 : }
337 : }
338 5 : break;
339 2111 : case XCB_MOTION_NOTIFY:
340 2111 : if (_window == ((xcb_motion_notify_event_t *)e)->event) {
341 2111 : auto ev = (xcb_motion_notify_event_t *)e;
342 2111 : if (lastInputTime != ev->time) {
343 2100 : dispatchEvents();
344 2100 : lastInputTime = ev->time;
345 : }
346 :
347 2111 : auto ext = _view->getExtent();
348 2111 : auto mod = getModifiers(ev->state);
349 :
350 2111 : core::InputEventData event({
351 : maxOf<uint32_t>(),
352 : core::InputEventName::MouseMove,
353 : core::InputMouseButton::None,
354 : mod,
355 2111 : float(ev->event_x),
356 2111 : float(ext.height - ev->event_y)
357 2111 : });
358 :
359 2111 : inputEvents.emplace_back(event);
360 : }
361 2111 : break;
362 15 : case XCB_ENTER_NOTIFY: {
363 15 : xcb_enter_notify_event_t *ev = (xcb_enter_notify_event_t*) e;
364 15 : if (lastInputTime != ev->time) {
365 15 : dispatchEvents();
366 15 : lastInputTime = ev->time;
367 : }
368 :
369 15 : auto ext = _view->getExtent();
370 15 : inputEvents.emplace_back(core::InputEventData::BoolEvent(core::InputEventName::PointerEnter, true,
371 15 : Vec2(float(ev->event_x), float(ext.height - ev->event_y))));
372 : // printf("Mouse entered window %d, at coordinates (%d,%d)\n", ev->event, ev->event_x, ev->event_y);
373 15 : break;
374 : }
375 11 : case XCB_LEAVE_NOTIFY: {
376 11 : xcb_leave_notify_event_t *ev = (xcb_leave_notify_event_t*) e;
377 11 : if (lastInputTime != ev->time) {
378 11 : dispatchEvents();
379 11 : lastInputTime = ev->time;
380 : }
381 :
382 11 : auto ext = _view->getExtent();
383 11 : inputEvents.emplace_back(core::InputEventData::BoolEvent(core::InputEventName::PointerEnter, false,
384 11 : Vec2(float(ev->event_x), float(ext.height - ev->event_y))));
385 : // printf("Mouse left window %d, at coordinates (%d,%d)\n", ev->event, ev->event_x, ev->event_y);
386 11 : break;
387 : }
388 25 : case XCB_FOCUS_IN: {
389 : // xcb_focus_in_event_t *ev = (xcb_focus_in_event_t*) e;
390 25 : inputEvents.emplace_back(core::InputEventData::BoolEvent(core::InputEventName::FocusGain, true));
391 25 : updateKeysymMapping();
392 25 : break;
393 : }
394 6 : case XCB_FOCUS_OUT: {
395 : // xcb_focus_in_event_t *ev = (xcb_focus_in_event_t*) e;
396 6 : inputEvents.emplace_back(core::InputEventData::BoolEvent(core::InputEventName::FocusGain, false));
397 6 : break;
398 : }
399 1 : case XCB_KEY_PRESS: {
400 1 : xcb_key_press_event_t *ev = (xcb_key_press_event_t*) e;
401 1 : if (lastInputTime != ev->time) {
402 1 : dispatchEvents();
403 1 : lastInputTime = ev->time;
404 : }
405 :
406 1 : auto mod = getModifiers(ev->state);
407 1 : auto ext = _view->getExtent();
408 :
409 : // in case of key autorepeat, ev->time will match
410 : // just replace event name from previous InputEventName::KeyReleased to InputEventName::KeyRepeated
411 1 : if (!inputEvents.empty() && inputEvents.back().event == core::InputEventName::KeyReleased) {
412 0 : auto &iev = inputEvents.back();
413 0 : if (iev.id == ev->time && iev.modifiers == mod && iev.x == float(ev->event_x)
414 0 : && iev.y == float(ext.height - ev->event_y)
415 0 : && iev.key.keysym == getKeysym(ev->detail, ev->state, false)) {
416 0 : iev.event = core::InputEventName::KeyRepeated;
417 0 : break;
418 : }
419 : }
420 :
421 1 : core::InputEventData event({
422 1 : ev->time,
423 : core::InputEventName::KeyPressed,
424 : core::InputMouseButton::None,
425 : mod,
426 1 : float(ev->event_x),
427 1 : float(ext.height - ev->event_y)
428 1 : });
429 :
430 1 : if (_xkb) {
431 1 : event.key.keycode = getKeyCode(ev->detail);
432 1 : event.key.compose = core::InputKeyComposeState::Nothing;
433 1 : event.key.keysym = getKeysym(ev->detail, ev->state, false);
434 1 : if (_view->isInputEnabled()) {
435 0 : const auto keysym = composeSymbol(_xkb->xkb_state_key_get_one_sym(_xkbState, ev->detail), event.key.compose);
436 0 : const uint32_t cp = _xkb->xkb_keysym_to_utf32(keysym);
437 0 : if (cp != 0 && keysym != XKB_KEY_NoSymbol) {
438 0 : event.key.keychar = cp;
439 : } else {
440 0 : event.key.keychar = 0;
441 : }
442 : } else {
443 1 : event.key.keychar = 0;
444 : }
445 : } else {
446 0 : auto sym = getKeysym(ev->detail, ev->state, false); // state-inpependent keysym
447 0 : event.key.keycode = getKeysymCode(sym);
448 0 : event.key.compose = core::InputKeyComposeState::Nothing;
449 0 : event.key.keysym = sym;
450 0 : if (_view->isInputEnabled()) {
451 0 : event.key.keychar = _glfwKeySym2Unicode(getKeysym(ev->detail, ev->state)); // use state-dependent keysym
452 : } else {
453 0 : event.key.keychar = 0;
454 : }
455 : }
456 :
457 1 : inputEvents.emplace_back(event);
458 :
459 1 : String str;
460 1 : unicode::utf8Encode(str, event.key.keychar);
461 : XL_X11_LOG("Key pressed in window ", ev->event, " (", (int)ev->time, ") ", event.key.keysym,
462 : " '", str, "' ", uint32_t(event.key.keychar));
463 1 : break;
464 1 : }
465 1 : case XCB_KEY_RELEASE: {
466 1 : xcb_key_release_event_t *ev = (xcb_key_release_event_t*) e;
467 1 : if (lastInputTime != ev->time) {
468 1 : dispatchEvents();
469 1 : lastInputTime = ev->time;
470 : }
471 :
472 1 : auto mod = getModifiers(ev->state);
473 1 : auto ext = _view->getExtent();
474 :
475 1 : core::InputEventData event({
476 1 : ev->time,
477 : core::InputEventName::KeyReleased,
478 : core::InputMouseButton::None,
479 : mod,
480 1 : float(ev->event_x),
481 1 : float(ext.height - ev->event_y)
482 1 : });
483 :
484 1 : if (_xkb) {
485 1 : event.key.keycode = getKeyCode(ev->detail);
486 1 : event.key.compose = core::InputKeyComposeState::Nothing;
487 1 : event.key.keysym = getKeysym(ev->detail, ev->state, false);
488 1 : if (_view->isInputEnabled()) {
489 0 : event.key.keychar = _xkb->xkb_state_key_get_utf32(_xkbState, ev->detail);
490 : } else {
491 1 : event.key.keychar = 0;
492 : }
493 : } else {
494 0 : auto sym = getKeysym(ev->detail, ev->state, false); // state-inpependent keysym
495 0 : event.key.keycode = getKeysymCode(sym);
496 0 : event.key.compose = core::InputKeyComposeState::Nothing;
497 0 : event.key.keysym = sym;
498 0 : if (_view->isInputEnabled()) {
499 0 : event.key.keychar = _glfwKeySym2Unicode(getKeysym(ev->detail, ev->state)); // use state-dependent keysym
500 : } else {
501 0 : event.key.keychar = 0;
502 : }
503 : }
504 :
505 1 : inputEvents.emplace_back(event);
506 :
507 1 : String str;
508 1 : unicode::utf8Encode(str, event.key.keychar);
509 : XL_X11_LOG("Key released in window ", ev->event, " (", (int)ev->time, ") ", event.key.keysym,
510 : " '", str, "' ", uint32_t(event.key.keychar));
511 1 : break;
512 1 : }
513 25 : case XCB_VISIBILITY_NOTIFY: {
514 25 : SPUNUSED xcb_visibility_notify_event_t *ev = (xcb_visibility_notify_event_t*) e;
515 : XL_X11_LOG("XCB_VISIBILITY_NOTIFY: ", ev->window);
516 25 : break;
517 : }
518 25 : case XCB_MAP_NOTIFY: {
519 25 : SPUNUSED xcb_map_notify_event_t *ev = (xcb_map_notify_event_t*) e;
520 : XL_X11_LOG("XCB_MAP_NOTIFY: ", ev->event);
521 25 : break;
522 : }
523 25 : case XCB_REPARENT_NOTIFY: {
524 : //xcb_reparent_notify_event_t *ev = (xcb_reparent_notify_event_t*) e;
525 : //XL_X11_LOG("XCB_REPARENT_NOTIFY: %d %d to %d\n", ev->event, ev->window, ev->parent);
526 25 : break;
527 : }
528 50 : case XCB_CONFIGURE_NOTIFY: {
529 50 : xcb_configure_notify_event_t *ev = (xcb_configure_notify_event_t*) e;
530 : // printf("XCB_CONFIGURE_NOTIFY: %d (%d) rect:%d,%d,%d,%d border:%d override:%d\n", ev->event, ev->window,
531 : // ev->x, ev->y, ev->width, ev->height, uint32_t(ev->border_width), uint32_t(ev->override_redirect));
532 50 : if (ev->width != _width || ev->height != _height) {
533 0 : _width = ev->width;
534 0 : _height = ev->height;
535 0 : deprecateSwapchain = true;
536 : }
537 50 : break;
538 : }
539 0 : case XCB_CLIENT_MESSAGE: {
540 0 : xcb_client_message_event_t *ev = (xcb_client_message_event_t*) e;
541 : XL_X11_LOG("XCB_CLIENT_MESSAGE: ", ev->window, " of type ", ev->type);
542 0 : if (ev->type == _atoms[0] && ev->data.data32[0] == _atoms[1]) {
543 0 : ret = false;
544 : }
545 0 : break;
546 : }
547 0 : case XCB_MAPPING_NOTIFY: {
548 0 : xcb_mapping_notify_event_t *ev = (xcb_mapping_notify_event_t*) e;
549 0 : if (_keysyms) {
550 0 : _xcb->xcb_refresh_keyboard_mapping(_keysyms, ev);
551 : }
552 : XL_X11_LOG("XCB_MAPPING_NOTIFY: ", (int) ev->request, " ", (int) ev->first_keycode, " ", (int) ev->count);
553 0 : break;
554 : }
555 0 : case XCB_COLORMAP_NOTIFY: {
556 : XL_X11_LOG("XCB_COLORMAP_NOTIFY: ", (int) ev->request);
557 0 : printf("XCB_PROPERTY_NOTIFY\n");
558 0 : break;
559 : }
560 69 : default:
561 69 : if (et == _xkbFirstEvent) {
562 69 : switch (e->pad0) {
563 0 : case XCB_XKB_NEW_KEYBOARD_NOTIFY:
564 0 : initXkb();
565 0 : break;
566 0 : case XCB_XKB_MAP_NOTIFY:
567 0 : updateXkbMapping();
568 0 : break;
569 69 : case XCB_XKB_STATE_NOTIFY: {
570 69 : xcb_xkb_state_notify_event_t *ev = (xcb_xkb_state_notify_event_t*)e;
571 69 : _xkb->xkb_state_update_mask(_xkbState, ev->baseMods, ev->latchedMods, ev->lockedMods,
572 69 : ev->baseGroup, ev->latchedGroup, ev->lockedGroup);
573 69 : break;
574 : }
575 : }
576 0 : } else if (et == _randrFirstEvent) {
577 0 : switch (e->pad0) {
578 0 : case XCB_RANDR_SCREEN_CHANGE_NOTIFY:
579 0 : _screenInfo = getScreenInfo();
580 0 : break;
581 0 : default:
582 0 : break;
583 : }
584 : } else {
585 : /* Unknown event type, ignore it */
586 : XL_X11_LOG("Unknown event: ", et);
587 : }
588 69 : break;
589 : }
590 :
591 : /* Free the Generic Event */
592 2869 : free(e);
593 : }
594 :
595 13013 : dispatchEvents();
596 :
597 13013 : if (deprecateSwapchain) {
598 0 : _view->deprecateSwapchain();
599 : }
600 :
601 13013 : return ret;
602 13013 : }
603 :
604 25 : uint64_t XcbView::getScreenFrameInterval() const {
605 25 : return 1'000'000 / _rate;
606 : }
607 :
608 25 : void XcbView::mapWindow() {
609 25 : _xcb->xcb_map_window(_connection, _window);
610 25 : _xcb->xcb_flush(_connection);
611 25 : }
612 :
613 0 : void XcbView::readFromClipboard(Function<void(BytesView, StringView)> &&cb, Ref *ref) {
614 0 : auto cookie = _xcb->xcb_get_selection_owner(_connection, _atoms[toInt(XcbAtomIndex::CLIPBOARD)]);
615 0 : auto reply = _xcb->xcb_get_selection_owner_reply(_connection, cookie, nullptr);
616 :
617 0 : if (reply->owner == _window) {
618 0 : cb(_clipboardSelection, StringView("text/plain"));
619 : } else {
620 0 : _xcb->xcb_convert_selection(_connection, _window,
621 : _atoms[toInt(XcbAtomIndex::CLIPBOARD)],
622 : _atoms[toInt(XcbAtomIndex::STRING)],
623 : _atoms[toInt(XcbAtomIndex::XENOLITH_CLIPBOARD)], XCB_CURRENT_TIME);
624 0 : _xcb->xcb_flush(_connection);
625 :
626 0 : if (_clipboardCallback) {
627 0 : _clipboardCallback(BytesView(), StringView());
628 : }
629 :
630 0 : _clipboardCallback = move(cb);
631 0 : _clipboardTarget = ref;
632 : }
633 0 : }
634 :
635 0 : void XcbView::writeToClipboard(BytesView data, StringView contentType) {
636 0 : _clipboardSelection = data.bytes<Interface>();
637 :
638 0 : _xcb->xcb_set_selection_owner(_connection, _window, _atoms[toInt(XcbAtomIndex::CLIPBOARD)], XCB_CURRENT_TIME);
639 :
640 0 : auto cookie = _xcb->xcb_get_selection_owner(_connection, _atoms[toInt(XcbAtomIndex::CLIPBOARD)]);
641 0 : auto reply = _xcb->xcb_get_selection_owner_reply(_connection, cookie, nullptr);
642 0 : if (reply->owner != _window) {
643 0 : log::error("XcbView", "Fail to set selection owner");
644 : }
645 0 : ::free(reply);
646 0 : }
647 :
648 25 : XcbView::ScreenInfoData XcbView::getScreenInfo() const {
649 25 : if (!_xcb->hasRandr()) {
650 0 : return ScreenInfoData();
651 : }
652 :
653 : // submit our version to X11
654 25 : auto versionCookie = _xcb->xcb_randr_query_version( _connection, XcbLibrary::RANDR_MAJOR_VERSION, XcbLibrary::RANDR_MINOR_VERSION);
655 25 : if (auto versionReply = _xcb->xcb_randr_query_version_reply( _connection, versionCookie, nullptr)) {
656 25 : if (versionReply->major_version != XcbLibrary::RANDR_MAJOR_VERSION) {
657 0 : ::free(versionReply);
658 0 : return ScreenInfoData();
659 : }
660 :
661 25 : ::free(versionReply);
662 : } else {
663 0 : return ScreenInfoData();
664 : }
665 :
666 25 : ScreenInfoData ret;
667 :
668 : // spawn requests
669 25 : auto screenResCurrentCookie = _xcb->xcb_randr_get_screen_resources_current_unchecked(_connection, _defaultScreen->root);
670 25 : auto outputPrimaryCookie = _xcb->xcb_randr_get_output_primary_unchecked(_connection, _defaultScreen->root);
671 25 : auto screenResCookie = _xcb->xcb_randr_get_screen_resources_unchecked(_connection, _defaultScreen->root);
672 25 : auto screenInfoCookie = _xcb->xcb_randr_get_screen_info_unchecked(_connection, _defaultScreen->root);
673 : xcb_randr_get_output_info_cookie_t outputInfoCookie;
674 :
675 25 : Vector<Pair<xcb_randr_crtc_t, xcb_randr_get_crtc_info_cookie_t>> crtcCookies;
676 :
677 : do {
678 : // process current modes
679 25 : auto curReply = _xcb->xcb_randr_get_screen_resources_current_reply(_connection, screenResCurrentCookie, nullptr);
680 25 : auto curModes = _xcb->xcb_randr_get_screen_resources_current_modes(curReply);
681 25 : auto curNmodes = _xcb->xcb_randr_get_screen_resources_current_modes_length(curReply);
682 25 : uint8_t *names = _xcb->xcb_randr_get_screen_resources_current_names(curReply);
683 :
684 825 : while (curNmodes > 0) {
685 800 : double vTotal = curModes->vtotal;
686 :
687 800 : if (curModes->mode_flags & XCB_RANDR_MODE_FLAG_DOUBLE_SCAN) {
688 : /* doublescan doubles the number of lines */
689 0 : vTotal *= 2;
690 : }
691 :
692 800 : if (curModes->mode_flags & XCB_RANDR_MODE_FLAG_INTERLACE) {
693 : /* interlace splits the frame into two fields */
694 : /* the field rate is what is typically reported by monitors */
695 0 : vTotal /= 2;
696 : }
697 :
698 800 : if (curModes->htotal && vTotal) {
699 800 : auto rate = uint16_t(floor(double(curModes->dot_clock) / (double(curModes->htotal) * double(vTotal))));
700 1600 : ret.currentModeInfo.emplace_back(ModeInfo{curModes->id, curModes->width, curModes->height, rate,
701 800 : String((const char *)names, curModes->name_len)});
702 : }
703 :
704 800 : names += curModes->name_len;
705 800 : ++ curModes;
706 800 : -- curNmodes;
707 : }
708 :
709 25 : auto outputs = _xcb->xcb_randr_get_screen_resources_current_outputs(curReply);
710 25 : auto noutputs = _xcb->xcb_randr_get_screen_resources_current_outputs_length(curReply);
711 :
712 275 : while (noutputs > 0) {
713 250 : ret.currentOutputs.emplace_back(*outputs);
714 250 : ++ outputs;
715 250 : -- noutputs;
716 : }
717 :
718 25 : ret.config = curReply->config_timestamp;
719 :
720 25 : auto crtcs = _xcb->xcb_randr_get_screen_resources_current_crtcs(curReply);
721 25 : auto ncrtcs = _xcb->xcb_randr_get_screen_resources_current_crtcs_length(curReply);
722 :
723 25 : crtcCookies.reserve(ncrtcs);
724 :
725 225 : while (ncrtcs > 0) {
726 200 : ret.currentCrtcs.emplace_back(*crtcs);
727 :
728 200 : crtcCookies.emplace_back(*crtcs, _xcb->xcb_randr_get_crtc_info_unchecked(_connection, *crtcs, ret.config));
729 :
730 200 : ++ crtcs;
731 200 : -- ncrtcs;
732 : }
733 :
734 25 : ::free(curReply);
735 : } while (0);
736 :
737 : do {
738 25 : auto reply = _xcb->xcb_randr_get_output_primary_reply(_connection, outputPrimaryCookie, nullptr);
739 25 : ret.primaryOutput.output = reply->output;
740 25 : ::free(reply);
741 :
742 25 : outputInfoCookie = _xcb->xcb_randr_get_output_info_unchecked(_connection, ret.primaryOutput.output, ret.config);
743 : } while (0);
744 :
745 : // process screen info
746 : do {
747 25 : auto reply = _xcb->xcb_randr_get_screen_info_reply(_connection, screenInfoCookie, nullptr);
748 25 : auto sizes = size_t(_xcb->xcb_randr_get_screen_info_sizes_length(reply));
749 :
750 25 : Vector<Vector<uint16_t>> ratesVec;
751 25 : Vector<uint16_t> tmp;
752 :
753 25 : auto ratesIt = _xcb->xcb_randr_get_screen_info_rates_iterator(reply);
754 475 : while (ratesIt.rem > 0) {
755 450 : auto nRates = _xcb->xcb_randr_refresh_rates_rates_length(ratesIt.data);
756 450 : auto rates = _xcb->xcb_randr_refresh_rates_rates(ratesIt.data);
757 450 : auto tmpNRates = nRates;
758 :
759 1275 : while (tmpNRates) {
760 825 : tmp.emplace_back(*rates);
761 825 : ++ rates;
762 825 : -- tmpNRates;
763 : }
764 :
765 450 : _xcb->xcb_randr_refresh_rates_next(&ratesIt);
766 450 : ratesIt.rem += 1 - nRates; // bypass rem bug
767 :
768 450 : ratesVec.emplace_back(move(tmp));
769 450 : tmp.clear();
770 : }
771 :
772 25 : auto sizesData = _xcb->xcb_randr_get_screen_info_sizes(reply);
773 475 : for (size_t i = 0; i < sizes; ++ i) {
774 450 : auto &it = sizesData[i];
775 450 : ScreenInfo info { it.width, it.height, it.mwidth, it.mheight };
776 :
777 450 : if (ratesVec.size() > i) {
778 450 : info.rates = ratesVec[i];
779 0 : } else if (ratesVec.size() == 1) {
780 0 : info.rates = ratesVec[0];
781 : } else {
782 0 : info.rates = Vector<uint16_t>{ _rate };
783 : }
784 :
785 450 : ret.screenInfo.emplace_back(move(info));
786 450 : }
787 :
788 25 : ::free(reply);
789 25 : } while (0);
790 :
791 : do {
792 25 : auto modesReply = _xcb->xcb_randr_get_screen_resources_reply(_connection, screenResCookie, nullptr);
793 25 : auto modes = _xcb->xcb_randr_get_screen_resources_modes(modesReply);
794 25 : auto nmodes = _xcb->xcb_randr_get_screen_resources_modes_length(modesReply);
795 :
796 825 : while (nmodes > 0) {
797 800 : double vTotal = modes->vtotal;
798 :
799 800 : if (modes->mode_flags & XCB_RANDR_MODE_FLAG_DOUBLE_SCAN) {
800 : /* doublescan doubles the number of lines */
801 0 : vTotal *= 2;
802 : }
803 :
804 800 : if (modes->mode_flags & XCB_RANDR_MODE_FLAG_INTERLACE) {
805 : /* interlace splits the frame into two fields */
806 : /* the field rate is what is typically reported by monitors */
807 0 : vTotal /= 2;
808 : }
809 :
810 800 : if (modes->htotal && vTotal) {
811 800 : auto rate = uint16_t(floor(double(modes->dot_clock) / (double(modes->htotal) * double(vTotal))));
812 800 : ret.modeInfo.emplace_back(ModeInfo{modes->id, modes->width, modes->height, rate});
813 : }
814 :
815 800 : ++ modes;
816 800 : -- nmodes;
817 : }
818 :
819 25 : ::free(modesReply);
820 : } while (0);
821 :
822 : do {
823 25 : auto reply = _xcb->xcb_randr_get_output_info_reply(_connection, outputInfoCookie, nullptr);
824 25 : auto modes = _xcb->xcb_randr_get_output_info_modes(reply);
825 25 : auto nmodes = _xcb->xcb_randr_get_output_info_modes_length(reply);
826 :
827 700 : while (nmodes > 0) {
828 675 : ret.primaryOutput.modes.emplace_back(*modes);
829 :
830 675 : ++ modes;
831 675 : -- nmodes;
832 : }
833 :
834 25 : auto name = _xcb->xcb_randr_get_output_info_name(reply);
835 25 : auto nameLen = _xcb->xcb_randr_get_output_info_name_length(reply);
836 :
837 25 : ret.primaryOutput.crtc = reply->crtc;
838 25 : ret.primaryOutput.name = String((const char *)name, nameLen);
839 :
840 25 : ::free(reply);
841 : } while (0);
842 :
843 225 : for (auto &crtcCookie : crtcCookies) {
844 200 : auto reply = _xcb->xcb_randr_get_crtc_info_reply(_connection, crtcCookie.second, nullptr);
845 :
846 200 : Vector<xcb_randr_output_t> outputs;
847 200 : Vector<xcb_randr_output_t> possible;
848 :
849 200 : auto outputsPtr = _xcb->xcb_randr_get_crtc_info_outputs(reply);
850 200 : auto noutputs = _xcb->xcb_randr_get_crtc_info_outputs_length(reply);
851 :
852 200 : outputs.reserve(noutputs);
853 :
854 250 : while (noutputs) {
855 50 : outputs.emplace_back(*outputsPtr);
856 50 : ++ outputsPtr;
857 50 : -- noutputs;
858 : }
859 :
860 200 : auto possiblePtr = _xcb->xcb_randr_get_crtc_info_possible(reply);
861 200 : auto npossible = _xcb->xcb_randr_get_crtc_info_possible_length(reply);
862 :
863 200 : possible.reserve(npossible);
864 :
865 1200 : while (npossible) {
866 1000 : possible.emplace_back(*possiblePtr);
867 1000 : ++ possiblePtr;
868 1000 : -- npossible;
869 : }
870 :
871 400 : ret.crtcInfo.emplace_back(CrtcInfo{
872 200 : crtcCookie.first, reply->x, reply->y, reply->width, reply->height, reply->mode, reply->rotation, reply->rotations,
873 400 : move(outputs), move(possible)
874 : });
875 :
876 200 : ::free(reply);
877 200 : }
878 :
879 25 : for (auto &it : ret.crtcInfo) {
880 25 : if (it.crtc == ret.primaryOutput.crtc) {
881 25 : ret.primaryCrtc = it;
882 :
883 25 : for (auto &iit : ret.currentModeInfo) {
884 25 : if (iit.id == ret.primaryCrtc.mode) {
885 25 : ret.primaryMode = iit;
886 25 : break;
887 : }
888 : }
889 :
890 25 : break;
891 : }
892 : }
893 :
894 25 : return ret;
895 25 : }
896 :
897 6400 : static void look_for(uint16_t &mask, xcb_keycode_t *codes, xcb_keycode_t kc, int i) {
898 6400 : if (mask == 0 && codes) {
899 5050 : for (xcb_keycode_t *ktest = codes; *ktest; ktest++) {
900 2600 : if (*ktest == kc) {
901 150 : mask = (uint16_t) (1 << i);
902 150 : break;
903 : }
904 : }
905 : }
906 6400 : }
907 :
908 25 : void XcbView::initXkb() {
909 25 : uint16_t xkbMajorVersion = 0;
910 25 : uint16_t xkbMinorVersion = 0;
911 :
912 25 : if (!_xcbSetup) {
913 25 : if (_xkb->xkb_x11_setup_xkb_extension(_connection, XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION,
914 25 : XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, &xkbMajorVersion, &xkbMinorVersion, &_xkbFirstEvent, &_xkbFirstError) != 1) {
915 0 : return;
916 : }
917 : }
918 :
919 25 : _xcbSetup = true;
920 25 : _xkbDeviceId = _xkb->xkb_x11_get_core_keyboard_device_id(_connection);
921 :
922 : enum {
923 : required_events = (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_NOTIFY | XCB_XKB_EVENT_TYPE_STATE_NOTIFY),
924 :
925 : required_nkn_details = (XCB_XKB_NKN_DETAIL_KEYCODES),
926 :
927 : required_map_parts = (XCB_XKB_MAP_PART_KEY_TYPES | XCB_XKB_MAP_PART_KEY_SYMS | XCB_XKB_MAP_PART_MODIFIER_MAP
928 : | XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS | XCB_XKB_MAP_PART_KEY_ACTIONS
929 : | XCB_XKB_MAP_PART_VIRTUAL_MODS | XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP),
930 :
931 : required_state_details = (XCB_XKB_STATE_PART_MODIFIER_BASE | XCB_XKB_STATE_PART_MODIFIER_LATCH
932 : | XCB_XKB_STATE_PART_MODIFIER_LOCK | XCB_XKB_STATE_PART_GROUP_BASE
933 : | XCB_XKB_STATE_PART_GROUP_LATCH | XCB_XKB_STATE_PART_GROUP_LOCK),
934 : };
935 :
936 : static const xcb_xkb_select_events_details_t details = {
937 : .affectNewKeyboard = required_nkn_details,
938 : .newKeyboardDetails = required_nkn_details,
939 : .affectState = required_state_details,
940 : .stateDetails = required_state_details
941 : };
942 :
943 25 : _xcb->xcb_xkb_select_events(_connection, _xkbDeviceId, required_events, 0,
944 : required_events, required_map_parts, required_map_parts, &details);
945 :
946 25 : updateXkbMapping();
947 : }
948 :
949 25 : void XcbView::updateXkbMapping() {
950 25 : if (_xkbState) {
951 0 : _xkb->xkb_state_unref(_xkbState);
952 0 : _xkbState = nullptr;
953 : }
954 25 : if (_xkbKeymap) {
955 0 : _xkb->xkb_keymap_unref(_xkbKeymap);
956 0 : _xkbKeymap = nullptr;
957 : }
958 25 : if (_xkbCompose) {
959 0 : _xkb->xkb_compose_state_unref(_xkbCompose);
960 0 : _xkbCompose = nullptr;
961 : }
962 :
963 25 : _xkbKeymap = _xkb->xkb_x11_keymap_new_from_device(_xkb->getContext(), _connection, _xkbDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS);
964 25 : if (_xkbKeymap == nullptr) {
965 0 : fprintf( stderr, "Failed to get Keymap for current keyboard device.\n");
966 0 : return;
967 : }
968 :
969 25 : _xkbState = _xkb->xkb_x11_state_new_from_device(_xkbKeymap, _connection, _xkbDeviceId);
970 25 : if (_xkbState == nullptr) {
971 0 : fprintf( stderr, "Failed to get state object for current keyboard device.\n");
972 0 : return;
973 : }
974 :
975 25 : memset(_keycodes, 0, sizeof(core::InputKeyCode) * 256);
976 :
977 25 : _xkb->xkb_keymap_key_for_each(_xkbKeymap, [] (struct xkb_keymap *keymap, xkb_keycode_t key, void *data) {
978 6200 : ((XcbView *)data)->updateXkbKey(key);
979 6200 : }, this);
980 :
981 25 : auto locale = getenv("LC_ALL");
982 25 : if (!locale) { locale = getenv("LC_CTYPE"); }
983 25 : if (!locale) { locale = getenv("LANG"); }
984 :
985 25 : auto composeTable = _xkb->xkb_compose_table_new_from_locale(_xkb->getContext(),
986 : locale ? locale : "C", XKB_COMPOSE_COMPILE_NO_FLAGS);
987 25 : if (composeTable) {
988 25 : _xkbCompose = _xkb->xkb_compose_state_new(composeTable, XKB_COMPOSE_STATE_NO_FLAGS);
989 25 : _xkb->xkb_compose_table_unref(composeTable);
990 : }
991 : }
992 :
993 50 : void XcbView::updateKeysymMapping() {
994 50 : if (_keysyms) {
995 25 : _xcb->xcb_key_symbols_free(_keysyms);
996 : }
997 :
998 50 : if (_xcb->hasKeysyms()) {
999 50 : _keysyms = _xcb->xcb_key_symbols_alloc( _connection );
1000 : }
1001 :
1002 50 : if (!_keysyms) {
1003 0 : return;
1004 : }
1005 :
1006 50 : auto modifierCookie = _xcb->xcb_get_modifier_mapping_unchecked( _connection );
1007 :
1008 : xcb_get_keyboard_mapping_cookie_t mappingCookie;
1009 50 : const xcb_setup_t* setup = _xcb->xcb_get_setup(_connection);
1010 :
1011 50 : if (!_xkb) {
1012 0 : mappingCookie = _xcb->xcb_get_keyboard_mapping(_connection, setup->min_keycode, setup->max_keycode - setup->min_keycode + 1);
1013 : }
1014 :
1015 50 : auto numlockcodes = _xcb->xcb_key_symbols_get_keycode( _keysyms, XK_Num_Lock );
1016 50 : auto shiftlockcodes = _xcb->xcb_key_symbols_get_keycode( _keysyms, XK_Shift_Lock );
1017 50 : auto capslockcodes = _xcb->xcb_key_symbols_get_keycode( _keysyms, XK_Caps_Lock );
1018 50 : auto modeswitchcodes = _xcb->xcb_key_symbols_get_keycode( _keysyms, XK_Mode_switch );
1019 :
1020 50 : auto modmap_r = _xcb->xcb_get_modifier_mapping_reply( _connection, modifierCookie, nullptr );
1021 50 : if (!modmap_r) {
1022 0 : return;
1023 : }
1024 :
1025 50 : xcb_keycode_t *modmap = _xcb->xcb_get_modifier_mapping_keycodes( modmap_r );
1026 :
1027 50 : _numlock = 0;
1028 50 : _shiftlock = 0;
1029 50 : _capslock = 0;
1030 50 : _modeswitch = 0;
1031 :
1032 450 : for (int i = 0; i < 8; i++) {
1033 2000 : for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
1034 1600 : xcb_keycode_t kc = modmap[i * modmap_r->keycodes_per_modifier + j];
1035 1600 : look_for(_numlock, numlockcodes, kc, i);
1036 1600 : look_for(_shiftlock, shiftlockcodes, kc, i);
1037 1600 : look_for(_capslock, capslockcodes, kc, i);
1038 1600 : look_for(_modeswitch, modeswitchcodes, kc, i);
1039 : }
1040 : }
1041 :
1042 50 : ::free(modmap_r);
1043 :
1044 50 : ::free(numlockcodes);
1045 50 : ::free(shiftlockcodes);
1046 50 : ::free(capslockcodes);
1047 50 : ::free(modeswitchcodes);
1048 :
1049 : // only if no xkb available
1050 50 : if (!_xkb) {
1051 0 : memset(_keycodes, 0, sizeof(core::InputKeyCode) * 256);
1052 : // from https://stackoverflow.com/questions/18689863/obtain-keyboard-layout-and-keysyms-with-xcb
1053 0 : xcb_get_keyboard_mapping_reply_t *keyboard_mapping = _xcb->xcb_get_keyboard_mapping_reply(_connection, mappingCookie, NULL);
1054 :
1055 0 : int nkeycodes = keyboard_mapping->length / keyboard_mapping->keysyms_per_keycode;
1056 :
1057 0 : xcb_keysym_t *keysyms = (xcb_keysym_t*) (keyboard_mapping + 1);
1058 :
1059 0 : for (int keycode_idx = 0; keycode_idx < nkeycodes; ++keycode_idx) {
1060 0 : _keycodes[setup->min_keycode + keycode_idx] = getKeysymCode(keysyms[keycode_idx * keyboard_mapping->keysyms_per_keycode]);
1061 : }
1062 :
1063 0 : free(keyboard_mapping);
1064 : }
1065 : }
1066 :
1067 2 : xcb_keysym_t XcbView::getKeysym(xcb_keycode_t code, uint16_t state, bool resolveMods) {
1068 : xcb_keysym_t k0, k1;
1069 :
1070 2 : if (!resolveMods) {
1071 2 : k0 = _xcb->xcb_key_symbols_get_keysym(_keysyms, code, 0);
1072 : // resolve only numlock
1073 2 : if ((state & _numlock)) {
1074 0 : k1 = _xcb->xcb_key_symbols_get_keysym(_keysyms, code, 1);
1075 0 : if (_xcb->xcb_is_keypad_key(k1)) {
1076 0 : if ((state & XCB_MOD_MASK_SHIFT) || ((state & XCB_MOD_MASK_LOCK) && (state & _shiftlock))) {
1077 0 : return k0;
1078 : } else {
1079 0 : return k1;
1080 : }
1081 : }
1082 : }
1083 2 : return k0;
1084 : }
1085 :
1086 0 : if (state & _modeswitch) {
1087 0 : k0 = _xcb->xcb_key_symbols_get_keysym(_keysyms, code, 2);
1088 0 : k1 = _xcb->xcb_key_symbols_get_keysym(_keysyms, code, 3);
1089 : } else {
1090 0 : k0 = _xcb->xcb_key_symbols_get_keysym(_keysyms, code, 0);
1091 0 : k1 = _xcb->xcb_key_symbols_get_keysym(_keysyms, code, 1);
1092 : }
1093 :
1094 0 : if (k1 == XCB_NO_SYMBOL)
1095 0 : k1 = k0;
1096 :
1097 0 : if ((state & _numlock) && _xcb->xcb_is_keypad_key(k1)) {
1098 0 : if ((state & XCB_MOD_MASK_SHIFT) || ((state & XCB_MOD_MASK_LOCK) && (state & _shiftlock))) {
1099 0 : return k0;
1100 : } else {
1101 0 : return k1;
1102 : }
1103 0 : } else if (!(state & XCB_MOD_MASK_SHIFT) && !(state & XCB_MOD_MASK_LOCK)) {
1104 0 : return k0;
1105 0 : } else if (!(state & XCB_MOD_MASK_SHIFT) && ((state & XCB_MOD_MASK_LOCK) && (state & _capslock))) {
1106 0 : if (k0 >= XK_0 && k0 <= XK_9) {
1107 0 : return k0;
1108 : }
1109 0 : return k1;
1110 0 : } else if ((state & XCB_MOD_MASK_SHIFT) && ((state & XCB_MOD_MASK_LOCK) && (state & _capslock))) {
1111 0 : return k1;
1112 0 : } else if ((state & XCB_MOD_MASK_SHIFT) || ((state & XCB_MOD_MASK_LOCK) && (state & _shiftlock))) {
1113 0 : return k1;
1114 : }
1115 :
1116 0 : return XCB_NO_SYMBOL;
1117 : }
1118 :
1119 0 : xkb_keysym_t XcbView::composeSymbol(xkb_keysym_t sym, core::InputKeyComposeState &compose) const {
1120 0 : if (sym == XKB_KEY_NoSymbol || !_xkbCompose) {
1121 : XL_X11_LOG("Compose: ", sym, " (disabled)");
1122 0 : return sym;
1123 : }
1124 0 : if (_xkb->xkb_compose_state_feed(_xkbCompose, sym) != XKB_COMPOSE_FEED_ACCEPTED) {
1125 : XL_X11_LOG("Compose: ", sym, " (not accepted)");
1126 0 : return sym;
1127 : }
1128 0 : auto composedSym = sym;
1129 0 : auto state = _xkb->xkb_compose_state_get_status(_xkbCompose);
1130 0 : switch (state) {
1131 0 : case XKB_COMPOSE_COMPOSED:
1132 0 : compose = core::InputKeyComposeState::Composed;
1133 0 : composedSym = _xkb->xkb_compose_state_get_one_sym(_xkbCompose);
1134 0 : _xkb->xkb_compose_state_reset(_xkbCompose);
1135 : XL_X11_LOG("Compose: ", sym, ": ", composedSym, " (composed)");
1136 0 : break;
1137 0 : case XKB_COMPOSE_COMPOSING:
1138 0 : compose = core::InputKeyComposeState::Composing;
1139 : XL_X11_LOG("Compose: ", sym, ": ", composedSym, " (composing)");
1140 0 : break;
1141 0 : case XKB_COMPOSE_CANCELLED:
1142 0 : _xkb->xkb_compose_state_reset(_xkbCompose);
1143 : XL_X11_LOG("Compose: ", sym, ": ", composedSym, " (cancelled)");
1144 0 : break;
1145 0 : case XKB_COMPOSE_NOTHING:
1146 0 : _xkb->xkb_compose_state_reset(_xkbCompose);
1147 : XL_X11_LOG("Compose: ", sym, ": ", composedSym, " (nothing)");
1148 0 : break;
1149 0 : default:
1150 : XL_X11_LOG("Compose: ", sym, ": ", composedSym, " (error)");
1151 0 : break;
1152 : }
1153 0 : return composedSym;
1154 : }
1155 :
1156 6200 : void XcbView::updateXkbKey(xcb_keycode_t code) {
1157 : static const struct {
1158 : core::InputKeyCode key;
1159 : const char *name;
1160 : } keymap[] = {
1161 : { core::InputKeyCode::GRAVE_ACCENT, "TLDE" },
1162 : { core::InputKeyCode::_1, "AE01" },
1163 : { core::InputKeyCode::_2, "AE02" },
1164 : { core::InputKeyCode::_3, "AE03" },
1165 : { core::InputKeyCode::_4, "AE04" },
1166 : { core::InputKeyCode::_5, "AE05" },
1167 : { core::InputKeyCode::_6, "AE06" },
1168 : { core::InputKeyCode::_7, "AE07" },
1169 : { core::InputKeyCode::_8, "AE08" },
1170 : { core::InputKeyCode::_9, "AE09" },
1171 : { core::InputKeyCode::_0, "AE10" },
1172 : { core::InputKeyCode::MINUS, "AE11" },
1173 : { core::InputKeyCode::EQUAL, "AE12" },
1174 : { core::InputKeyCode::Q, "AD01" },
1175 : { core::InputKeyCode::W, "AD02" },
1176 : { core::InputKeyCode::E, "AD03" },
1177 : { core::InputKeyCode::R, "AD04" },
1178 : { core::InputKeyCode::T, "AD05" },
1179 : { core::InputKeyCode::Y, "AD06" },
1180 : { core::InputKeyCode::U, "AD07" },
1181 : { core::InputKeyCode::I, "AD08" },
1182 : { core::InputKeyCode::O, "AD09" },
1183 : { core::InputKeyCode::P, "AD10" },
1184 : { core::InputKeyCode::LEFT_BRACKET, "AD11" },
1185 : { core::InputKeyCode::RIGHT_BRACKET, "AD12" },
1186 : { core::InputKeyCode::A, "AC01" },
1187 : { core::InputKeyCode::S, "AC02" },
1188 : { core::InputKeyCode::D, "AC03" },
1189 : { core::InputKeyCode::F, "AC04" },
1190 : { core::InputKeyCode::G, "AC05" },
1191 : { core::InputKeyCode::H, "AC06" },
1192 : { core::InputKeyCode::J, "AC07" },
1193 : { core::InputKeyCode::K, "AC08" },
1194 : { core::InputKeyCode::L, "AC09" },
1195 : { core::InputKeyCode::SEMICOLON, "AC10" },
1196 : { core::InputKeyCode::APOSTROPHE, "AC11" },
1197 : { core::InputKeyCode::Z, "AB01" },
1198 : { core::InputKeyCode::X, "AB02" },
1199 : { core::InputKeyCode::C, "AB03" },
1200 : { core::InputKeyCode::V, "AB04" },
1201 : { core::InputKeyCode::B, "AB05" },
1202 : { core::InputKeyCode::N, "AB06" },
1203 : { core::InputKeyCode::M, "AB07" },
1204 : { core::InputKeyCode::COMMA, "AB08" },
1205 : { core::InputKeyCode::PERIOD, "AB09" },
1206 : { core::InputKeyCode::SLASH, "AB10" },
1207 : { core::InputKeyCode::BACKSLASH, "BKSL" },
1208 : { core::InputKeyCode::WORLD_1, "LSGT" },
1209 : { core::InputKeyCode::SPACE, "SPCE" },
1210 : { core::InputKeyCode::ESCAPE, "ESC" },
1211 : { core::InputKeyCode::ENTER, "RTRN" },
1212 : { core::InputKeyCode::TAB, "TAB" },
1213 : { core::InputKeyCode::BACKSPACE, "BKSP" },
1214 : { core::InputKeyCode::INSERT, "INS" },
1215 : { core::InputKeyCode::DELETE, "DELE" },
1216 : { core::InputKeyCode::RIGHT, "RGHT" },
1217 : { core::InputKeyCode::LEFT, "LEFT" },
1218 : { core::InputKeyCode::DOWN, "DOWN" },
1219 : { core::InputKeyCode::UP, "UP" },
1220 : { core::InputKeyCode::PAGE_UP, "PGUP" },
1221 : { core::InputKeyCode::PAGE_DOWN, "PGDN" },
1222 : { core::InputKeyCode::HOME, "HOME" },
1223 : { core::InputKeyCode::END, "END" },
1224 : { core::InputKeyCode::CAPS_LOCK, "CAPS" },
1225 : { core::InputKeyCode::SCROLL_LOCK, "SCLK" },
1226 : { core::InputKeyCode::NUM_LOCK, "NMLK" },
1227 : { core::InputKeyCode::PRINT_SCREEN, "PRSC" },
1228 : { core::InputKeyCode::PAUSE, "PAUS" },
1229 : { core::InputKeyCode::F1, "FK01" },
1230 : { core::InputKeyCode::F2, "FK02" },
1231 : { core::InputKeyCode::F3, "FK03" },
1232 : { core::InputKeyCode::F4, "FK04" },
1233 : { core::InputKeyCode::F5, "FK05" },
1234 : { core::InputKeyCode::F6, "FK06" },
1235 : { core::InputKeyCode::F7, "FK07" },
1236 : { core::InputKeyCode::F8, "FK08" },
1237 : { core::InputKeyCode::F9, "FK09" },
1238 : { core::InputKeyCode::F10, "FK10" },
1239 : { core::InputKeyCode::F11, "FK11" },
1240 : { core::InputKeyCode::F12, "FK12" },
1241 : { core::InputKeyCode::F13, "FK13" },
1242 : { core::InputKeyCode::F14, "FK14" },
1243 : { core::InputKeyCode::F15, "FK15" },
1244 : { core::InputKeyCode::F16, "FK16" },
1245 : { core::InputKeyCode::F17, "FK17" },
1246 : { core::InputKeyCode::F18, "FK18" },
1247 : { core::InputKeyCode::F19, "FK19" },
1248 : { core::InputKeyCode::F20, "FK20" },
1249 : { core::InputKeyCode::F21, "FK21" },
1250 : { core::InputKeyCode::F22, "FK22" },
1251 : { core::InputKeyCode::F23, "FK23" },
1252 : { core::InputKeyCode::F24, "FK24" },
1253 : { core::InputKeyCode::F25, "FK25" },
1254 : { core::InputKeyCode::KP_0, "KP0" },
1255 : { core::InputKeyCode::KP_1, "KP1" },
1256 : { core::InputKeyCode::KP_2, "KP2" },
1257 : { core::InputKeyCode::KP_3, "KP3" },
1258 : { core::InputKeyCode::KP_4, "KP4" },
1259 : { core::InputKeyCode::KP_5, "KP5" },
1260 : { core::InputKeyCode::KP_6, "KP6" },
1261 : { core::InputKeyCode::KP_7, "KP7" },
1262 : { core::InputKeyCode::KP_8, "KP8" },
1263 : { core::InputKeyCode::KP_9, "KP9" },
1264 : { core::InputKeyCode::KP_DECIMAL, "KPDL" },
1265 : { core::InputKeyCode::KP_DIVIDE, "KPDV" },
1266 : { core::InputKeyCode::KP_MULTIPLY, "KPMU" },
1267 : { core::InputKeyCode::KP_SUBTRACT, "KPSU" },
1268 : { core::InputKeyCode::KP_ADD, "KPAD" },
1269 : { core::InputKeyCode::KP_ENTER, "KPEN" },
1270 : { core::InputKeyCode::KP_EQUAL, "KPEQ" },
1271 : { core::InputKeyCode::LEFT_SHIFT, "LFSH" },
1272 : { core::InputKeyCode::LEFT_CONTROL, "LCTL" },
1273 : { core::InputKeyCode::LEFT_ALT, "LALT" },
1274 : { core::InputKeyCode::LEFT_SUPER, "LWIN" },
1275 : { core::InputKeyCode::RIGHT_SHIFT, "RTSH" },
1276 : { core::InputKeyCode::RIGHT_CONTROL, "RCTL" },
1277 : { core::InputKeyCode::RIGHT_ALT, "RALT" },
1278 : { core::InputKeyCode::RIGHT_ALT, "LVL3" },
1279 : { core::InputKeyCode::RIGHT_ALT, "MDSW" },
1280 : { core::InputKeyCode::RIGHT_SUPER, "RWIN" },
1281 : { core::InputKeyCode::MENU, "MENU" }
1282 : };
1283 :
1284 6200 : core::InputKeyCode key = core::InputKeyCode::Unknown;
1285 6200 : if (auto name = _xkb->xkb_keymap_key_get_name(_xkbKeymap, code)) {
1286 566525 : for (size_t i = 0; i < sizeof(keymap) / sizeof(keymap[0]); i++) {
1287 563350 : if (strncmp(name, keymap[i].name, 4) == 0) {
1288 2975 : key = keymap[i].key;
1289 2975 : break;
1290 : }
1291 : }
1292 : }
1293 :
1294 6200 : if (key != core::InputKeyCode::Unknown) {
1295 2975 : _keycodes[code] = key;
1296 : }
1297 6200 : }
1298 :
1299 2 : core::InputKeyCode XcbView::getKeyCode(xcb_keycode_t code) const {
1300 2 : return _keycodes[code];
1301 : }
1302 :
1303 0 : core::InputKeyCode XcbView::getKeysymCode(xcb_keysym_t sym) const {
1304 : // from GLFW: x11_init.c
1305 0 : switch (sym) {
1306 0 : case XK_KP_0: return core::InputKeyCode::KP_0;
1307 0 : case XK_KP_1: return core::InputKeyCode::KP_1;
1308 0 : case XK_KP_2: return core::InputKeyCode::KP_2;
1309 0 : case XK_KP_3: return core::InputKeyCode::KP_3;
1310 0 : case XK_KP_4: return core::InputKeyCode::KP_4;
1311 0 : case XK_KP_5: return core::InputKeyCode::KP_5;
1312 0 : case XK_KP_6: return core::InputKeyCode::KP_6;
1313 0 : case XK_KP_7: return core::InputKeyCode::KP_7;
1314 0 : case XK_KP_8: return core::InputKeyCode::KP_8;
1315 0 : case XK_KP_9: return core::InputKeyCode::KP_9;
1316 0 : case XK_KP_Separator:
1317 0 : case XK_KP_Decimal: return core::InputKeyCode::KP_DECIMAL;
1318 0 : case XK_Escape: return core::InputKeyCode::ESCAPE;
1319 0 : case XK_Tab: return core::InputKeyCode::TAB;
1320 0 : case XK_Shift_L: return core::InputKeyCode::LEFT_SHIFT;
1321 0 : case XK_Shift_R: return core::InputKeyCode::RIGHT_SHIFT;
1322 0 : case XK_Control_L: return core::InputKeyCode::LEFT_CONTROL;
1323 0 : case XK_Control_R: return core::InputKeyCode::RIGHT_CONTROL;
1324 0 : case XK_Meta_L:
1325 0 : case XK_Alt_L: return core::InputKeyCode::LEFT_ALT;
1326 0 : case XK_Mode_switch: // Mapped to Alt_R on many keyboards
1327 : case XK_ISO_Level3_Shift: // AltGr on at least some machines
1328 : case XK_Meta_R:
1329 0 : case XK_Alt_R: return core::InputKeyCode::RIGHT_ALT;
1330 0 : case XK_Super_L: return core::InputKeyCode::LEFT_SUPER;
1331 0 : case XK_Super_R: return core::InputKeyCode::RIGHT_SUPER;
1332 0 : case XK_Menu: return core::InputKeyCode::MENU;
1333 0 : case XK_Num_Lock: return core::InputKeyCode::NUM_LOCK;
1334 0 : case XK_Caps_Lock: return core::InputKeyCode::CAPS_LOCK;
1335 0 : case XK_Print: return core::InputKeyCode::PRINT_SCREEN;
1336 0 : case XK_Scroll_Lock: return core::InputKeyCode::SCROLL_LOCK;
1337 0 : case XK_Pause: return core::InputKeyCode::PAUSE;
1338 0 : case XK_Delete: return core::InputKeyCode::DELETE;
1339 0 : case XK_BackSpace: return core::InputKeyCode::BACKSPACE;
1340 0 : case XK_Return: return core::InputKeyCode::ENTER;
1341 0 : case XK_Home: return core::InputKeyCode::HOME;
1342 0 : case XK_End: return core::InputKeyCode::END;
1343 0 : case XK_Page_Up: return core::InputKeyCode::PAGE_UP;
1344 0 : case XK_Page_Down: return core::InputKeyCode::PAGE_DOWN;
1345 0 : case XK_Insert: return core::InputKeyCode::INSERT;
1346 0 : case XK_Left: return core::InputKeyCode::LEFT;
1347 0 : case XK_Right: return core::InputKeyCode::RIGHT;
1348 0 : case XK_Down: return core::InputKeyCode::DOWN;
1349 0 : case XK_Up: return core::InputKeyCode::UP;
1350 0 : case XK_F1: return core::InputKeyCode::F1;
1351 0 : case XK_F2: return core::InputKeyCode::F2;
1352 0 : case XK_F3: return core::InputKeyCode::F3;
1353 0 : case XK_F4: return core::InputKeyCode::F4;
1354 0 : case XK_F5: return core::InputKeyCode::F5;
1355 0 : case XK_F6: return core::InputKeyCode::F6;
1356 0 : case XK_F7: return core::InputKeyCode::F7;
1357 0 : case XK_F8: return core::InputKeyCode::F8;
1358 0 : case XK_F9: return core::InputKeyCode::F9;
1359 0 : case XK_F10: return core::InputKeyCode::F10;
1360 0 : case XK_F11: return core::InputKeyCode::F11;
1361 0 : case XK_F12: return core::InputKeyCode::F12;
1362 0 : case XK_F13: return core::InputKeyCode::F13;
1363 0 : case XK_F14: return core::InputKeyCode::F14;
1364 0 : case XK_F15: return core::InputKeyCode::F15;
1365 0 : case XK_F16: return core::InputKeyCode::F16;
1366 0 : case XK_F17: return core::InputKeyCode::F17;
1367 0 : case XK_F18: return core::InputKeyCode::F18;
1368 0 : case XK_F19: return core::InputKeyCode::F19;
1369 0 : case XK_F20: return core::InputKeyCode::F20;
1370 0 : case XK_F21: return core::InputKeyCode::F21;
1371 0 : case XK_F22: return core::InputKeyCode::F22;
1372 0 : case XK_F23: return core::InputKeyCode::F23;
1373 0 : case XK_F24: return core::InputKeyCode::F24;
1374 0 : case XK_F25: return core::InputKeyCode::F25;
1375 :
1376 : // Numeric keypad
1377 0 : case XK_KP_Divide: return core::InputKeyCode::KP_DIVIDE;
1378 0 : case XK_KP_Multiply: return core::InputKeyCode::KP_MULTIPLY;
1379 0 : case XK_KP_Subtract: return core::InputKeyCode::KP_SUBTRACT;
1380 0 : case XK_KP_Add: return core::InputKeyCode::KP_ADD;
1381 :
1382 : // These should have been detected in secondary keysym test above!
1383 0 : case XK_KP_Insert: return core::InputKeyCode::KP_0;
1384 0 : case XK_KP_End: return core::InputKeyCode::KP_1;
1385 0 : case XK_KP_Down: return core::InputKeyCode::KP_2;
1386 0 : case XK_KP_Page_Down: return core::InputKeyCode::KP_3;
1387 0 : case XK_KP_Left: return core::InputKeyCode::KP_4;
1388 0 : case XK_KP_Right: return core::InputKeyCode::KP_6;
1389 0 : case XK_KP_Home: return core::InputKeyCode::KP_7;
1390 0 : case XK_KP_Up: return core::InputKeyCode::KP_8;
1391 0 : case XK_KP_Page_Up: return core::InputKeyCode::KP_9;
1392 0 : case XK_KP_Delete: return core::InputKeyCode::KP_DECIMAL;
1393 0 : case XK_KP_Equal: return core::InputKeyCode::KP_EQUAL;
1394 0 : case XK_KP_Enter: return core::InputKeyCode::KP_ENTER;
1395 :
1396 : // Last resort: Check for printable keys (should not happen if the XKB
1397 : // extension is available). This will give a layout dependent mapping
1398 : // (which is wrong, and we may miss some keys, especially on non-US
1399 : // keyboards), but it's better than nothing...
1400 0 : case XK_a: return core::InputKeyCode::A;
1401 0 : case XK_b: return core::InputKeyCode::B;
1402 0 : case XK_c: return core::InputKeyCode::C;
1403 0 : case XK_d: return core::InputKeyCode::D;
1404 0 : case XK_e: return core::InputKeyCode::E;
1405 0 : case XK_f: return core::InputKeyCode::F;
1406 0 : case XK_g: return core::InputKeyCode::G;
1407 0 : case XK_h: return core::InputKeyCode::H;
1408 0 : case XK_i: return core::InputKeyCode::I;
1409 0 : case XK_j: return core::InputKeyCode::J;
1410 0 : case XK_k: return core::InputKeyCode::K;
1411 0 : case XK_l: return core::InputKeyCode::L;
1412 0 : case XK_m: return core::InputKeyCode::M;
1413 0 : case XK_n: return core::InputKeyCode::N;
1414 0 : case XK_o: return core::InputKeyCode::O;
1415 0 : case XK_p: return core::InputKeyCode::P;
1416 0 : case XK_q: return core::InputKeyCode::Q;
1417 0 : case XK_r: return core::InputKeyCode::R;
1418 0 : case XK_s: return core::InputKeyCode::S;
1419 0 : case XK_t: return core::InputKeyCode::T;
1420 0 : case XK_u: return core::InputKeyCode::U;
1421 0 : case XK_v: return core::InputKeyCode::V;
1422 0 : case XK_w: return core::InputKeyCode::W;
1423 0 : case XK_x: return core::InputKeyCode::X;
1424 0 : case XK_y: return core::InputKeyCode::Y;
1425 0 : case XK_z: return core::InputKeyCode::Z;
1426 0 : case XK_1: return core::InputKeyCode::_1;
1427 0 : case XK_2: return core::InputKeyCode::_2;
1428 0 : case XK_3: return core::InputKeyCode::_3;
1429 0 : case XK_4: return core::InputKeyCode::_4;
1430 0 : case XK_5: return core::InputKeyCode::_5;
1431 0 : case XK_6: return core::InputKeyCode::_6;
1432 0 : case XK_7: return core::InputKeyCode::_7;
1433 0 : case XK_8: return core::InputKeyCode::_8;
1434 0 : case XK_9: return core::InputKeyCode::_9;
1435 0 : case XK_0: return core::InputKeyCode::_0;
1436 0 : case XK_space: return core::InputKeyCode::SPACE;
1437 0 : case XK_minus: return core::InputKeyCode::MINUS;
1438 0 : case XK_equal: return core::InputKeyCode::EQUAL;
1439 0 : case XK_bracketleft: return core::InputKeyCode::LEFT_BRACKET;
1440 0 : case XK_bracketright: return core::InputKeyCode::RIGHT_BRACKET;
1441 0 : case XK_backslash: return core::InputKeyCode::BACKSLASH;
1442 0 : case XK_semicolon: return core::InputKeyCode::SEMICOLON;
1443 0 : case XK_apostrophe: return core::InputKeyCode::APOSTROPHE;
1444 0 : case XK_grave: return core::InputKeyCode::GRAVE_ACCENT;
1445 0 : case XK_comma: return core::InputKeyCode::COMMA;
1446 0 : case XK_period: return core::InputKeyCode::PERIOD;
1447 0 : case XK_slash: return core::InputKeyCode::SLASH;
1448 0 : case XK_less: return core::InputKeyCode::WORLD_1; // At least in some layouts...
1449 0 : default: break;
1450 : }
1451 0 : return core::InputKeyCode::Unknown;
1452 : }
1453 :
1454 0 : void XcbView::notifyClipboard(BytesView data) {
1455 0 : if (_clipboardCallback) {
1456 0 : _clipboardCallback(data, "text/plain");
1457 : }
1458 0 : _clipboardCallback = nullptr;
1459 0 : _clipboardTarget = nullptr;
1460 0 : }
1461 :
1462 0 : xcb_atom_t XcbView::writeTargetToProperty(xcb_selection_request_event_t *request) {
1463 0 : if (request->property == 0) {
1464 : // The requester is a legacy client (ICCCM section 2.2)
1465 : // We don't support legacy clients, so fail here
1466 0 : return 0;
1467 : }
1468 :
1469 0 : if (request->target == _atoms[toInt(XcbAtomIndex::TARGETS)]) {
1470 : // The list of supported targets was requested
1471 :
1472 0 : const xcb_atom_t targets[] = { _atoms[toInt(XcbAtomIndex::TARGETS)], _atoms[toInt(XcbAtomIndex::STRING)] };
1473 :
1474 0 : _xcb->xcb_change_property(_connection, XCB_PROP_MODE_REPLACE, request->requestor, request->property, XCB_ATOM_ATOM, 8*sizeof(xcb_atom_t),
1475 : sizeof(targets) / sizeof(targets[0]), (unsigned char*) targets);
1476 0 : return request->property;
1477 : }
1478 :
1479 0 : if (request->target == _atoms[toInt(XcbAtomIndex::SAVE_TARGETS)]) {
1480 : // The request is a check whether we support SAVE_TARGETS
1481 : // It should be handled as a no-op side effect target
1482 :
1483 0 : _xcb->xcb_change_property(_connection, XCB_PROP_MODE_REPLACE, request->requestor, request->property,
1484 : _atoms[toInt(XcbAtomIndex::XNULL)], 32, 0, nullptr);
1485 0 : return request->property;
1486 : }
1487 :
1488 : // Conversion to a data target was requested
1489 :
1490 0 : if (request->target == _atoms[toInt(XcbAtomIndex::STRING)]) {
1491 0 : _xcb->xcb_change_property(_connection, XCB_PROP_MODE_REPLACE, request->requestor, request->property, request->target, 8,
1492 0 : _clipboardSelection.size(), _clipboardSelection.data());
1493 0 : return request->property;
1494 : }
1495 :
1496 : // The requested target is not supported
1497 :
1498 0 : return 0;
1499 : }
1500 :
1501 0 : void XcbView::handleSelectionRequest(xcb_selection_request_event_t *event) {
1502 0 : if (event->target == _atoms[toInt(XcbAtomIndex::TARGETS)]) {
1503 : // The list of supported targets was requested
1504 :
1505 0 : const xcb_atom_t targets[] = { _atoms[toInt(XcbAtomIndex::TARGETS)], _atoms[toInt(XcbAtomIndex::STRING)] };
1506 :
1507 0 : _xcb->xcb_change_property(_connection, XCB_PROP_MODE_REPLACE, event->requestor, event->property, XCB_ATOM_ATOM, 8*sizeof(xcb_atom_t),
1508 : sizeof(targets) / sizeof(targets[0]), (unsigned char*) targets);
1509 : }
1510 :
1511 0 : if (event->target == _atoms[toInt(XcbAtomIndex::SAVE_TARGETS)]) {
1512 0 : _xcb->xcb_change_property(_connection, XCB_PROP_MODE_REPLACE, event->requestor, event->property,
1513 : _atoms[toInt(XcbAtomIndex::XNULL)], 32, 0, nullptr);
1514 : }
1515 :
1516 0 : if (event->target == _atoms[toInt(XcbAtomIndex::STRING)]) {
1517 0 : _xcb->xcb_change_property(_connection, XCB_PROP_MODE_REPLACE, event->requestor, event->property, event->target, 8,
1518 0 : _clipboardSelection.size(), _clipboardSelection.data());
1519 : }
1520 :
1521 : xcb_selection_notify_event_t notify;
1522 0 : notify.response_type = XCB_SELECTION_NOTIFY;
1523 0 : notify.pad0 = 0;
1524 0 : notify.sequence = 0;
1525 0 : notify.time = event->time;
1526 0 : notify.requestor = event->requestor;
1527 0 : notify.selection = event->selection;
1528 0 : notify.target = event->target;
1529 0 : notify.property = event->property;
1530 :
1531 0 : _xcb->xcb_send_event(_connection, false, event->requestor, XCB_EVENT_MASK_NO_EVENT, // SelectionNotify events go without mask
1532 : (const char*) ¬ify);
1533 0 : _xcb->xcb_flush(_connection);
1534 0 : }
1535 :
1536 : }
|