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