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