1 //===========================================
2 //  Lumina-DE source code
3 //  Copyright (c) 2017, Ken Moore
4 //  Available under the 3-clause BSD license
5 //  See the LICENSE file for full details
6 //===========================================
7 #include "LuminaRandR.h"
8 
9 //#include "X11/extensions/Xrandr.h"
10 
atomToName(xcb_atom_t atom)11 inline QString atomToName(xcb_atom_t atom){
12   xcb_get_atom_name_reply_t *nreply = xcb_get_atom_name_reply(QX11Info::connection(), xcb_get_atom_name_unchecked(QX11Info::connection(), atom), NULL);
13     QString name = QString::fromLocal8Bit(xcb_get_atom_name_name(nreply), xcb_get_atom_name_name_length(nreply));
14     free(nreply);
15   return name;
16 };
17 
18 //More efficient method for converting lots of atoms to strings
atomsToNames(xcb_atom_t * atoms,unsigned int num)19 inline QStringList atomsToNames(xcb_atom_t *atoms, unsigned int num){
20   //qDebug() << "atomsToNames:" << num;
21   QList< xcb_get_atom_name_cookie_t > cookies;
22   //qDebug() << " - Get cookies";
23   for(unsigned int i=0; i<num; i++){ cookies << xcb_get_atom_name_unchecked(QX11Info::connection(), atoms[i]);  }
24   QStringList names;
25   //qDebug() << " - Get names";
26   for(int i=0; i<cookies.length(); i++){
27     xcb_get_atom_name_reply_t *nreply = xcb_get_atom_name_reply(QX11Info::connection(), cookies[i], NULL);
28     if(nreply==0){ continue; }
29       names << QString::fromLocal8Bit(xcb_get_atom_name_name(nreply), xcb_get_atom_name_name_length(nreply));
30     free(nreply);
31   }
32   return names;
33 };
34 
loadScreenInfo(p_objects * p_obj)35 inline bool loadScreenInfo(p_objects *p_obj){
36   //Reset the primary cached values (just in case things error out below and it can't finish)
37   p_obj->current_mode = 0;
38   p_obj->geometry = QRect();
39   p_obj->physicalSizeMM = QSize();
40   p_obj->primary = false;
41   p_obj->modes.clear();
42   p_obj->resolutions.clear();
43   p_obj->crtc = 0;
44 
45   //Get the information associated with the output and save it in the p_objects cache
46   xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(QX11Info::connection(),
47 		xcb_randr_get_output_info_unchecked(QX11Info::connection(), p_obj->output, QX11Info::appTime()),
48 		NULL);
49   if(info==0){ return false; } //bad output value
50   //First read off the information associated with the output itself
51   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)); }
52   p_obj->physicalSizeMM = QSize(info->mm_width, info->mm_height);
53 
54     //Modes
55     int mode_len = xcb_randr_get_output_info_modes_length(info);
56     for(int j=0; j<mode_len; j++){
57       p_obj->modes.append( xcb_randr_get_output_info_modes(info)[j] );
58     }
59     //int pref_len = info->num_preferred;
60     //qDebug() << "Modes:" << p_obj->modes << "Num Preferred:" << pref_len;
61     /*for(int j=0; j<pref_len; j++){
62       p_obj->preferred.append( xcb_randr_get_output_info_preferred(info)[j] );
63     }*/
64   p_obj->crtc = info->crtc;
65   free(info); //done with output_info
66 
67   //Now load the current status of the output (crtc information)
68   xcb_randr_get_crtc_info_reply_t *cinfo = xcb_randr_get_crtc_info_reply(QX11Info::connection(),
69 		xcb_randr_get_crtc_info_unchecked(QX11Info::connection(), p_obj->crtc, QX11Info::appTime()),
70 		NULL);
71   if(cinfo!=0){
72     p_obj->geometry = QRect(cinfo->x, cinfo->y, cinfo->width, cinfo->height);
73     p_obj->current_mode = cinfo->mode;
74     free(cinfo); //done with crtc_info
75   }
76 
77   if(!p_obj->modes.isEmpty()){
78     //And see if this output is currently the primary output
79     xcb_randr_get_output_primary_reply_t *preply = xcb_randr_get_output_primary_reply(QX11Info::connection(),
80 		xcb_randr_get_output_primary_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL);
81 
82     if(preply !=0){
83       p_obj->primary = (preply->output == p_obj->output);
84       free(preply);
85     }
86 
87     //Now load all the screen resources information, and find matches for the current modes
88     xcb_randr_get_screen_resources_reply_t *srreply = xcb_randr_get_screen_resources_reply(QX11Info::connection(),
89 		xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL);
90     if(srreply!=0){
91       for(int i=0; i<xcb_randr_get_screen_resources_modes_length(srreply); i++){
92         xcb_randr_mode_info_t minfo = xcb_randr_get_screen_resources_modes(srreply)[i];
93         if(p_obj->modes.contains(minfo.id)){
94           QSize sz(minfo.width, minfo.height);
95           if(!p_obj->resolutions.contains(sz)){ p_obj->resolutions.append( sz); }
96         }
97       }
98       free(srreply);
99     }
100   }
101   return true;
102 }
103 
104 
modeForResolution(QSize res,QList<xcb_randr_mode_t> modes)105 inline xcb_randr_mode_t modeForResolution(QSize res, QList<xcb_randr_mode_t> modes){
106   xcb_randr_mode_t det_mode = XCB_NONE;
107   xcb_randr_get_screen_resources_reply_t *srreply = xcb_randr_get_screen_resources_reply(QX11Info::connection(),
108 		xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL);
109   if(srreply!=0){
110     unsigned int refreshrate = 0;
111     QSize sz;
112     for(int i=0; i<xcb_randr_get_screen_resources_modes_length(srreply); i++){
113       xcb_randr_mode_info_t minfo = xcb_randr_get_screen_resources_modes(srreply)[i];
114       if(modes.contains(minfo.id)){
115        //qDebug() << "Found mode!" << minfo.id << res << refreshrate;
116         if(res.isNull() && (minfo.width > sz.width() || minfo.height > sz.height()) ){
117           //No resolution requested - pick the largest one
118           //qDebug() << "Found Bigger Mode:" << sz << QSize(minfo.width, minfo.height);
119           sz = QSize(minfo.width, minfo.height);
120           det_mode = minfo.id;
121         }else if(!res.isNull()){
122           sz = QSize(minfo.width, minfo.height);
123            //qDebug() << "Compare Sizes:" << sz << res;
124           if(sz == res && minfo.dot_clock > refreshrate){ det_mode = minfo.id; refreshrate = minfo.dot_clock; }
125         }
126       }
127     }
128     free(srreply);
129   }
130   return det_mode;
131 }
132 
adjustScreenTotal(xcb_randr_crtc_t output,QRect geom,bool addingoutput)133 inline void adjustScreenTotal(xcb_randr_crtc_t output, QRect geom, bool addingoutput){
134   QRect total, mmTotal;
135   xcb_randr_get_screen_resources_reply_t *srreply = xcb_randr_get_screen_resources_reply(QX11Info::connection(),
136 		xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()), NULL);
137   if(srreply!=0){
138     for(int i=0; i<xcb_randr_get_screen_resources_crtcs_length(srreply); i++){
139       xcb_randr_crtc_t crtc = xcb_randr_get_screen_resources_crtcs(srreply)[i];
140       if(output == crtc){
141         //Found the output we are (going) to treat differently
142         if(addingoutput){
143           total = total.united(geom);
144         }
145         //ignore the output if we just removed it
146       }else{
147         //Get the current geometry of this crtc (if available) and add it to the total
148         xcb_randr_get_crtc_info_reply_t *cinfo = xcb_randr_get_crtc_info_reply(QX11Info::connection(),
149 		xcb_randr_get_crtc_info_unchecked(QX11Info::connection(), crtc, QX11Info::appTime()),
150 		NULL);
151         if(cinfo!=0){
152           total = total.united( QRect(cinfo->x, cinfo->y, cinfo->width, cinfo->height) );
153 	   //QSize dpi( qRound((cinfo->width * 25.4)/cinfo->), qRound((p_obj.geometry.height() * 25.4)/p_obj.physicalSizeMM.height() ) );
154           free(cinfo); //done with crtc_info
155         }
156       }
157     }
158     free(srreply);
159   }
160   QSize newRes = total.size();
161   QSize newMM = mmTotal.size();
162   xcb_randr_set_screen_size(QX11Info::connection(), QX11Info::appRootWindow(), newRes.width(), newRes.height(), newMM.width(), newMM.height());
163 }
164 
showOutput(QRect geom,p_objects * p_obj)165 inline bool showOutput(QRect geom, p_objects *p_obj){
166   //if no geom provided, will add as the right-most screen at optimal resolution
167   qDebug() << "Enable Monitor:" << geom;
168   xcb_randr_mode_t mode = modeForResolution(geom.size(), p_obj->modes);
169   if(mode==XCB_NONE){ qDebug() << "[ERROR] Invalid resolution supplied!"; return false; } //invalid resolution for this monitor
170   //qDebug() << " - Found Mode:" << mode;
171   if(p_obj->crtc == 0){
172     //Need to scan for an available crtc to use (turning on a monitor for the first time)
173     xcb_randr_get_screen_resources_reply_t *reply = xcb_randr_get_screen_resources_reply(QX11Info::connection(),
174 		xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()),
175 		NULL);
176     int num = xcb_randr_get_screen_resources_crtcs_length(reply);
177     for(int i=0; i<num && p_obj->crtc==0; i++){
178       xcb_randr_crtc_t crt = xcb_randr_get_screen_resources_crtcs(reply)[i];
179       xcb_randr_get_crtc_info_reply_t *info = xcb_randr_get_crtc_info_reply(QX11Info::connection(),
180 		xcb_randr_get_crtc_info_unchecked(QX11Info::connection(), crt, QX11Info::appTime()),
181 		NULL);
182       //Verify that the output is supported by this crtc
183       QList<xcb_randr_output_t> possible;
184       if(xcb_randr_get_crtc_info_outputs_length(info) < 1){ //make sure it is not already associated with an output
185         int pnum = xcb_randr_get_crtc_info_possible_length(info);
186         for(int p=0; p<pnum; p++){ possible << xcb_randr_get_crtc_info_possible(info)[p]; }
187       }
188       if(possible.contains(p_obj->output)){ p_obj->crtc = crt; }
189       free(info);
190     }
191     free(reply);
192   }
193 if(p_obj->crtc == 0){ qDebug() << "[ERROR] No Available CRTC devices for display"; return false; }
194   //Now need to update the overall session size (if necessary)
195   adjustScreenTotal(p_obj->crtc, geom, true); //adding output at this geometry
196 
197   //qDebug() << " - Using crtc:" << p_obj->crtc;
198   //qDebug() << " - Using mode:" << mode;
199   xcb_randr_output_t outList[1]{ p_obj->output };
200 
201   xcb_randr_set_crtc_config_cookie_t cookie = xcb_randr_set_crtc_config_unchecked(QX11Info::connection(), p_obj->crtc,
202 		XCB_CURRENT_TIME, XCB_CURRENT_TIME, geom.x(), geom.y(), mode, XCB_RANDR_ROTATION_ROTATE_0, 1, outList);
203     //Now check the result of the configuration
204     xcb_randr_set_crtc_config_reply_t *reply = xcb_randr_set_crtc_config_reply(QX11Info::connection(), cookie, NULL);
205     bool ok = false;
206     if(reply!=0){ ok = (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); }
207     free(reply);
208     return ok;
209 }
210 /*
211     //Clones
212     qDebug() << "Number of Clones:" << xcb_randr_get_output_info_clones_length(info);
213     //Properties
214     xcb_randr_list_output_properties_reply_t *pinfo = xcb_randr_list_output_properties_reply(QX11Info::connection(),
215 		xcb_randr_list_output_properties_unchecked(QX11Info::connection(), output),
216 		NULL);
217     int pinfo_len = xcb_randr_list_output_properties_atoms_length(pinfo);
218     qDebug() << "Properties:" << pinfo_len;
219     for(int p=0; p<pinfo_len; p++){
220       xcb_atom_t atom = xcb_randr_list_output_properties_atoms(pinfo)[p];
221       //Property Name
222       QString name = atomToName(atom);
223       //Property Value
224       xcb_randr_query_output_property_reply_t *pvalue = xcb_randr_query_output_property_reply(QX11Info::connection(),
225 		xcb_randr_query_output_property_unchecked(QX11Info::connection(), output, atom),
226 		NULL);
227       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
228       free(pvalue);
229       qDebug() << " -- " << name << "=" << values;
230 
231     }
232 */
233 
234 //FUNCTIONS (do not use typically use manually - use the OutputDeviceList class instead)
OutputDevice(QString id)235 OutputDevice::OutputDevice(QString id){
236   //p_obj = new p_objects();
237   p_obj.name = id;
238   p_obj.primary = false;
239   p_obj.output = 0;
240   bool ok = false;
241   p_obj.output = id.toInt(&ok);
242   if(ok){
243     //output ID number instead
244     p_obj.name.clear();
245   }
246   updateInfoCache();
247 }
248 
~OutputDevice()249 OutputDevice::~OutputDevice(){
250   //delete p_obj;
251 }
252 
253 // INFORMATION FUNCTIONS (simply read from cache)
ID()254 QString OutputDevice::ID(){ return p_obj.name; }
isEnabled()255 bool OutputDevice::isEnabled(){ return !p_obj.geometry.isNull(); }
isPrimary()256 bool OutputDevice::isPrimary(){ return p_obj.primary; }
isConnected()257 bool OutputDevice::isConnected(){ return !p_obj.modes.isEmpty(); }
258 
availableResolutions()259 QList<QSize> OutputDevice::availableResolutions(){ return p_obj.resolutions; }
currentResolution()260 QSize OutputDevice::currentResolution(){ return p_obj.geometry.size(); } //no concept of panning/scaling yet
currentGeometry()261 QRect OutputDevice::currentGeometry(){ return p_obj.geometry; }
physicalSizeMM()262 QSize OutputDevice::physicalSizeMM(){ return p_obj.physicalSizeMM; }
physicalDPI()263 QSize OutputDevice::physicalDPI(){
264   QSize dpi( qRound((p_obj.geometry.width() * 25.4)/p_obj.physicalSizeMM.width()), qRound((p_obj.geometry.height() * 25.4)/p_obj.physicalSizeMM.height() ) );
265   return dpi;
266 }
267 
268 //Modification
setAsPrimary(bool set)269 bool OutputDevice::setAsPrimary(bool set){
270   if(p_obj.primary == set){ return true; } //no change needed
271     if(set){ xcb_randr_set_output_primary (QX11Info::connection(), QX11Info::appRootWindow(), p_obj.output); }
272     p_obj.primary = set; //Only need to push a "set" primary up through XCB - will automatically deactivate the other monitors
273   return true;
274 }
275 
disable()276 bool OutputDevice::disable(){
277   if(p_obj.output!=0 && p_obj.current_mode!=0 && p_obj.crtc!=0){
278     //qDebug() << " - Go ahead";
279     xcb_randr_set_crtc_config_cookie_t cookie = xcb_randr_set_crtc_config_unchecked(QX11Info::connection(), p_obj.crtc,
280 		XCB_CURRENT_TIME, XCB_CURRENT_TIME, 0, 0, XCB_NONE, XCB_RANDR_ROTATION_ROTATE_0, 0, NULL);
281     //Now check the result of the configuration
282     xcb_randr_set_crtc_config_reply_t *reply = xcb_randr_set_crtc_config_reply(QX11Info::connection(), cookie, NULL);
283     if(reply==0){ return false; }
284     bool ok = (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS);
285     free(reply);
286     if(ok){
287       adjustScreenTotal(p_obj.crtc, QRect(), false); //adding output at this geometry
288     }
289     return ok;
290   }
291   return false;
292 }
293 
enable(QRect geom)294 bool OutputDevice::enable(QRect geom){
295   if(this->isEnabled()){ return false; } //already enabled
296   return showOutput(geom, &p_obj);
297 }
298 
changeResolution(QSize res)299 bool OutputDevice::changeResolution(QSize res){
300   if(!this->isEnabled()){ return false; }
301   return showOutput( QRect( p_obj.geometry.topLeft(), res), &p_obj );
302 }
303 
move(QPoint pt)304 bool OutputDevice::move(QPoint pt){
305   if(!this->isEnabled()){ return false; }
306   return showOutput( QRect( pt, p_obj.geometry.size()), &p_obj);
307 }
308 
setGeometry(QRect geom)309 bool OutputDevice::setGeometry(QRect geom){
310   if(!this->isEnabled()){ return false; }
311   return showOutput(geom, &p_obj);
312 }
313 
updateInfoCache()314 void OutputDevice::updateInfoCache(){
315   if(p_obj.output==0){
316       //Only have a name (first run) - need to find the corresponding output for this ID
317       xcb_randr_get_screen_resources_reply_t *reply = xcb_randr_get_screen_resources_reply(QX11Info::connection(),
318 		xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()),
319 		NULL);
320     int outputnum = xcb_randr_get_screen_resources_outputs_length(reply);
321     for(int i=0; i<outputnum; i++){
322       xcb_randr_output_t output = xcb_randr_get_screen_resources_outputs(reply)[i];
323       xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(QX11Info::connection(),
324 		xcb_randr_get_output_info_unchecked(QX11Info::connection(), output, QX11Info::appTime()),
325 		NULL);
326       //Compare names
327       QString name = QString::fromLocal8Bit( (char*) xcb_randr_get_output_info_name(info), xcb_randr_get_output_info_name_length(info));
328       free(info);
329       if(name == p_obj.name){ p_obj.output = output; break; }
330     }
331     free(reply);
332   }
333   if(p_obj.output == 0){ return; } //bad ID/output?
334   loadScreenInfo(&p_obj);
335 }
336 
337 // ============================
338 //             OutputDeviceList
339 // ============================
340 
OutputDeviceList()341 OutputDeviceList::OutputDeviceList(){
342   xcb_randr_get_screen_resources_reply_t *reply = xcb_randr_get_screen_resources_reply(QX11Info::connection(),
343 		xcb_randr_get_screen_resources_unchecked(QX11Info::connection(), QX11Info::appRootWindow()),
344 		NULL);
345   if(reply==0){ return; } //could not get screen information
346   int outputnum = xcb_randr_get_screen_resources_outputs_length(reply);
347   for(int i=0; i<outputnum; i++){
348     xcb_randr_output_t output = xcb_randr_get_screen_resources_outputs(reply)[i];
349     OutputDevice dev(QString::number(output)); //use the raw output integer
350     out_devs.append(dev); //add to the internal list
351   }
352   free(reply);
353 }
354 
~OutputDeviceList()355 OutputDeviceList::~OutputDeviceList(){
356 
357 }
358 
359 //Simplification functions for dealing with multiple monitors
setPrimaryMonitor(QString id)360 void OutputDeviceList::setPrimaryMonitor(QString id){
361   for(int i=0; i<out_devs.length(); i++){
362     out_devs[i].setAsPrimary(out_devs[i].ID() == id);
363   }
364 }
365 
primaryMonitor()366 QString OutputDeviceList::primaryMonitor(){
367   for(int i=0; i<out_devs.length(); i++){
368     if(out_devs[i].isPrimary()){ return out_devs[i].ID(); }
369   }
370   return "";
371 }
372 
disableMonitor(QString id)373 bool OutputDeviceList::disableMonitor(QString id){
374   bool ok = false;
375   for(int i=0; i<out_devs.length(); i++){
376     if(out_devs[i].ID() == id){
377         ok = out_devs[i].disable();
378         out_devs[i].updateInfoCache();
379       break;
380     }
381   }
382   return ok;
383 }
384 
enableMonitor(QString id,QRect geom)385 bool OutputDeviceList::enableMonitor(QString id, QRect geom){
386   bool ok = false;
387   for(int i=0; i<out_devs.length(); i++){
388     if(out_devs[i].ID() == id){
389         ok = out_devs[i].enable(geom);
390         out_devs[i].updateInfoCache();
391       break;
392     }
393   }
394   return ok;
395 }
396