1 /*  This file is part of the KDE project
2  *    Copyright (C) 2010 Lukas Tinkl <ltinkl@redhat.com>
3  *    Copyright (C) 2015 Kai Uwe Broulik <kde@privat.broulik.de>
4  *
5  *    This library is free software; you can redistribute it and/or
6  *    modify it under the terms of the GNU Library General Public
7  *    License version 2 as published by the Free Software Foundation.
8  *
9  *    This library is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *    Library General Public License for more details.
13  *
14  *    You should have received a copy of the GNU Library General Public License
15  *    along with this library; see the file COPYING.LIB.  If not, write to
16  *    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  *    Boston, MA 02110-1301, USA.
18  *
19  */
20 
21 
22 #include <QX11Info>
23 #include <QDebug>
24 
25 #include "xrandrbrightness.h"
26 
XRandrBrightness()27 XRandrBrightness::XRandrBrightness()
28 {
29     if (!QX11Info::isPlatformX11()) {
30         return;
31     }
32     ScopedCPointer<xcb_randr_query_version_reply_t> versionReply(xcb_randr_query_version_reply(QX11Info::connection(),
33         xcb_randr_query_version(QX11Info::connection(), 1, 2),
34     nullptr));
35 
36     if (!versionReply) {
37         qDebug() << "RandR Query version returned null";
38         return;
39     }
40 
41     if (versionReply->major_version < 1 || (versionReply->major_version == 1 && versionReply->minor_version < 2)) {
42         qDebug() << "RandR version" << versionReply->major_version << "." << versionReply->minor_version << " too old";
43         return;
44     }
45     ScopedCPointer<xcb_intern_atom_reply_t> backlightReply(xcb_intern_atom_reply(QX11Info::connection(),
46         xcb_intern_atom (QX11Info::connection(), 1, strlen("Backlight"), "Backlight"),
47     nullptr));
48 
49     if (!backlightReply) {
50         qDebug() << "Intern Atom for Backlight returned null";
51         return;
52     }
53 
54     m_backlight = backlightReply->atom;
55 
56     if (m_backlight == XCB_NONE) {
57         qDebug() << "No outputs have backlight property";
58         //return;
59     }
60     xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(QX11Info::connection()));
61     if (!iter.rem) {
62         qDebug() << "XCB Screen Roots Iterator rem was null";
63         return;
64     }
65 
66     xcb_screen_t *screen = iter.data;
67     xcb_window_t root = screen->root;
68 
69     m_resources.reset(xcb_randr_get_screen_resources_current_reply(QX11Info::connection(),
70         xcb_randr_get_screen_resources_current(QX11Info::connection(), root)
71     , nullptr));
72 
73     if (!m_resources) {
74         qDebug() << "RANDR Get Screen Resources returned null";
75         return;
76     }
77 }
78 
79 
80 
backlight_get_with_range(xcb_randr_output_t output,long & value,long & min,long & max) const81 bool XRandrBrightness::backlight_get_with_range(xcb_randr_output_t output, long &value, long &min, long &max) const {
82     long cur = backlight_get(output);
83     if (cur == -1) {
84        return false;
85     }
86 
87     ScopedCPointer<xcb_randr_query_output_property_reply_t> propertyReply(xcb_randr_query_output_property_reply(QX11Info::connection(),
88         xcb_randr_query_output_property(QX11Info::connection(), output, m_backlight)
89     , nullptr));
90 
91     if (!propertyReply) {
92         return false;
93     }
94 
95     if (propertyReply->range && xcb_randr_query_output_property_valid_values_length(propertyReply.data()) == 2) {
96         int32_t *values = xcb_randr_query_output_property_valid_values(propertyReply.data());
97         value = cur;
98         min = values[0];
99         max = values[1];
100         return true;
101     }
102 
103     return false;
104 }
105 
backlight_get(xcb_randr_output_t output) const106 long XRandrBrightness::backlight_get(xcb_randr_output_t output) const
107 {
108     ScopedCPointer<xcb_randr_get_output_property_reply_t> propertyReply;
109     long value;
110 
111     if (m_backlight != XCB_ATOM_NONE) {
112         propertyReply.reset(xcb_randr_get_output_property_reply(QX11Info::connection(),
113             xcb_randr_get_output_property(QX11Info::connection(), output, m_backlight, XCB_ATOM_NONE, 0, 4, 0, 0)
114         , nullptr));
115 
116         if (!propertyReply) {
117             return -1;
118         }
119     }
120 
121     if (!propertyReply || propertyReply->type != XCB_ATOM_INTEGER || propertyReply->num_items != 1 || propertyReply->format != 32) {
122         value = -1;
123     } else {
124         value = *(reinterpret_cast<long *>(xcb_randr_get_output_property_data(propertyReply.data())));
125     }
126     return value;
127 }
128 
backlight_set(xcb_randr_output_t output,long value)129 void XRandrBrightness::backlight_set(xcb_randr_output_t output, long value)
130 {
131     xcb_randr_change_output_property(QX11Info::connection(), output, m_backlight, XCB_ATOM_INTEGER,
132                                      32, XCB_PROP_MODE_REPLACE,
133                                      1, reinterpret_cast<unsigned char *>(&value));
134 }
135 
136 
gamma_brightness_get(xcb_randr_output_t output)137 float XRandrBrightness::gamma_brightness_get(xcb_randr_output_t output)
138 {
139     xcb_generic_error_t  *error;
140 
141     xcb_randr_get_output_info_cookie_t output_info_cookie = xcb_randr_get_output_info (QX11Info::connection(), output, 0);
142     ScopedCPointer<xcb_randr_get_output_info_reply_t> output_info(xcb_randr_get_output_info_reply (QX11Info::connection(), output_info_cookie, &error));
143     if(error != nullptr)
144     {
145         qDebug() << "Error getting output_info";
146         return -1;
147     }
148     if(output_info == nullptr)
149     {
150         qDebug() << "Error: output_info is null";
151         return -1;
152     }
153     // xcb_randr_get_output_info_reply_t tiene como elemento crtc
154     xcb_randr_get_crtc_gamma_cookie_t gamma_cookie = xcb_randr_get_crtc_gamma_unchecked (QX11Info::connection(), output_info->crtc);
155     ScopedCPointer<xcb_randr_get_crtc_gamma_reply_t> gamma_reply(xcb_randr_get_crtc_gamma_reply (QX11Info::connection(), gamma_cookie, &error));
156     if(error != nullptr)
157     {
158         qDebug() << "Error getting gamma_reply";
159         return -1;
160     }
161     if(gamma_reply == nullptr)
162     {
163         qDebug() << "Error: gamma_reply is null";
164         return -1;
165     }
166     uint16_t * red = xcb_randr_get_crtc_gamma_red (gamma_reply.data());
167     if(red == nullptr)
168     {
169         qDebug() << "Error: red is null";
170         return -1;
171     }
172     int red_length = xcb_randr_get_crtc_gamma_red_length(gamma_reply.data());
173 
174     // uint16_t *green = xcb_randr_get_crtc_gamma_green (gamma_reply);
175     // if(green == NULL)
176     // {
177     //     qDebug() << "Error: green is null";
178     //     return -1;
179     // }
180     // uint16_t *blue = xcb_randr_get_crtc_gamma_blue (gamma_reply);
181     // if(blue == NULL)
182     // {
183     //     qDebug() << "Error: blue is null";
184     //     return -1;
185     // }
186 
187     float brightness = (float)red[red_length-1]/65535.0;
188     return brightness;
189 }
190 
gamma_brightness_set(xcb_randr_output_t output,float percent)191 void XRandrBrightness::gamma_brightness_set(xcb_randr_output_t output, float percent)
192 {
193     xcb_generic_error_t  *error;
194 
195     xcb_randr_get_output_info_cookie_t output_info_cookie = xcb_randr_get_output_info (QX11Info::connection(), output, 0);
196     ScopedCPointer<xcb_randr_get_output_info_reply_t> output_info(xcb_randr_get_output_info_reply (QX11Info::connection(), output_info_cookie, &error));
197     if(error != nullptr)
198     {
199         qDebug() << "Error getting output_info";
200         return;
201     }
202     if(output_info == nullptr)
203     {
204         qDebug() << "Error: output_info is null";
205         return;
206     }
207     // xcb_randr_get_output_info_reply_t tiene como elemento crtc
208     xcb_randr_get_crtc_gamma_cookie_t gamma_cookie = xcb_randr_get_crtc_gamma_unchecked (QX11Info::connection(), output_info->crtc);
209     ScopedCPointer<xcb_randr_get_crtc_gamma_reply_t> gamma_reply(xcb_randr_get_crtc_gamma_reply (QX11Info::connection(), gamma_cookie, &error));
210     if(error != nullptr)
211     {
212         qDebug() << "Error getting gamma_reply";
213         return;
214     }
215     if(gamma_reply == nullptr)
216     {
217         qDebug() << "Error: gamma_reply is null";
218         return;
219     }
220     uint16_t *red = xcb_randr_get_crtc_gamma_red (gamma_reply.data());
221     if(red == nullptr)
222     {
223         qDebug() << "Error: red is null";
224         return;
225     }
226     int red_length = xcb_randr_get_crtc_gamma_red_length(gamma_reply.data());
227     uint16_t *green = xcb_randr_get_crtc_gamma_green (gamma_reply.data());
228     if(green == nullptr)
229     {
230         qDebug() << "Error: green is null";
231         return;
232     }
233     uint16_t *blue = xcb_randr_get_crtc_gamma_blue (gamma_reply.data());
234     if(blue == nullptr)
235     {
236         qDebug() << "Error: blue is null";
237         return;
238     }
239 
240     float max_gamma = 65535*percent;
241     for(int i=0;i<red_length;i++)
242     {
243         int value = qMin((int)(((float)i/(float)(red_length-1))*max_gamma),65535);
244         green[i] = blue[i] = red[i] = value;
245     }
246     xcb_randr_set_crtc_gamma (QX11Info::connection(), output_info->crtc, red_length, red, green, blue);
247 }
248 
249 
getMonitorsInfo()250 QList<MonitorInfo> XRandrBrightness::getMonitorsInfo()
251 {
252     QList<MonitorInfo> monitors;
253 
254     if (!m_resources) {
255         return monitors;
256     }
257 
258     auto *outputs = xcb_randr_get_screen_resources_current_outputs(m_resources.data());
259     for (int i = 0; i < m_resources->num_outputs; ++i) {
260         xcb_randr_output_t output = outputs[i];
261 
262         xcb_generic_error_t  *error;
263 
264         xcb_randr_get_output_info_cookie_t output_info_cookie = xcb_randr_get_output_info (QX11Info::connection(), output, 0);
265         ScopedCPointer <xcb_randr_get_output_info_reply_t> output_info(xcb_randr_get_output_info_reply (QX11Info::connection(), output_info_cookie, &error));
266         if(error != nullptr)
267         {
268             qDebug() << "Error getting output_info";
269             continue;
270         }
271         if(output_info == nullptr)
272         {
273             qDebug() << "Error: output_info is null";
274             continue;
275         }
276 
277         QString name = QString::fromUtf8((const char *) xcb_randr_get_output_info_name(output_info.data()), output_info->name_len);
278 
279 
280         qDebug() << "Found output:" << name;
281 
282 
283         // Is connected?
284         if ( (xcb_randr_connection_t)(output_info->connection) != XCB_RANDR_CONNECTION_CONNECTED )
285         {
286             qDebug() << "Output is not connected";
287             continue; // This output is not connected. Check other
288         }
289 
290         // Is enabled?
291         if( output_info->crtc == 0)
292         {
293             qDebug() << "Crtc is not null. Output not enabled.";
294             continue;
295         }
296         xcb_randr_get_crtc_info_cookie_t crtc_info_cookie = xcb_randr_get_crtc_info_unchecked (QX11Info::connection(), output_info->crtc, 0);
297         ScopedCPointer<xcb_randr_get_crtc_info_reply_t> crtc_info(xcb_randr_get_crtc_info_reply (QX11Info::connection(), crtc_info_cookie, &error));
298         if(error != nullptr)
299         {
300             qDebug() << "Error getting output_info";
301             continue;
302         }
303         if(crtc_info == nullptr)
304         {
305             qDebug() << "Error: output_info is null";
306             continue;
307         }
308         if( crtc_info->mode == XCB_NONE )
309         {
310             qDebug() << "No modes. Output not enabled.";
311             continue;
312         }
313 
314         // Output is connected and enabled. Get data:
315         bool backlightIsSuported = false;
316         long cur, min, max, backlight_max = -1;
317         if (backlight_get(output) != -1)
318         {
319             if (backlight_get_with_range(output, cur, min, max))
320             {
321                 backlightIsSuported = true;
322                 backlight_max = max - min;
323             }
324         }
325 
326         MonitorInfo monitor((int)output, name, backlight_max);
327 
328         if(backlightIsSuported)
329             monitor.setBacklight(cur-min);
330 
331         monitor.setBrightness(gamma_brightness_get(output));
332 
333         qDebug() << "Output:" << name << "added";
334         monitors.append(monitor);
335 
336     }
337 
338     return monitors;
339 }
340 
setMonitorsSettings(QList<MonitorInfo> monitors)341 void XRandrBrightness::setMonitorsSettings(QList<MonitorInfo> monitors)
342 {
343     if (!m_resources) {
344         return;
345     }
346 
347     auto *outputs = xcb_randr_get_screen_resources_current_outputs(m_resources.data());
348     for (int i = 0; i < m_resources->num_outputs; ++i) {
349         xcb_randr_output_t output = outputs[i];
350 
351         xcb_generic_error_t  *error;
352 
353         xcb_randr_get_output_info_cookie_t output_info_cookie = xcb_randr_get_output_info (QX11Info::connection(), output, 0);
354         ScopedCPointer<xcb_randr_get_output_info_reply_t> output_info(xcb_randr_get_output_info_reply (QX11Info::connection(), output_info_cookie, &error));
355         if(error != nullptr)
356         {
357             qDebug() << "Error getting output_info";
358             continue;
359         }
360         if(output_info == nullptr)
361         {
362             qDebug() << "Error: output_info is null";
363             continue;
364         }
365 
366         // Is connected?
367         if ( (xcb_randr_connection_t)(output_info->connection) != XCB_RANDR_CONNECTION_CONNECTED )
368             continue; // This output is not connected. Check other
369 
370         // Is enabled?
371         if( output_info->crtc == 0)
372             continue;
373         xcb_randr_get_crtc_info_cookie_t crtc_info_cookie = xcb_randr_get_crtc_info_unchecked (QX11Info::connection(), output_info->crtc, 0);
374         ScopedCPointer<xcb_randr_get_crtc_info_reply_t> crtc_info(xcb_randr_get_crtc_info_reply (QX11Info::connection(), crtc_info_cookie, &error));
375         if(error != nullptr)
376             continue;
377         if(crtc_info == nullptr || crtc_info->mode == XCB_NONE )
378             continue;
379 
380         QString name = QString::fromUtf8((const char *) xcb_randr_get_output_info_name(output_info.data()), output_info->name_len);
381 
382         // Output is connected and enabled. Get data:
383         bool backlightIsSuported = false;
384         long cur, min, max, backlight_value = 0;
385         if (backlight_get(output) != -1)
386         {
387             if (backlight_get_with_range(output, cur, min, max))
388             {
389                 backlightIsSuported = true;
390                 backlight_value = cur - min;
391             }
392         }
393         float brightness_value = gamma_brightness_get(output);
394 
395         // Compare output info with settings and set it.
396         for(const MonitorInfo &monitor: monitors)
397         {
398             //qDebug() << "[XRandrBrightness::setMonitorsSettings]" << monitor.id() << (int)output << monitor.name() << name ;
399             if(monitor.id() == (int)output && monitor.name() == name)
400             {
401                 // Set settings
402                 if(backlightIsSuported && monitor.backlight() != backlight_value)
403                     backlight_set(output, min+monitor.backlight());
404                 if(monitor.brightness() != brightness_value)
405                     gamma_brightness_set(output, monitor.brightness());
406                 break;
407             }
408         }
409     }
410 }
411 
412