]> git.tdb.fi Git - libs/gui.git/blob - source/graphics/x11/display.cpp
Refactor storage of monitor settings
[libs/gui.git] / source / graphics / x11 / display.cpp
1 #include <X11/Xlib.h>
2 #include <X11/Xatom.h>
3 #ifdef WITH_XRANDR
4 #include <X11/extensions/Xrandr.h>
5 #endif
6 #include <msp/io/print.h>
7 #include <msp/strings/format.h>
8 #include <msp/strings/lexicalcast.h>
9 #include "display.h"
10 #include "display_private.h"
11
12 using namespace std;
13
14 namespace {
15
16 bool error_flag = false;
17 std::string error_msg;
18
19 int x_error_handler(Display *display, XErrorEvent *event)
20 {
21         char err[128];
22         XGetErrorText(display, event->error_code, err, sizeof(err));
23
24         string request_code = Msp::lexical_cast<string, int>(event->request_code);
25         char req[128];
26         XGetErrorDatabaseText(display, "XRequest", request_code.c_str(), request_code.c_str(), req, sizeof(req));
27
28         string msg = Msp::format("Request %s failed with %s [%08X]", req, err, event->resourceid);
29         if(error_flag)
30                 Msp::IO::print(Msp::IO::cerr, "Discarding error: %s\n", msg);
31         else
32         {
33                 Msp::IO::print(Msp::IO::cerr, "%s\n", msg);
34                 error_msg = msg;
35                 error_flag = true;
36         }
37
38         return 0;
39 }
40
41 #ifdef WITH_XRANDR
42 inline Msp::Graphics::VideoRotation rotation_from_sys(Rotation r)
43 {
44         switch(r)
45         {
46         case RR_Rotate_90: return Msp::Graphics::ROTATE_RIGHT;
47         case RR_Rotate_180: return Msp::Graphics::ROTATE_INVERTED;
48         case RR_Rotate_270: return Msp::Graphics::ROTATE_LEFT;
49         default: return Msp::Graphics::ROTATE_NORMAL;
50         }
51 }
52
53 inline Rotation rotation_to_sys(Msp::Graphics::VideoRotation r)
54 {
55         switch(r)
56         {
57         case Msp::Graphics::ROTATE_RIGHT: return RR_Rotate_90;
58         case Msp::Graphics::ROTATE_INVERTED: return RR_Rotate_180;
59         case Msp::Graphics::ROTATE_LEFT: return RR_Rotate_270;
60         default: return RR_Rotate_0;
61         }
62 }
63
64 inline bool monitor_x_compare(const Msp::Graphics::Monitor &m1, const Msp::Graphics::Monitor &m2)
65 {
66         if(m1.desktop_settings.mode && !m2.desktop_settings.mode)
67                 return true;
68         return m1.desktop_settings.x<m2.desktop_settings.x;
69 }
70 #endif
71
72 inline unsigned mode_width(const Msp::Graphics::VideoMode &m, Msp::Graphics::VideoRotation r)
73 {
74         if(r==Msp::Graphics::ROTATE_RIGHT || r==Msp::Graphics::ROTATE_LEFT)
75                 return m.height;
76         else
77                 return m.width;
78 }
79
80 }
81
82
83 namespace Msp {
84 namespace Graphics {
85
86 Display::Display(const string &disp_name):
87         primary_monitor(0),
88         priv(new Private),
89         focus_window(0)
90 {
91         if(disp_name.empty())
92                 priv->display = XOpenDisplay(0);
93         else
94                 priv->display = XOpenDisplay(disp_name.c_str());
95         if(!priv->display)
96                 throw runtime_error("XOpenDisplay");
97
98         XSetErrorHandler(x_error_handler);
99
100         priv->root_window = DefaultRootWindow(priv->display);
101
102         err_dialog = new ErrorDialog(this);
103
104 #ifdef WITH_XRANDR
105         int event_base;
106         int error_base;
107         if(XRRQueryExtension(priv->display, &event_base, &error_base))
108         {
109                 int major, minor;
110                 XRRQueryVersion(priv->display, &major, &minor);
111                 if(major>1 || (major==1 && minor>=2))
112                 {
113                         XRRScreenResources *res = XRRGetScreenResources(priv->display, priv->root_window);
114                         RROutput primary = XRRGetOutputPrimary(priv->display, priv->root_window);
115                         Atom edid_prop = XInternAtom(priv->display, RR_PROPERTY_RANDR_EDID, true);
116
117                         map<RRMode, XRRModeInfo *> modes_by_id;
118                         for(int i=0; i<res->nmode; ++i)
119                                 modes_by_id[res->modes[i].id] = &res->modes[i];
120
121                         for(int i=0; i<res->noutput; ++i)
122                         {
123                                 XRROutputInfo *output = XRRGetOutputInfo(priv->display, res, res->outputs[i]);
124                                 XRRCrtcInfo *crtc = (output->crtc ? XRRGetCrtcInfo(priv->display, res, output->crtc) : 0);
125
126                                 monitors.push_back(Monitor());
127                                 Monitor &monitor = monitors.back();
128                                 monitor.index = monitors.size()-1;
129                                 monitor.name.assign(output->name, output->nameLen);
130                                 priv->monitors.push_back(res->outputs[i]);
131
132                                 if(edid_prop)
133                                 {
134                                         Atom prop_type;
135                                         int prop_format;
136                                         unsigned long length;
137                                         unsigned long overflow;
138                                         unsigned char *edid = 0;
139                                         XRRGetOutputProperty(priv->display, res->outputs[i], edid_prop, 0, 32, false, false, XA_INTEGER, &prop_type, &prop_format, &length, &overflow, &edid);
140                                         if(prop_type==XA_INTEGER && prop_format==8)
141                                         {
142                                                 for(unsigned j=0; j<4; ++j)
143                                                 {
144                                                         unsigned offset = 54+j*18;
145                                                         if(edid[offset]==0 && edid[offset+1]==0 && edid[offset+3]==0xFC)
146                                                         {
147                                                                 unsigned k;
148                                                                 for(k=0; (k<13 && edid[offset+5+k]!=0x0A); ++k) ;
149                                                                 monitor.name.assign(reinterpret_cast<char *>(edid+offset+5), k);
150                                                         }
151                                                 }
152                                         }
153                                         XFree(edid);
154                                 }
155
156                                 if(crtc)
157                                 {
158                                         monitor.desktop_settings.rotation = rotation_from_sys(crtc->rotation);
159                                         monitor.desktop_settings.x = crtc->x;
160                                         monitor.desktop_settings.y = crtc->y;
161                                 }
162
163                                 if(res->outputs[i]==primary)
164                                         primary_monitor = &monitor;
165
166                                 for(int j=0; j<output->nmode; ++j)
167                                 {
168                                         map<RRMode, XRRModeInfo *>::iterator k = modes_by_id.find(output->modes[j]);
169                                         if(k==modes_by_id.end())
170                                                 continue;
171
172                                         XRRModeInfo *info = k->second;
173
174                                         VideoMode mode(info->width, info->height);
175                                         mode.index = modes.size();
176                                         mode.monitor = &monitor;
177                                         mode.rate = static_cast<float>(info->dotClock)/(info->hTotal*info->vTotal);
178                                         if(find_mode(mode, 0.01f))
179                                                 continue;
180
181                                         modes.push_back(mode);
182                                         priv->modes.push_back(info->id);
183                                         monitor.video_modes.push_back(&modes.back());
184
185                                         if(crtc && info->id==crtc->mode)
186                                                 monitor.desktop_settings.mode = &modes.back();
187                                 }
188
189                                 monitor.current_settings = monitor.desktop_settings;
190
191                                 XRRFreeOutputInfo(output);
192                                 if(crtc)
193                                         XRRFreeCrtcInfo(crtc);
194                         }
195
196                         XRRFreeScreenResources(res);
197
198                         monitors.sort(monitor_x_compare);
199                         Monitor *prev_enabled = 0;
200                         for(list<Monitor>::iterator i=monitors.begin(); i!=monitors.end(); ++i)
201                                 if(i->desktop_settings.mode)
202                                 {
203                                         i->next_left = prev_enabled;
204                                         if(prev_enabled)
205                                                 prev_enabled->next_right = &*i;
206                                         prev_enabled = &*i;
207                                 }
208
209                         if(!primary_monitor || !primary_monitor->desktop_settings.mode)
210                         {
211                                 // XRandR didn't give a sensible primary monitor.  Try to guess one.
212                                 unsigned largest = 0;
213                                 for(list<Monitor>::iterator i=monitors.begin(); i!=monitors.end(); ++i)
214                                         if(const VideoMode *desktop_mode = i->desktop_settings.mode)
215                                         {
216                                                 unsigned size = desktop_mode->width*desktop_mode->height;
217                                                 if(size>largest)
218                                                 {
219                                                         largest = size;
220                                                         primary_monitor = &*i;
221                                                 }
222                                         }
223                         }
224                 }
225         }
226 #endif
227 }
228
229 Display::~Display()
230 {
231         XCloseDisplay(priv->display);
232         delete priv;
233         delete err_dialog;
234 }
235
236 void Display::set_mode(const VideoMode &requested_mode, bool exclusive)
237 {
238 #ifdef WITH_XRANDR
239         const VideoMode *mode = find_mode(requested_mode);
240         if(!mode)
241                 throw unsupported_video_mode(requested_mode);
242
243         VideoRotation requested_rotation = requested_mode.rotation;
244         if(requested_rotation==ROTATE_ANY)
245                 requested_rotation = mode->monitor->desktop_settings.rotation;
246
247         XRRScreenResources *res = XRRGetScreenResources(priv->display, priv->root_window);
248         RROutput output = priv->monitors[mode->monitor->index];
249         XRROutputInfo *output_info = XRRGetOutputInfo(priv->display, res, output);
250
251         // Check if the output already has a CRTC and find a free one if it doesn't
252         RRCrtc crtc = output_info->crtc;
253         XRRCrtcInfo *crtc_info = 0;
254         if(crtc)
255                 crtc_info = XRRGetCrtcInfo(priv->display, res, crtc);
256         else
257         {
258                 for(int i=0; i<res->ncrtc; ++i)
259                 {
260                         crtc_info = XRRGetCrtcInfo(priv->display, res, res->crtcs[i]);
261                         if(!crtc_info->noutput)
262                         {
263                                 crtc = res->crtcs[i];
264                                 break;
265                         }
266                         XRRFreeCrtcInfo(crtc_info);
267                 }
268
269                 if(!crtc)
270                 {
271                         XRRFreeOutputInfo(output_info);
272                         throw unsupported_video_mode(requested_mode);
273                 }
274         }
275
276         int x = 0;
277         int y = 0;
278
279         if(exclusive)
280         {
281                 // Disable other outputs for exclusive mode
282                 for(list<Monitor>::iterator i=monitors.begin(); i!=monitors.end(); ++i)
283                         if(&*i!=mode->monitor)
284                         {
285                                 XRROutputInfo *o = XRRGetOutputInfo(priv->display, res, priv->monitors[i->index]);
286                                 if(o->crtc)
287                                         XRRSetCrtcConfig(priv->display, res, o->crtc, CurrentTime, 0, 0, 0, RR_Rotate_0, 0, 0);
288                                 XRRFreeOutputInfo(o);
289
290                                 i->current_settings.mode = 0;
291                                 i->current_settings.rotation = ROTATE_NORMAL;
292                                 i->current_settings.x = 0;
293                                 i->current_settings.y = 0;
294                         }
295         }
296         else
297         {
298                 const Monitor *left = mode->monitor->next_left;
299                 while(left && !left->current_settings.mode)
300                         left = left->next_left;
301
302                 if(left)
303                 {
304                         const Monitor::Settings &cs = left->current_settings;
305                         x = cs.x+mode_width(*cs.mode, cs.rotation);
306                         y = cs.y;
307                 }
308         }
309
310         XRRSetCrtcConfig(priv->display, res, crtc, CurrentTime, x, y, priv->modes[mode->index], rotation_to_sys(requested_rotation), &output, 1);
311
312         list<Monitor>::iterator i;
313         for(i=monitors.begin(); i!=monitors.end(); ++i)
314                 if(&*i==mode->monitor)
315                 {
316                         i->current_settings.mode = mode;
317                         i->current_settings.rotation = requested_rotation;
318                         i->current_settings.x = x;
319                         i->current_settings.y = y;
320
321                         x += mode_width(*mode, requested_rotation);
322                         ++i;
323                         break;
324                 }
325
326         for(; i!=monitors.end(); ++i)
327                 if(i->current_settings.mode)
328                 {
329                         XRROutputInfo *o = XRRGetOutputInfo(priv->display, res, priv->monitors[i->index]);
330                         XRRSetCrtcConfig(priv->display, res, o->crtc, CurrentTime, x, y, priv->modes[i->current_settings.mode->index], rotation_to_sys(i->current_settings.rotation), &priv->monitors[i->index], 1);
331
332                         XRRPanning panning;
333                         panning.timestamp = CurrentTime;
334                         panning.left = x;
335                         panning.top = y;
336                         panning.width = i->current_settings.mode->width;
337                         panning.height = i->current_settings.mode->height;
338                         panning.track_left = panning.left;
339                         panning.track_top = panning.top;
340                         panning.track_width = panning.width;
341                         panning.track_height = panning.height;
342                         panning.border_left = 0;
343                         panning.border_top = 0;
344                         panning.border_right = 0;
345                         panning.border_bottom = 0;
346                         XRRSetPanning(priv->display, res, o->crtc, &panning);
347
348                         XRRFreeOutputInfo(o);
349
350                         i->current_settings.x = x;
351                         i->current_settings.y = y;
352
353                         x += mode_width(*i->current_settings.mode, i->current_settings.rotation);
354                 }
355
356         XRRFreeOutputInfo(output_info);
357         XRRFreeCrtcInfo(crtc_info);
358         XRRFreeScreenResources(res);
359 #else
360         (void)requested_mode;
361         (void)exclusive;
362         throw runtime_error("no xrandr support");
363 #endif
364 }
365
366 bool Display::process_events()
367 {
368         int pending = XPending(priv->display);
369         if(pending==0)
370                 return false;
371
372         for(; pending--;)
373         {
374                 Window::Event event;
375                 XNextEvent(priv->display, &event.xevent);
376
377                 check_error();
378
379                 map<WindowHandle, Window *>::iterator j = priv->windows.find(event.xevent.xany.window);
380                 if(j!=priv->windows.end())
381                 {
382                         /* Filter keyboard autorepeat.  If this packet is a KeyRelease and
383                         the next one is a KeyPress with the exact same parameters, they
384                         indicate autorepeat and must be dropped. */
385                         if(event.xevent.type==KeyRelease && !j->second->get_keyboard_autorepeat() && pending>0)
386                         {
387                                 XKeyEvent &kev = event.xevent.xkey;
388                                 XEvent ev2;
389                                 XPeekEvent(priv->display, &ev2);
390                                 if(ev2.type==KeyPress)
391                                 {
392                                         XKeyEvent &kev2 = ev2.xkey;
393                                         if(kev2.window==kev.window && kev2.time==kev.time && kev2.keycode==kev.keycode)
394                                         {
395                                                 XNextEvent(priv->display, &ev2);
396                                                 --pending;
397                                                 continue;
398                                         }
399                                 }
400                         }
401
402                         j->second->event(event);
403                 }
404         }
405
406         return true;
407 }
408
409 void Display::check_error()
410 {
411         if(error_flag)
412         {
413                 error_flag = false;
414                 throw runtime_error(error_msg);
415         }
416 }
417
418 } // namespace Msp
419 } // namespace Graphics