]> git.tdb.fi Git - libs/gui.git/blob - source/graphics/x11/display.cpp
fd6048566d3faec09b0108440e7b4f008a985a33
[libs/gui.git] / source / graphics / x11 / display.cpp
1 #include "display.h"
2 #include "display_private.h"
3 #include <X11/Xlib.h>
4 #include <X11/Xatom.h>
5 #ifdef WITH_XRANDR
6 #include <X11/extensions/Xrandr.h>
7 #endif
8 #include <msp/io/print.h>
9 #include <msp/strings/format.h>
10 #include <msp/strings/lexicalcast.h>
11
12 using namespace std;
13
14 namespace {
15
16 bool error_flag = false;
17 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         priv(new Private)
88 {
89         if(disp_name.empty())
90                 priv->display = XOpenDisplay(nullptr);
91         else
92                 priv->display = XOpenDisplay(disp_name.c_str());
93         if(!priv->display)
94                 throw runtime_error("XOpenDisplay");
95
96         XSetErrorHandler(x_error_handler);
97
98         priv->root_window = DefaultRootWindow(priv->display);
99
100         err_dialog = new ErrorDialog(this);
101
102 #ifdef WITH_XRANDR
103         int event_base;
104         int error_base;
105         if(XRRQueryExtension(priv->display, &event_base, &error_base))
106         {
107                 int major, minor;
108                 XRRQueryVersion(priv->display, &major, &minor);
109                 if(major>1 || (major==1 && minor>=2))
110                 {
111                         XRRScreenResources *res = XRRGetScreenResources(priv->display, priv->root_window);
112                         RROutput primary = XRRGetOutputPrimary(priv->display, priv->root_window);
113                         Atom edid_prop = XInternAtom(priv->display, RR_PROPERTY_RANDR_EDID, true);
114
115                         map<RRMode, XRRModeInfo *> modes_by_id;
116                         for(int i=0; i<res->nmode; ++i)
117                                 modes_by_id[res->modes[i].id] = &res->modes[i];
118
119                         for(int i=0; i<res->noutput; ++i)
120                         {
121                                 XRROutputInfo *output = XRRGetOutputInfo(priv->display, res, res->outputs[i]);
122                                 XRRCrtcInfo *crtc = (output->crtc ? XRRGetCrtcInfo(priv->display, res, output->crtc) : nullptr);
123
124                                 monitors.push_back(Monitor());
125                                 Monitor &monitor = monitors.back();
126                                 monitor.index = monitors.size()-1;
127                                 monitor.name.assign(output->name, output->nameLen);
128                                 priv->monitors.push_back(res->outputs[i]);
129
130                                 if(edid_prop)
131                                 {
132                                         Atom prop_type;
133                                         int prop_format;
134                                         unsigned long length;
135                                         unsigned long overflow;
136                                         unsigned char *edid = nullptr;
137                                         XRRGetOutputProperty(priv->display, res->outputs[i], edid_prop, 0, 32, false, false, XA_INTEGER, &prop_type, &prop_format, &length, &overflow, &edid);
138                                         if(prop_type==XA_INTEGER && prop_format==8)
139                                         {
140                                                 for(unsigned j=0; j<4; ++j)
141                                                 {
142                                                         unsigned offset = 54+j*18;
143                                                         if(edid[offset]==0 && edid[offset+1]==0 && edid[offset+3]==0xFC)
144                                                         {
145                                                                 unsigned k;
146                                                                 for(k=0; (k<13 && edid[offset+5+k]!=0x0A); ++k) ;
147                                                                 monitor.name.assign(reinterpret_cast<char *>(edid+offset+5), k);
148                                                         }
149                                                 }
150                                         }
151                                         XFree(edid);
152                                 }
153
154                                 if(crtc)
155                                 {
156                                         monitor.desktop_settings.rotation = rotation_from_sys(crtc->rotation);
157                                         monitor.desktop_settings.x = crtc->x;
158                                         monitor.desktop_settings.y = crtc->y;
159                                 }
160
161                                 if(res->outputs[i]==primary)
162                                         primary_monitor = &monitor;
163
164                                 for(int j=0; j<output->nmode; ++j)
165                                 {
166                                         auto k = modes_by_id.find(output->modes[j]);
167                                         if(k==modes_by_id.end())
168                                                 continue;
169
170                                         XRRModeInfo *info = k->second;
171
172                                         VideoMode mode(info->width, info->height);
173                                         mode.index = modes.size();
174                                         mode.monitor = &monitor;
175                                         mode.rate = static_cast<float>(info->dotClock)/(info->hTotal*info->vTotal);
176                                         if(find_mode(mode, 0.01f))
177                                                 continue;
178
179                                         modes.push_back(mode);
180                                         priv->modes.push_back(info->id);
181                                         monitor.video_modes.push_back(&modes.back());
182
183                                         if(crtc && info->id==crtc->mode)
184                                                 monitor.desktop_settings.mode = &modes.back();
185                                 }
186
187                                 monitor.current_settings = monitor.desktop_settings;
188
189                                 XRRFreeOutputInfo(output);
190                                 if(crtc)
191                                         XRRFreeCrtcInfo(crtc);
192                         }
193
194                         XRRFreeScreenResources(res);
195
196                         monitors.sort(monitor_x_compare);
197                         Monitor *prev_enabled = nullptr;
198                         for(Monitor &m: monitors)
199                                 if(m.desktop_settings.mode)
200                                 {
201                                         m.next_left = prev_enabled;
202                                         if(prev_enabled)
203                                                 prev_enabled->next_right = &m;
204                                         prev_enabled = &m;
205                                 }
206
207                         if(!primary_monitor || !primary_monitor->desktop_settings.mode)
208                         {
209                                 // XRandR didn't give a sensible primary monitor.  Try to guess one.
210                                 unsigned largest = 0;
211                                 for(Monitor &m: monitors)
212                                         if(const VideoMode *desktop_mode = m.desktop_settings.mode)
213                                         {
214                                                 unsigned size = desktop_mode->width*desktop_mode->height;
215                                                 if(size>largest)
216                                                 {
217                                                         largest = size;
218                                                         primary_monitor = &m;
219                                                 }
220                                         }
221                         }
222                 }
223         }
224 #endif
225 }
226
227 Display::~Display()
228 {
229         XCloseDisplay(priv->display);
230         delete priv;
231         delete err_dialog;
232 }
233
234 void Display::set_mode(const VideoMode &requested_mode, bool exclusive)
235 {
236 #ifdef WITH_XRANDR
237         const VideoMode *mode = find_mode(requested_mode);
238         if(!mode)
239                 throw unsupported_video_mode(requested_mode);
240
241         VideoRotation requested_rotation = requested_mode.rotation;
242         if(requested_rotation==ROTATE_ANY)
243                 requested_rotation = mode->monitor->desktop_settings.rotation;
244
245         XRRScreenResources *res = XRRGetScreenResources(priv->display, priv->root_window);
246         RROutput output = priv->monitors[mode->monitor->index];
247         XRROutputInfo *output_info = XRRGetOutputInfo(priv->display, res, output);
248
249         // Check if the output already has a CRTC and find a free one if it doesn't
250         RRCrtc crtc = output_info->crtc;
251         XRRCrtcInfo *crtc_info = nullptr;
252         if(crtc)
253                 crtc_info = XRRGetCrtcInfo(priv->display, res, crtc);
254         else
255         {
256                 for(int i=0; i<res->ncrtc; ++i)
257                 {
258                         crtc_info = XRRGetCrtcInfo(priv->display, res, res->crtcs[i]);
259                         if(!crtc_info->noutput)
260                         {
261                                 crtc = res->crtcs[i];
262                                 break;
263                         }
264                         XRRFreeCrtcInfo(crtc_info);
265                 }
266
267                 if(!crtc)
268                 {
269                         XRRFreeOutputInfo(output_info);
270                         throw unsupported_video_mode(requested_mode);
271                 }
272         }
273
274         /* Due to the semantics of find_mode, the mode's monitor pointer must point
275         to one of the elements of the monitors list, which is non-const here. */
276         Monitor *monitor = const_cast<Monitor *>(mode->monitor);
277
278         if(exclusive)
279         {
280                 monitor->current_settings.mode = mode;
281                 monitor->current_settings.rotation = requested_rotation;
282                 monitor->current_settings.x = 0;
283                 monitor->current_settings.y = 0;
284
285                 // Disable other outputs for exclusive mode
286                 for(Monitor &m: monitors)
287                         if(&m!=mode->monitor)
288                         {
289                                 XRROutputInfo *o = XRRGetOutputInfo(priv->display, res, priv->monitors[m.index]);
290                                 if(o->crtc)
291                                         XRRSetCrtcConfig(priv->display, res, o->crtc, CurrentTime, 0, 0, 0, RR_Rotate_0, nullptr, 0);
292                                 XRRFreeOutputInfo(o);
293
294                                 m.current_settings.mode = nullptr;
295                                 m.current_settings.rotation = ROTATE_NORMAL;
296                                 m.current_settings.x = 0;
297                                 m.current_settings.y = 0;
298                         }
299         }
300         else
301         {
302                 monitor->current_settings.x = monitor->desktop_settings.x;
303                 monitor->current_settings.y = monitor->desktop_settings.y;
304         }
305
306         RRMode mode_id = priv->modes[mode->index];
307         int x = monitor->current_settings.x;
308         int y = monitor->current_settings.y;
309         Rotation sys_rot = rotation_to_sys(requested_rotation);
310         XRRSetCrtcConfig(priv->display, res, crtc, CurrentTime, x, y, mode_id, sys_rot, &output, 1);
311
312         XRRFreeOutputInfo(output_info);
313         XRRFreeCrtcInfo(crtc_info);
314         XRRFreeScreenResources(res);
315 #else
316         (void)requested_mode;
317         (void)exclusive;
318         throw runtime_error("no xrandr support");
319 #endif
320 }
321
322 bool Display::process_events()
323 {
324         int pending = XPending(priv->display);
325         if(pending==0)
326                 return false;
327
328         for(; pending--;)
329         {
330                 Window::Event event;
331                 XNextEvent(priv->display, &event.xevent);
332
333                 check_error();
334
335                 auto j = priv->windows.find(event.xevent.xany.window);
336                 if(j!=priv->windows.end())
337                 {
338                         /* Filter keyboard autorepeat.  If this packet is a KeyRelease and
339                         the next one is a KeyPress with the exact same parameters, they
340                         indicate autorepeat and must be dropped. */
341                         if(event.xevent.type==KeyRelease && !j->second->get_keyboard_autorepeat() && pending>0)
342                         {
343                                 XKeyEvent &kev = event.xevent.xkey;
344                                 XEvent ev2;
345                                 XPeekEvent(priv->display, &ev2);
346                                 if(ev2.type==KeyPress)
347                                 {
348                                         XKeyEvent &kev2 = ev2.xkey;
349                                         if(kev2.window==kev.window && kev2.time==kev.time && kev2.keycode==kev.keycode)
350                                         {
351                                                 XNextEvent(priv->display, &ev2);
352                                                 --pending;
353                                                 continue;
354                                         }
355                                 }
356                         }
357
358                         j->second->event(event);
359                 }
360         }
361
362         return true;
363 }
364
365 void Display::check_error()
366 {
367         if(error_flag)
368         {
369                 error_flag = false;
370                 throw runtime_error(error_msg);
371         }
372 }
373
374 } // namespace Msp
375 } // namespace Graphics