LCOV - code coverage report
Current view: top level - xenolith/platform/linux - XLPlatformLinuxXcbView.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 472 865 54.6 %
Date: 2024-05-12 00:16:13 Functions: 18 27 66.7 %

          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*) &notify);
    1533           0 :         _xcb->xcb_flush(_connection);
    1534           0 : }
    1535             : 
    1536             : }

Generated by: LCOV version 1.14