]> git.tdb.fi Git - libs/gui.git/blob - source/graphics/x11/display.cpp
Expose functions for finding video modes from Display
[libs/gui.git] / source / graphics / x11 / display.cpp
1 #include <iostream>
2 #include <X11/Xlib.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 inline Msp::Graphics::VideoRotation rotation_from_sys(Rotation r)
42 {
43         switch(r)
44         {
45         case RR_Rotate_90: return Msp::Graphics::ROTATE_RIGHT;
46         case RR_Rotate_180: return Msp::Graphics::ROTATE_INVERTED;
47         case RR_Rotate_270: return Msp::Graphics::ROTATE_LEFT;
48         default: return Msp::Graphics::ROTATE_NORMAL;
49         }
50 }
51
52 inline Rotation rotation_to_sys(Msp::Graphics::VideoRotation r)
53 {
54         switch(r)
55         {
56         case Msp::Graphics::ROTATE_RIGHT: return RR_Rotate_90;
57         case Msp::Graphics::ROTATE_INVERTED: return RR_Rotate_180;
58         case Msp::Graphics::ROTATE_LEFT: return RR_Rotate_270;
59         default: return RR_Rotate_0;
60         }
61 }
62
63 bool monitor_x_compare(const Msp::Graphics::Monitor &m1, const Msp::Graphics::Monitor &m2)
64 {
65         if(m1.desktop_mode && !m2.desktop_mode)
66                 return true;
67         return m1.x<m2.x;
68 }
69
70 inline unsigned mode_width(const Msp::Graphics::VideoMode &m, Msp::Graphics::VideoRotation r)
71 {
72         if(r==Msp::Graphics::ROTATE_RIGHT || r==Msp::Graphics::ROTATE_LEFT)
73                 return m.height;
74         else
75                 return m.width;
76 }
77
78 }
79
80
81 namespace Msp {
82 namespace Graphics {
83
84 Display::Display(const string &disp_name):
85         primary_monitor(0),
86         priv(new Private)
87 {
88         if(disp_name.empty())
89                 priv->display = XOpenDisplay(0);
90         else
91                 priv->display = XOpenDisplay(disp_name.c_str());
92         if(!priv->display)
93                 throw runtime_error("XOpenDisplay");
94
95         XSetErrorHandler(x_error_handler);
96
97         err_dialog = new ErrorDialog(this);
98
99 #ifdef WITH_XRANDR
100         int event_base;
101         int error_base;
102         if(XRRQueryExtension(priv->display, &event_base, &error_base))
103         {
104                 int major, minor;
105                 XRRQueryVersion(priv->display, &major, &minor);
106                 if(major>1 || (major==1 && minor>=2))
107                 {
108                         WindowHandle root = DefaultRootWindow(priv->display);
109                         XRRScreenResources *res = XRRGetScreenResources(priv->display, root);
110                         RROutput primary = XRRGetOutputPrimary(priv->display, root);
111
112                         map<RRMode, XRRModeInfo *> modes_by_id;
113                         for(int i=0; i<res->nmode; ++i)
114                                 modes_by_id[res->modes[i].id] = &res->modes[i];
115
116                         for(int i=0; i<res->noutput; ++i)
117                         {
118                                 XRROutputInfo *output = XRRGetOutputInfo(priv->display, res, res->outputs[i]);
119                                 XRRCrtcInfo *crtc = (output->crtc ? XRRGetCrtcInfo(priv->display, res, output->crtc) : 0);
120
121                                 monitors.push_back(Monitor());
122                                 Monitor &monitor = monitors.back();
123                                 monitor.index = monitors.size()-1;
124                                 priv->monitors.push_back(res->outputs[i]);
125
126                                 if(crtc)
127                                 {
128                                         monitor.desktop_rotation = rotation_from_sys(crtc->rotation);
129                                         monitor.current_rotation = monitor.desktop_rotation;
130                                         monitor.x = crtc->x;
131                                         monitor.y = crtc->y;
132                                 }
133
134                                 if(res->outputs[i]==primary)
135                                         primary_monitor = &monitor;
136
137                                 for(int j=0; j<output->nmode; ++j)
138                                 {
139                                         map<RRMode, XRRModeInfo *>::iterator k = modes_by_id.find(output->modes[j]);
140                                         if(k==modes_by_id.end())
141                                                 continue;
142
143                                         XRRModeInfo *info = k->second;
144
145                                         VideoMode mode(info->width, info->height);
146                                         mode.index = modes.size();
147                                         mode.monitor = &monitor;
148                                         mode.rate = info->dotClock/(info->hTotal*info->vTotal);
149                                         if(find_mode(mode))
150                                                 continue;
151
152                                         modes.push_back(mode);
153                                         priv->modes.push_back(info->id);
154                                         monitor.video_modes.push_back(&modes.back());
155
156                                         if(crtc && info->id==crtc->mode)
157                                         {
158                                                 monitor.desktop_mode = &modes.back();
159                                                 monitor.current_mode = monitor.desktop_mode;
160                                         }
161                                 }
162
163                                 XRRFreeOutputInfo(output);
164                                 if(crtc)
165                                         XRRFreeCrtcInfo(crtc);
166                         }
167
168                         XRRFreeScreenResources(res);
169
170                         monitors.sort(monitor_x_compare);
171                         Monitor *prev_enabled = 0;
172                         for(list<Monitor>::iterator i=monitors.begin(); i!=monitors.end(); ++i)
173                                 if(i->desktop_mode)
174                                 {
175                                         i->next_left = prev_enabled;
176                                         if(prev_enabled)
177                                                 prev_enabled->next_right = &*i;
178                                         prev_enabled = &*i;
179                                 }
180
181                         if(!primary_monitor || !primary_monitor->desktop_mode)
182                         {
183                                 // XRandR didn't give a sensible primary monitor.  Try to guess one.
184                                 unsigned largest = 0;
185                                 for(list<Monitor>::iterator i=monitors.begin(); i!=monitors.end(); ++i)
186                                         if(i->desktop_mode)
187                                         {
188                                                 unsigned size = i->desktop_mode->width*i->desktop_mode->height;
189                                                 if(size>largest)
190                                                 {
191                                                         largest = size;
192                                                         primary_monitor = &*i;
193                                                 }
194                                         }
195                         }
196                 }
197         }
198 #endif
199 }
200
201 Display::~Display()
202 {
203         XCloseDisplay(priv->display);
204         delete priv;
205         delete err_dialog;
206 }
207
208 void Display::set_mode(const VideoMode &requested_mode, bool exclusive)
209 {
210 #ifdef WITH_XRANDR
211         const VideoMode *mode = find_mode(requested_mode);
212         if(!mode)
213                 throw unsupported_video_mode(requested_mode);
214
215         VideoRotation requested_rotation = requested_mode.rotation;
216         if(requested_rotation==ROTATE_ANY)
217                 requested_rotation = mode->monitor->desktop_rotation;
218
219         WindowHandle root = DefaultRootWindow(priv->display);
220         XRRScreenResources *res = XRRGetScreenResources(priv->display, root);
221         RROutput output = priv->monitors[mode->monitor->index];
222         XRROutputInfo *output_info = XRRGetOutputInfo(priv->display, res, output);
223
224         // Check if the output already has a CRTC and find a free one if it doesn't
225         RRCrtc crtc = output_info->crtc;
226         XRRCrtcInfo *crtc_info = 0;
227         if(crtc)
228                 crtc_info = XRRGetCrtcInfo(priv->display, res, crtc);
229         else
230         {
231                 for(int i=0; i<res->ncrtc; ++i)
232                 {
233                         crtc_info = XRRGetCrtcInfo(priv->display, res, res->crtcs[i]);
234                         if(!crtc_info->noutput)
235                         {
236                                 crtc = res->crtcs[i];
237                                 break;
238                         }
239                         XRRFreeCrtcInfo(crtc_info);
240                 }
241
242                 if(!crtc)
243                 {
244                         XRRFreeOutputInfo(output_info);
245                         throw unsupported_video_mode(requested_mode);
246                 }
247         }
248
249         int x = 0;
250         int y = 0;
251
252         if(exclusive)
253         {
254                 // Disable other outputs for exclusive mode
255                 for(list<Monitor>::iterator i=monitors.begin(); i!=monitors.end(); ++i)
256                         if(&*i!=mode->monitor)
257                         {
258                                 XRROutputInfo *o = XRRGetOutputInfo(priv->display, res, priv->monitors[i->index]);
259                                 if(o->crtc)
260                                         XRRSetCrtcConfig(priv->display, res, o->crtc, CurrentTime, 0, 0, 0, RR_Rotate_0, 0, 0);
261                                 XRRFreeOutputInfo(o);
262
263                                 i->current_mode = 0;
264                                 i->current_rotation = ROTATE_NORMAL;
265                                 i->x = 0;
266                                 i->y = 0;
267                         }
268         }
269         else
270         {
271                 const Monitor *left = mode->monitor->next_left;
272                 while(left && !left->current_mode)
273                         left = left->next_left;
274
275                 if(left)
276                 {
277                         x = left->x+mode_width(*left->current_mode, left->current_rotation);
278                         y = left->y;
279                 }
280         }
281
282         XRRSetCrtcConfig(priv->display, res, crtc, CurrentTime, x, y, priv->modes[mode->index], rotation_to_sys(requested_rotation), &output, 1);
283
284         list<Monitor>::iterator i;
285         for(i=monitors.begin(); i!=monitors.end(); ++i)
286                 if(&*i==mode->monitor)
287                 {
288                         i->current_mode = mode;
289                         i->current_rotation = requested_rotation;
290                         i->x = x;
291                         i->y = y;
292
293                         x += mode_width(*mode, requested_rotation);
294                         ++i;
295                         break;
296                 }
297
298         for(; i!=monitors.end(); ++i)
299                 if(i->current_mode)
300                 {
301                         XRROutputInfo *o = XRRGetOutputInfo(priv->display, res, priv->monitors[i->index]);
302                         XRRSetCrtcConfig(priv->display, res, o->crtc, CurrentTime, x, y, priv->modes[i->current_mode->index], rotation_to_sys(i->current_rotation), &priv->monitors[i->index], 1);
303                         XRRFreeOutputInfo(o);
304
305                         i->x = x;
306                         i->y = y;
307
308                         x += mode_width(*i->current_mode, i->current_rotation);
309                 }
310
311         XRRFreeOutputInfo(output_info);
312         XRRFreeCrtcInfo(crtc_info);
313         XRRFreeScreenResources(res);
314 #else
315         (void)requested_mode;
316         (void)exclusive;
317         throw runtime_error("no xrandr support");
318 #endif
319 }
320
321 bool Display::process_events()
322 {
323         int pending = XPending(priv->display);
324         if(pending==0)
325                 return false;
326
327         for(; pending--;)
328         {
329                 Window::Event event;
330                 XNextEvent(priv->display, &event.xevent);
331
332                 check_error();
333
334                 map<WindowHandle, Window *>::iterator j = priv->windows.find(event.xevent.xany.window);
335                 if(j!=priv->windows.end())
336                 {
337                         /* Filter keyboard autorepeat.  If this packet is a KeyRelease and
338                         the next one is a KeyPress with the exact same parameters, they
339                         indicate autorepeat and must be dropped. */
340                         if(event.xevent.type==KeyRelease && !j->second->get_keyboard_autorepeat() && pending>0)
341                         {
342                                 XKeyEvent &kev = event.xevent.xkey;
343                                 XEvent ev2;
344                                 XPeekEvent(priv->display, &ev2);
345                                 if(ev2.type==KeyPress)
346                                 {
347                                         XKeyEvent &kev2 = ev2.xkey;
348                                         if(kev2.window==kev.window && kev2.time==kev.time && kev2.keycode==kev.keycode)
349                                         {
350                                                 XNextEvent(priv->display, &ev2);
351                                                 --pending;
352                                                 continue;
353                                         }
354                                 }
355                         }
356
357                         j->second->event(event);
358                 }
359         }
360
361         return true;
362 }
363
364 void Display::check_error()
365 {
366         if(error_flag)
367         {
368                 error_flag = false;
369                 throw runtime_error(error_msg);
370         }
371 }
372
373 } // namespace Msp
374 } // namespace Graphics