//=========================================== // Lumina-DE source code // Copyright (c) 2017, Ken Moore // Available under the 3-clause BSD license // See the LICENSE file for full details //=========================================== // This is the XCB version of the NativeWindowSystem class, // used for interacting with the X11 display system on BSD/Linux/Unix systems //=========================================== #include "NativeWindowSystem.h" //Additional Qt includes #include #include #include #include //XCB Library includes #include #include #include #include #include #include #include #include #include //XLib includes (XCB Damage lib does not appear to register for damage events properly) #include //SYSTEM TRAY STANDARD DEFINITIONS #define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 #define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 #define SYSTEM_TRAY_REQUEST_DOCK 0 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 #define URGENCYHINT (1L << 8) //For window urgency detection #define ROOT_WIN_EVENT_MASK (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \ XCB_EVENT_MASK_BUTTON_PRESS | \ XCB_EVENT_MASK_STRUCTURE_NOTIFY | \ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | \ XCB_EVENT_MASK_POINTER_MOTION | \ XCB_EVENT_MASK_PROPERTY_CHANGE | \ XCB_EVENT_MASK_FOCUS_CHANGE | \ XCB_EVENT_MASK_ENTER_WINDOW) //Internal XCB private objects class class NativeWindowSystem::p_objects{ public: xcb_ewmh_connection_t EWMH; //This is where all the screen info and atoms are located QHash ATOMS; xcb_screen_t *root_screen; xcb_window_t root_window, wm_window, tray_window; //Functions for setting up these objects as needed bool init_ATOMS(){ QStringList atoms; atoms << "WM_TAKE_FOCUS" << "WM_DELETE_WINDOW" << "WM_PROTOCOLS" << "WM_CHANGE_STATE" << "_NET_SYSTEM_TRAY_OPCODE" << "_NET_SYSTEM_TRAY_ORIENTATION" << "_NET_SYSTEM_TRAY_VISUAL" << QString("_NET_SYSTEM_TRAY_S%1").arg(QString::number(QX11Info::appScreen())); //Create all the requests for the atoms QList reply; for(int i=0; iatom); free(reply[i]); //done with this reply }else{ //Invalid atom - could not be created qDebug() << "Could not initialize XCB atom:" << atoms[i]; } } //loop over reply return (ATOMS.keys().length() == atoms.length()); } bool register_wm(){ uint32_t value_list[1] = {ROOT_WIN_EVENT_MASK}; xcb_generic_error_t *status = xcb_request_check( QX11Info::connection(), xcb_change_window_attributes_checked(QX11Info::connection(), root_window, XCB_CW_EVENT_MASK, value_list)); if(status!=0){ return false; } uint32_t params[] = {1}; wm_window = xcb_generate_id(QX11Info::connection()); //need a new ID xcb_create_window(QX11Info::connection(), root_screen->root_depth, \ wm_window, root_window, -1, -1, 1, 1, 0, \ XCB_WINDOW_CLASS_INPUT_OUTPUT, root_screen->root_visual, \ XCB_CW_OVERRIDE_REDIRECT, params); if(wm_window==0){ return false; } //Set the _NET_SUPPORTING_WM property on the root window first xcb_ewmh_set_supporting_wm_check(&EWMH, root_window, wm_window); //Also set this property on the child window (pointing to itself) xcb_ewmh_set_supporting_wm_check(&EWMH, wm_window, wm_window); //Now also setup the root event mask on the wm_window status = xcb_request_check( QX11Info::connection(), xcb_change_window_attributes_checked(QX11Info::connection(), wm_window, XCB_CW_EVENT_MASK, value_list)); if(status!=0){ return false; } return true; } bool start_system_tray(){ xcb_atom_t _NET_SYSTEM_TRAY_S = ATOMS.value( QString("_NET_SYSTEM_TRAY_S%1").arg(QString::number(QX11Info::appScreen())) ); //Make sure that there is no other system tray running xcb_get_selection_owner_reply_t *ownreply = xcb_get_selection_owner_reply(QX11Info::connection(), \ xcb_get_selection_owner_unchecked(QX11Info::connection(), _NET_SYSTEM_TRAY_S), NULL); if(ownreply == 0){ qWarning() << " - Could not get owner selection reply"; return false; }else if(ownreply->owner != 0){ free(ownreply); qWarning() << " - An alternate system tray is currently in use"; return false; } free(ownreply); //Now create the window to use (just offscreen) tray_window = xcb_generate_id(QX11Info::connection()); //need a new ID uint32_t params[] = {1}; xcb_create_window(QX11Info::connection(), root_screen->root_depth, \ tray_window, root_screen->root, -1, -1, 1, 1, 0, \ XCB_WINDOW_CLASS_INPUT_OUTPUT, root_screen->root_visual, \ XCB_CW_OVERRIDE_REDIRECT, params); //Now register this widget as the system tray xcb_set_selection_owner(QX11Info::connection(), tray_window, _NET_SYSTEM_TRAY_S, XCB_CURRENT_TIME); //Make sure that it was registered properly ownreply = xcb_get_selection_owner_reply(QX11Info::connection(), \ xcb_get_selection_owner_unchecked(QX11Info::connection(), _NET_SYSTEM_TRAY_S), NULL); if(ownreply==0 || ownreply->owner != tray_window){ if(ownreply!=0){ free(ownreply); } qWarning() << " - Could not register the system tray"; xcb_destroy_window(QX11Info::connection(), tray_window); return false; } free(ownreply); //done with structure //Now register the orientation of the system tray uint32_t orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ; xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, tray_window, \ ATOMS.value("_NET_SYSTEM_TRAY_ORIENTATION"), XCB_ATOM_CARDINAL, 32, 1, &orient); //Now set the visual ID for the system tray (same as the root window, but TrueColor) xcb_visualtype_t *type = xcb_aux_find_visual_by_attrs(root_screen, XCB_VISUAL_CLASS_TRUE_COLOR, 32); if(type!=0){ xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, tray_window, \ ATOMS.value("_NET_SYSTEM_TRAY_VISUAL"), XCB_ATOM_VISUALID, 32, 1, &type->visual_id); }else{ qWarning() << " - Could not set TrueColor visual for system tray"; } //Finally, send out an X event letting others know that the system tray is up and running xcb_client_message_event_t event; event.response_type = XCB_CLIENT_MESSAGE; event.format = 32; event.window = root_screen->root; event.type = EWMH.MANAGER; //MANAGER atom event.data.data32[0] = XCB_TIME_CURRENT_TIME; //CurrentTime; event.data.data32[1] = _NET_SYSTEM_TRAY_S; //_NET_SYSTEM_TRAY_S atom event.data.data32[2] = tray_window; event.data.data32[3] = 0; event.data.data32[4] = 0; xcb_send_event(QX11Info::connection(), 0, root_screen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); return true; } }; //end private objects class //inline functions for setting up the internal objects // === PUBLIC === NativeWindowSystem::NativeWindowSystem() : QObject(){ obj = 0; } NativeWindowSystem::~NativeWindowSystem(){ xcb_ewmh_connection_wipe(&(obj->EWMH)); free(obj); } //Overarching start/stop functions bool NativeWindowSystem::start(){ //Initialize the XCB/EWMH objects if(obj==0){ obj = new p_objects(); //instantiate the private objects obj->wm_window = 0; obj->tray_window = 0; xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(QX11Info::connection(), &obj->EWMH); if(!xcb_ewmh_init_atoms_replies(&obj->EWMH, cookie, NULL) ){ qDebug() << "Error with XCB atom initializations"; return false; } obj->root_screen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); obj->root_window = obj->root_screen->root; //simplification for later - minor duplication of memory (unsigned int) //Initialize all the extra atoms that the EWMH object does not have if( !obj->init_ATOMS() ){ return false; } } //Done with private object init bool ok = obj->register_wm(); if(ok){ ok = obj->start_system_tray(); } return ok; } void NativeWindowSystem::stop(){ } //Small simplification functions Qt::Key NativeWindowSystem::KeycodeToQt(int keycode){ return Qt::Key_unknown; } NativeWindowSystem::MouseButton NativeWindowSystem::MouseToQt(int keycode){ switch(keycode){ case 1: return NativeWindowSystem::LeftButton; case 3: return NativeWindowSystem::RightButton; case 2: return NativeWindowSystem::MidButton; case 4: return NativeWindowSystem::WheelUp; case 5: return NativeWindowSystem::WheelDown; case 6: return NativeWindowSystem::WheelLeft; case 7: return NativeWindowSystem::WheelRight; case 8: return NativeWindowSystem::BackButton; //Not sure if this is correct yet (1/27/17) case 9: return NativeWindowSystem::ForwardButton; //Not sure if this is correct yet (1/27/17) default: return NativeWindowSystem::NoButton; } } // === PRIVATE === void NativeWindowSystem::UpdateWindowProperties(NativeWindow* win, QList< NativeWindow::Property > props){ //Put the properties in logical groups as appropriate (some XCB calls return multiple properties) if(props.contains(NativeWindow::Title)){ //Try the EWMH standards first // _NET_WM_NAME QString name; xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name_unchecked(&obj->EWMH, win->id()); if(cookie.sequence != 0){ xcb_ewmh_get_utf8_strings_reply_t data; if( 1 == xcb_ewmh_get_wm_name_reply(&obj->EWMH, cookie, &data, NULL) ){ name = QString::fromUtf8(data.strings, data.strings_len); } } if(name.isEmpty()){ //_NET_WM_VISIBLE_NAME xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_name_unchecked(&obj->EWMH, win->id()); if(cookie.sequence != 0){ xcb_ewmh_get_utf8_strings_reply_t data; if( 1 == xcb_ewmh_get_wm_visible_name_reply(&obj->EWMH, cookie, &data, NULL) ){ name = QString::fromUtf8(data.strings, data.strings_len); } } } if(name.isEmpty()){ //Now try the ICCCM standard xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_name_unchecked(QX11Info::connection(), win->id()); xcb_icccm_get_text_property_reply_t reply; if(1 == xcb_icccm_get_wm_name_reply(QX11Info::connection(), cookie, &reply, NULL) ){ name = QString::fromLocal8Bit(reply.name, reply.name_len); xcb_icccm_get_text_property_reply_wipe(&reply); } } win->setProperty(NativeWindow::Name, name); } //end TITLE property if(props.contains(NativeWindow::ShortTitle)){ //Try the EWMH standards first // _NET_WM_ICON_NAME QString name; xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_name_unchecked(&obj->EWMH, win->id()); if(cookie.sequence != 0){ xcb_ewmh_get_utf8_strings_reply_t data; if( 1 == xcb_ewmh_get_wm_icon_name_reply(&obj->EWMH, cookie, &data, NULL) ){ name = QString::fromUtf8(data.strings, data.strings_len); } } if(name.isEmpty()){ //_NET_WM_VISIBLE_ICON_NAME xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_icon_name_unchecked(&obj->EWMH, win->id()); if(cookie.sequence != 0){ xcb_ewmh_get_utf8_strings_reply_t data; if( 1 == xcb_ewmh_get_wm_visible_icon_name_reply(&obj->EWMH, cookie, &data, NULL) ){ name = QString::fromUtf8(data.strings, data.strings_len); } } } if(name.isEmpty()){ //Now try the ICCCM standard xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_icon_name_unchecked(QX11Info::connection(), win->id()); xcb_icccm_get_text_property_reply_t reply; if(1 == xcb_icccm_get_wm_icon_name_reply(QX11Info::connection(), cookie, &reply, NULL) ){ name = QString::fromLocal8Bit(reply.name, reply.name_len); xcb_icccm_get_text_property_reply_wipe(&reply); } } win->setProperty(NativeWindow::ShortTitle, name); } //end SHORTTITLE property if(props.contains(NativeWindow::Icon)){ //See if this is a tray icon first (different routine - entire app window is the icon) QIcon icon; if(win == findTrayWindow(win->id())){ //Tray Icon Window QPixmap pix; //Get the current QScreen (for XCB->Qt conversion) QList scrnlist = QApplication::screens(); //Try to grab the given window directly with Qt for(int i=0; igrabWindow(win->id()); } icon.addPixmap(pix); }else{ //Standard window //Fetch the _NET_WM_ICON for the window and return it as a QIcon xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_unchecked(&obj->EWMH, win->id()); xcb_ewmh_get_wm_icon_reply_t reply; if(1 == xcb_ewmh_get_wm_icon_reply(&obj->EWMH, cookie, &reply, NULL)){ xcb_ewmh_wm_icon_iterator_t iter = xcb_ewmh_get_wm_icon_iterator(&reply); //Just use the first bool done =false; while(!done){ //Now convert the current data into a Qt image // - first 2 elements are width and height (removed via XCB functions) // - data in rows from left to right and top to bottom QImage image(iter.width, iter.height, QImage::Format_ARGB32); //initial setup uint* dat = iter.data; //dat+=2; //remember the first 2 element offset for(int i=0; isetProperty(NativeWindow::Icon, icon); } //end ICON property } // === PUBLIC SLOTS === //These are the slots which are only used by the desktop system itself or the NativeWindowEventFilter void NativeWindowSystem::RegisterVirtualRoot(WId){ } //NativeWindowEventFilter interactions void NativeWindowSystem::NewWindowDetected(WId){ } void NativeWindowSystem::NewTrayWindowDetected(WId){ } void NativeWindowSystem::WindowCloseDetected(WId){ } void NativeWindowSystem::WindowPropertiesChanged(WId, NativeWindow::Property){ } /*void NativeWindowSystem::NewKeyPress(int keycode, WId win){ emit NewInputEvent(); } void NativeWindowSystem::NewKeyRelease(int keycode, WId win){ emit NewInputEvent(); //Convert the native button code into a Qt keycode //Qt::Key key = keycode; //TODO //emit KeyReleaseDetected( key, win); } void NativeWindowSystem::NewMousePress(int buttoncode, WId win){ emit NewInputEvent(); //Convert the native button code into a Qt mouse button code Qt::MouseButton button; switch(buttoncode){ case 1: button = Qt::LeftButton ; break; case 2: button = Qt::MiddleButton ; break; case 3: button = Qt::RightButton ; break; case 4: button = Qt::LeftButton ; break; default: return; //Unhandled button } emit MousePressDetected(button, win); } void NativeWindowSystem::NewMouseRelease(int buttoncode, WId win){ emit NewInputEvent(); //Convert the native button code into a Qt mouse button code Qt::MouseButton button; switch(buttoncode){ case 1: button = Qt::LeftButton ; break; case 2: button = Qt::MiddleButton ; break; case 3: button = Qt::RightButton ; break; case 4: button = Qt::LeftButton ; break; default: return; //Unhandled button } emit MouseReleaseDetected(button, win); }*/ void NativeWindowSystem::CheckDamageID(WId win){ NativeWindow *WIN = findTrayWindow(win); if(WIN!=0){ UpdateWindowProperties(WIN, QList() << NativeWindow::Icon); } } // === PRIVATE SLOTS === //These are the slots which are built-in and automatically connected when a new NativeWindow is created void NativeWindowSystem::RequestPropertiesChange(WId win, QList props, QList vals){ //Find the window object associated with this id bool istraywin = false; //just in case we care later if it is a tray window or a regular window NativeWindow *WIN = findWindow(win); if(WIN==0){ istraywin = true; WIN = findTrayWindow(win); } if(WIN==0){ return; } //invalid window ID - no longer available //Now make any changes as needed } void NativeWindowSystem::RequestClose(WId win){ //Send the window a WM_DELETE_WINDOW message xcb_client_message_event_t event; event.response_type = XCB_CLIENT_MESSAGE; event.format = 32; event.window = win; event.type = obj->ATOMS.value("WM_PROTOCOLS"); event.data.data32[0] = obj->ATOMS.value("WM_DELETE_WINDOW"); event.data.data32[1] = XCB_TIME_CURRENT_TIME; //CurrentTime; event.data.data32[2] = 0; event.data.data32[3] = 0; event.data.data32[4] = 0; xcb_send_event(QX11Info::connection(), 0, win, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event); xcb_flush(QX11Info::connection()); } void NativeWindowSystem::RequestKill(WId win){ xcb_kill_client(QX11Info::connection(), win); } void NativeWindowSystem::RequestPing(WId win){ xcb_ewmh_send_wm_ping(&obj->EWMH, win, XCB_CURRENT_TIME); }