]> git.tdb.fi Git - libs/gui.git/blob - source/graphics/x11/display.cpp
Initial XRandR support
[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 }
42
43
44 namespace Msp {
45 namespace Graphics {
46
47 Display::Display(const string &disp_name):
48         primary_monitor(0),
49         priv(new Private)
50 {
51         if(disp_name.empty())
52                 priv->display = XOpenDisplay(0);
53         else
54                 priv->display = XOpenDisplay(disp_name.c_str());
55         if(!priv->display)
56                 throw runtime_error("XOpenDisplay");
57
58         XSetErrorHandler(x_error_handler);
59
60 #ifdef WITH_XRANDR
61         int event_base;
62         int error_base;
63         if(XRRQueryExtension(priv->display, &event_base, &error_base))
64         {
65                 int major, minor;
66                 XRRQueryVersion(priv->display, &major, &minor);
67                 if(major>1 || (major==1 && minor>=2))
68                 {
69                         WindowHandle root = DefaultRootWindow(priv->display);
70                         XRRScreenResources *res = XRRGetScreenResources(priv->display, root);
71                         RROutput primary = XRRGetOutputPrimary(priv->display, root);
72
73                         map<RRMode, XRRModeInfo *> modes_by_id;
74                         for(int i=0; i<res->nmode; ++i)
75                                 modes_by_id[res->modes[i].id] = &res->modes[i];
76
77                         for(int i=0; i<res->noutput; ++i)
78                         {
79                                 XRROutputInfo *output = XRRGetOutputInfo(priv->display, res, res->outputs[i]);
80                                 XRRCrtcInfo *crtc = (output->crtc ? XRRGetCrtcInfo(priv->display, res, output->crtc) : 0);
81
82                                 monitors.push_back(Monitor());
83                                 Monitor &monitor = monitors.back();
84                                 monitor.index = monitors.size()-1;
85                                 priv->monitors.push_back(res->outputs[i]);
86
87                                 if(res->outputs[i]==primary)
88                                         primary_monitor = &monitor;
89
90                                 for(int j=0; j<output->nmode; ++j)
91                                 {
92                                         map<RRMode, XRRModeInfo *>::iterator k = modes_by_id.find(output->modes[j]);
93                                         if(k==modes_by_id.end())
94                                                 continue;
95
96                                         XRRModeInfo *info = k->second;
97
98                                         VideoMode mode(info->width, info->height);
99                                         mode.index = modes.size();
100                                         mode.monitor = &monitor;
101                                         mode.rate = info->dotClock/(info->hTotal*info->vTotal);
102                                         if(find_matching_mode(mode))
103                                                 continue;
104
105                                         modes.push_back(mode);
106                                         priv->modes.push_back(info->id);
107                                         monitor.video_modes.push_back(&modes.back());
108
109                                         if(crtc && info->id==crtc->mode)
110                                                 monitor.desktop_mode = &modes.back();
111                                 }
112
113                                 XRRFreeOutputInfo(output);
114                                 if(crtc)
115                                         XRRFreeCrtcInfo(crtc);
116                         }
117
118                         XRRFreeScreenResources(res);
119                 }
120         }
121 #endif
122 }
123
124 Display::~Display()
125 {
126         XCloseDisplay(priv->display);
127         delete priv;
128 }
129
130 void Display::set_mode(const VideoMode &requested_mode, bool exclusive)
131 {
132 #ifdef WITH_XRANDR
133         const VideoMode *mode = find_matching_mode(requested_mode);
134         if(!mode)
135                 throw unsupported_video_mode(requested_mode);
136
137         WindowHandle root = DefaultRootWindow(priv->display);
138         XRRScreenResources *res = XRRGetScreenResources(priv->display, root);
139         RROutput output = priv->monitors[mode->monitor->index];
140         XRROutputInfo *output_info = XRRGetOutputInfo(priv->display, res, output);
141
142         // Check if the output already has a CRTC and find a free one if it doesn't
143         RRCrtc crtc = output_info->crtc;
144         XRRCrtcInfo *crtc_info = 0;
145         if(crtc)
146                 crtc_info = XRRGetCrtcInfo(priv->display, res, crtc);
147         else
148         {
149                 for(int i=0; i<res->ncrtc; ++i)
150                 {
151                         crtc_info = XRRGetCrtcInfo(priv->display, res, res->crtcs[i]);
152                         if(!crtc_info->noutput)
153                         {
154                                 crtc = res->crtcs[i];
155                                 break;
156                         }
157                         XRRFreeCrtcInfo(crtc_info);
158                 }
159
160                 if(!crtc)
161                 {
162                         XRRFreeOutputInfo(output_info);
163                         throw unsupported_video_mode(requested_mode);
164                 }
165         }
166
167         if(exclusive)
168         {
169                 // Disable other outputs for exclusive mode
170                 for(unsigned i=0; i<priv->monitors.size(); ++i)
171                         if(i!=mode->monitor->index)
172                         {
173                                 XRROutputInfo *o = XRRGetOutputInfo(priv->display, res, priv->monitors[i]);
174                                 if(o->crtc)
175                                         XRRSetCrtcConfig(priv->display, res, o->crtc, CurrentTime, 0, 0, 0, RR_Rotate_0, 0, 0);
176                                 XRRFreeOutputInfo(o);
177                         }
178         }
179
180         XRRSetCrtcConfig(priv->display, res, crtc, CurrentTime, 0, 0, priv->modes[mode->index], RR_Rotate_0, &output, 1);
181
182         XRRFreeOutputInfo(output_info);
183         XRRFreeCrtcInfo(crtc_info);
184         XRRFreeScreenResources(res);
185 #else
186         (void)requested_mode;
187         (void)exclusive;
188         throw runtime_error("no xrandr support");
189 #endif
190 }
191
192 bool Display::process_events()
193 {
194         int pending = XPending(priv->display);
195         if(pending==0)
196                 return false;
197
198         for(; pending--;)
199         {
200                 Window::Event event;
201                 XNextEvent(priv->display, &event.xevent);
202
203                 check_error();
204
205                 map<WindowHandle, Window *>::iterator j = priv->windows.find(event.xevent.xany.window);
206                 if(j!=priv->windows.end())
207                 {
208                         /* Filter keyboard autorepeat.  If this packet is a KeyRelease and
209                         the next one is a KeyPress with the exact same parameters, they
210                         indicate autorepeat and must be dropped. */
211                         if(event.xevent.type==KeyRelease && !j->second->get_keyboard_autorepeat() && pending>0)
212                         {
213                                 XKeyEvent &kev = event.xevent.xkey;
214                                 XEvent ev2;
215                                 XPeekEvent(priv->display, &ev2);
216                                 if(ev2.type==KeyPress)
217                                 {
218                                         XKeyEvent &kev2 = ev2.xkey;
219                                         if(kev2.window==kev.window && kev2.time==kev.time && kev2.keycode==kev.keycode)
220                                         {
221                                                 XNextEvent(priv->display, &ev2);
222                                                 --pending;
223                                                 continue;
224                                         }
225                                 }
226                         }
227
228                         j->second->event(event);
229                 }
230         }
231
232         return true;
233 }
234
235 void Display::check_error()
236 {
237         if(error_flag)
238         {
239                 error_flag = false;
240                 throw runtime_error(error_msg);
241         }
242 }
243
244 } // namespace Msp
245 } // namespace Graphics