//=========================================== // Lumina-DE source code // Copyright (c) 2017, Ken Moore // Available under the 3-clause BSD license // See the LICENSE file for full details //=========================================== #include "LuminaRandR.h" //#include "X11/extensions/Xrandr.h" inline QString atomToName(xcb_atom_t atom){ xcb_get_atom_name_reply_t *nreply = xcb_get_atom_name_reply(QX11Info::connection(), xcb_get_atom_name_unchecked(QX11Info::connection(), atom), NULL); QString name = QString::fromLocal8Bit(xcb_get_atom_name_name(nreply), xcb_get_atom_name_name_length(nreply)); free(nreply); return name; }; //More efficient method for converting lots of atoms to strings inline QStringList atomsToNames(xcb_atom_t *atoms, unsigned int num){ //qDebug() << "atomsToNames:" << num; QList< xcb_get_atom_name_cookie_t > cookies; //qDebug() << " - Get cookies"; for(unsigned int i=0; i<num; i++){ cookies << xcb_get_atom_name_unchecked(QX11Info::connection(), atoms[i]); } QStringList names; //qDebug() << " - Get names"; for(int i=0; i<cookies.length(); i++){ xcb_get_atom_name_reply_t *nreply = xcb_get_atom_name_reply(QX11Info::connection(), cookies[i], NULL); if(nreply==0){ continue; } names << QString::fromLocal8Bit(xcb_get_atom_name_name(nreply), xcb_get_atom_name_name_length(nreply)); free(nreply); } return names; }; inline bool loadScreenInfo(p_objects *p_obj){ //Reset the primary cached values (just in case things error out below and it can't finish) p_obj->current_mode = 0; p_obj->geometry = QRect(); p_obj->physicalSizeMM = QSize(); p_obj->primary = false; p_obj->modes.clear(); p_obj->resolutions.clear(); p_obj->crtc = 0; //Get the information associated with the output and save it in the p_objects cache xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(QX11Info::connection(), xcb_randr_get_output_info_unchecked(QX11Info::connection(), p_obj->output, QX11Info::appTime()), NULL); if(info==0){ return false; } //bad output value //First read off the information associated with the output itself if(p_obj->name.isEmpty()){ p_obj->name = QString::fromLocal8Bit( (char*) xcb_randr_get_output_info_name(info), xcb_randr_get_output_info_name_length(info)); } p_obj->physicalSizeMM = QSize(info->mm_width, info->mm_height); //Modes int mode_len = xcb_randr_get_output_info_modes_length(info); for(int j=0; j<mode_len; j++){ p_obj->modes.append( xcb_randr_get_output_info_modes(info)[j] ); } //int pref_len = info->num_preferred; //qDebug() << "Modes:" << p_obj->modes << "Num Preferred:" << pref_len; /*for(int j=0; j<pref_len; j++){ p_obj->preferred.append( xcb_randr_get_output_info_preferred(info)[j] ); }*/ p_obj->crtc = info->crtc; free(info); //done with output_info //Now load the current status of the output (crtc information) xcb_randr_get_crtc_info_reply_t *cinfo = xcb_randr_get_crtc_info_reply(QX11Info::connection(), xcb_randr_get_crtc_info_unchecked(QX11Info::connection(), p_obj->crtc, QX11Info::appTime()), NULL); if(cinfo!=0){ p_obj->geometry = QRect(cinfo->x, cinfo->y, cinfo->width, cinfo->height); p_obj->current_mode = cinfo->mode; free(cinfo); //done with crtc_info } if(!p_obj->modes.isEmpty()){ //And see if this output is currently the primary output xcb_randr_get_output_primary_reply_t *preply = xcb_randr_get_output_primary_reply(QX11Info::connection(), xcb_randr_get_output_primary_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL); if(preply !=0){ p_obj->primary = (preply->output == p_obj->output); free(preply); } //Now load all the screen resources information, and find matches for the current modes xcb_randr_get_screen_resources_reply_t *srreply = xcb_randr_get_screen_resources_reply(QX11Info::connection(), xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL); if(srreply!=0){ for(int i=0; i<xcb_randr_get_screen_resources_modes_length(srreply); i++){ xcb_randr_mode_info_t minfo = xcb_randr_get_screen_resources_modes(srreply)[i]; if(p_obj->modes.contains(minfo.id)){ QSize sz(minfo.width, minfo.height); if(!p_obj->resolutions.contains(sz)){ p_obj->resolutions.append( sz); } } } free(srreply); } } return true; } inline xcb_randr_mode_t modeForResolution(QSize res, QList<xcb_randr_mode_t> modes){ xcb_randr_mode_t det_mode = XCB_NONE; xcb_randr_get_screen_resources_reply_t *srreply = xcb_randr_get_screen_resources_reply(QX11Info::connection(), xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL); if(srreply!=0){ unsigned int refreshrate = 0; QSize sz; for(int i=0; i<xcb_randr_get_screen_resources_modes_length(srreply); i++){ xcb_randr_mode_info_t minfo = xcb_randr_get_screen_resources_modes(srreply)[i]; if(modes.contains(minfo.id)){ //qDebug() << "Found mode!" << minfo.id << res << refreshrate; if(res.isNull() && (minfo.width > sz.width() || minfo.height > sz.height()) ){ //No resolution requested - pick the largest one //qDebug() << "Found Bigger Mode:" << sz << QSize(minfo.width, minfo.height); sz = QSize(minfo.width, minfo.height); det_mode = minfo.id; }else if(!res.isNull()){ sz = QSize(minfo.width, minfo.height); //qDebug() << "Compare Sizes:" << sz << res; if(sz == res && minfo.dot_clock > refreshrate){ det_mode = minfo.id; refreshrate = minfo.dot_clock; } } } } free(srreply); } return det_mode; } inline void adjustScreenTotal(xcb_randr_crtc_t output, QRect geom, bool addingoutput){ QRect total, mmTotal; xcb_randr_get_screen_resources_reply_t *srreply = xcb_randr_get_screen_resources_reply(QX11Info::connection(), xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL); if(srreply!=0){ for(int i=0; i<xcb_randr_get_screen_resources_crtcs_length(srreply); i++){ xcb_randr_crtc_t crtc = xcb_randr_get_screen_resources_crtcs(srreply)[i]; if(output == crtc){ //Found the output we are (going) to treat differently if(addingoutput){ total = total.united(geom); } //ignore the output if we just removed it }else{ //Get the current geometry of this crtc (if available) and add it to the total xcb_randr_get_crtc_info_reply_t *cinfo = xcb_randr_get_crtc_info_reply(QX11Info::connection(), xcb_randr_get_crtc_info_unchecked(QX11Info::connection(), crtc, QX11Info::appTime()), NULL); if(cinfo!=0){ total = total.united( QRect(cinfo->x, cinfo->y, cinfo->width, cinfo->height) ); //QSize dpi( qRound((cinfo->width * 25.4)/cinfo->), qRound((p_obj.geometry.height() * 25.4)/p_obj.physicalSizeMM.height() ) ); free(cinfo); //done with crtc_info } } } free(srreply); } QSize newRes = total.size(); QSize newMM = mmTotal.size(); xcb_randr_set_screen_size(QX11Info::connection(), QX11Info::appRootWindow(), newRes.width(), newRes.height(), newMM.width(), newMM.height()); } inline bool showOutput(QRect geom, p_objects *p_obj){ //if no geom provided, will add as the right-most screen at optimal resolution qDebug() << "Enable Monitor:" << geom; xcb_randr_mode_t mode = modeForResolution(geom.size(), p_obj->modes); if(mode==XCB_NONE){ qDebug() << "[ERROR] Invalid resolution supplied!"; return false; } //invalid resolution for this monitor //qDebug() << " - Found Mode:" << mode; if(p_obj->crtc == 0){ //Need to scan for an available crtc to use (turning on a monitor for the first time) xcb_randr_get_screen_resources_reply_t *reply = xcb_randr_get_screen_resources_reply(QX11Info::connection(), xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL); int num = xcb_randr_get_screen_resources_crtcs_length(reply); for(int i=0; i<num && p_obj->crtc==0; i++){ xcb_randr_crtc_t crt = xcb_randr_get_screen_resources_crtcs(reply)[i]; xcb_randr_get_crtc_info_reply_t *info = xcb_randr_get_crtc_info_reply(QX11Info::connection(), xcb_randr_get_crtc_info_unchecked(QX11Info::connection(), crt, QX11Info::appTime()), NULL); //Verify that the output is supported by this crtc QList<xcb_randr_output_t> possible; if(xcb_randr_get_crtc_info_outputs_length(info) < 1){ //make sure it is not already associated with an output int pnum = xcb_randr_get_crtc_info_possible_length(info); for(int p=0; p<pnum; p++){ possible << xcb_randr_get_crtc_info_possible(info)[p]; } } if(possible.contains(p_obj->output)){ p_obj->crtc = crt; } free(info); } free(reply); } if(p_obj->crtc == 0){ qDebug() << "[ERROR] No Available CRTC devices for display"; return false; } //Now need to update the overall session size (if necessary) adjustScreenTotal(p_obj->crtc, geom, true); //adding output at this geometry //qDebug() << " - Using crtc:" << p_obj->crtc; //qDebug() << " - Using mode:" << mode; xcb_randr_output_t outList[1]{ p_obj->output }; xcb_randr_set_crtc_config_cookie_t cookie = xcb_randr_set_crtc_config_unchecked(QX11Info::connection(), p_obj->crtc, XCB_CURRENT_TIME, XCB_CURRENT_TIME, geom.x(), geom.y(), mode, XCB_RANDR_ROTATION_ROTATE_0, 1, outList); //Now check the result of the configuration xcb_randr_set_crtc_config_reply_t *reply = xcb_randr_set_crtc_config_reply(QX11Info::connection(), cookie, NULL); bool ok = false; if(reply!=0){ ok = (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); } free(reply); return ok; } /* //Clones qDebug() << "Number of Clones:" << xcb_randr_get_output_info_clones_length(info); //Properties xcb_randr_list_output_properties_reply_t *pinfo = xcb_randr_list_output_properties_reply(QX11Info::connection(), xcb_randr_list_output_properties_unchecked(QX11Info::connection(), output), NULL); int pinfo_len = xcb_randr_list_output_properties_atoms_length(pinfo); qDebug() << "Properties:" << pinfo_len; for(int p=0; p<pinfo_len; p++){ xcb_atom_t atom = xcb_randr_list_output_properties_atoms(pinfo)[p]; //Property Name QString name = atomToName(atom); //Property Value xcb_randr_query_output_property_reply_t *pvalue = xcb_randr_query_output_property_reply(QX11Info::connection(), xcb_randr_query_output_property_unchecked(QX11Info::connection(), output, atom), NULL); QStringList values = atomsToNames ( (xcb_atom_t*) xcb_randr_query_output_property_valid_values(pvalue), xcb_randr_query_output_property_valid_values_length(pvalue) ); //need to read values free(pvalue); qDebug() << " -- " << name << "=" << values; } */ //FUNCTIONS (do not use typically use manually - use the OutputDeviceList class instead) OutputDevice::OutputDevice(QString id){ //p_obj = new p_objects(); p_obj.name = id; p_obj.primary = false; p_obj.output = 0; bool ok = false; p_obj.output = id.toInt(&ok); if(ok){ //output ID number instead p_obj.name.clear(); } updateInfoCache(); } OutputDevice::~OutputDevice(){ //delete p_obj; } // INFORMATION FUNCTIONS (simply read from cache) QString OutputDevice::ID(){ return p_obj.name; } bool OutputDevice::isEnabled(){ return !p_obj.geometry.isNull(); } bool OutputDevice::isPrimary(){ return p_obj.primary; } bool OutputDevice::isConnected(){ return !p_obj.modes.isEmpty(); } QList<QSize> OutputDevice::availableResolutions(){ return p_obj.resolutions; } QSize OutputDevice::currentResolution(){ return p_obj.geometry.size(); } //no concept of panning/scaling yet QRect OutputDevice::currentGeometry(){ return p_obj.geometry; } QSize OutputDevice::physicalSizeMM(){ return p_obj.physicalSizeMM; } QSize OutputDevice::physicalDPI(){ QSize dpi( qRound((p_obj.geometry.width() * 25.4)/p_obj.physicalSizeMM.width()), qRound((p_obj.geometry.height() * 25.4)/p_obj.physicalSizeMM.height() ) ); return dpi; } //Modification bool OutputDevice::setAsPrimary(bool set){ if(p_obj.primary == set){ return true; } //no change needed if(set){ xcb_randr_set_output_primary (QX11Info::connection(), QX11Info::appRootWindow(), p_obj.output); } p_obj.primary = set; //Only need to push a "set" primary up through XCB - will automatically deactivate the other monitors return true; } bool OutputDevice::disable(){ if(p_obj.output!=0 && p_obj.current_mode!=0 && p_obj.crtc!=0){ //qDebug() << " - Go ahead"; xcb_randr_set_crtc_config_cookie_t cookie = xcb_randr_set_crtc_config_unchecked(QX11Info::connection(), p_obj.crtc, XCB_CURRENT_TIME, XCB_CURRENT_TIME, 0, 0, XCB_NONE, XCB_RANDR_ROTATION_ROTATE_0, 0, NULL); //Now check the result of the configuration xcb_randr_set_crtc_config_reply_t *reply = xcb_randr_set_crtc_config_reply(QX11Info::connection(), cookie, NULL); if(reply==0){ return false; } bool ok = (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); free(reply); if(ok){ adjustScreenTotal(p_obj.crtc, QRect(), false); //adding output at this geometry } return ok; } return false; } bool OutputDevice::enable(QRect geom){ if(this->isEnabled()){ return false; } //already enabled return showOutput(geom, &p_obj); } bool OutputDevice::changeResolution(QSize res){ if(!this->isEnabled()){ return false; } return showOutput( QRect( p_obj.geometry.topLeft(), res), &p_obj ); } bool OutputDevice::move(QPoint pt){ if(!this->isEnabled()){ return false; } return showOutput( QRect( pt, p_obj.geometry.size()), &p_obj); } bool OutputDevice::setGeometry(QRect geom){ if(!this->isEnabled()){ return false; } return showOutput(geom, &p_obj); } void OutputDevice::updateInfoCache(){ if(p_obj.output==0){ //Only have a name (first run) - need to find the corresponding output for this ID xcb_randr_get_screen_resources_reply_t *reply = xcb_randr_get_screen_resources_reply(QX11Info::connection(), xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL); int outputnum = xcb_randr_get_screen_resources_outputs_length(reply); for(int i=0; i<outputnum; i++){ xcb_randr_output_t output = xcb_randr_get_screen_resources_outputs(reply)[i]; xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(QX11Info::connection(), xcb_randr_get_output_info_unchecked(QX11Info::connection(), output, QX11Info::appTime()), NULL); //Compare names QString name = QString::fromLocal8Bit( (char*) xcb_randr_get_output_info_name(info), xcb_randr_get_output_info_name_length(info)); free(info); if(name == p_obj.name){ p_obj.output = output; break; } } free(reply); } if(p_obj.output == 0){ return; } //bad ID/output? loadScreenInfo(&p_obj); } // ============================ // OutputDeviceList // ============================ OutputDeviceList::OutputDeviceList(){ xcb_randr_get_screen_resources_reply_t *reply = xcb_randr_get_screen_resources_reply(QX11Info::connection(), xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL); if(reply==0){ return; } //could not get screen information int outputnum = xcb_randr_get_screen_resources_outputs_length(reply); for(int i=0; i<outputnum; i++){ xcb_randr_output_t output = xcb_randr_get_screen_resources_outputs(reply)[i]; OutputDevice dev(QString::number(output)); //use the raw output integer out_devs.append(dev); //add to the internal list } free(reply); } OutputDeviceList::~OutputDeviceList(){ } //Simplification functions for dealing with multiple monitors void OutputDeviceList::setPrimaryMonitor(QString id){ for(int i=0; i<out_devs.length(); i++){ out_devs[i].setAsPrimary(out_devs[i].ID() == id); } } QString OutputDeviceList::primaryMonitor(){ for(int i=0; i<out_devs.length(); i++){ if(out_devs[i].isPrimary()){ return out_devs[i].ID(); } } return ""; } bool OutputDeviceList::disableMonitor(QString id){ bool ok = false; for(int i=0; i<out_devs.length(); i++){ if(out_devs[i].ID() == id){ ok = out_devs[i].disable(); out_devs[i].updateInfoCache(); break; } } return ok; } bool OutputDeviceList::enableMonitor(QString id, QRect geom){ bool ok = false; for(int i=0; i<out_devs.length(); i++){ if(out_devs[i].ID() == id){ ok = out_devs[i].enable(geom); out_devs[i].updateInfoCache(); break; } } return ok; }