1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2008-2010 Richard Hughes <richard@hughsie.com>
4  * Copyright (C) 2012-2021 MATE Developers
5  *
6  * Licensed under the GNU General Public License Version 2
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <time.h>
30 #include <errno.h>
31 
32 #include <X11/Xatom.h>
33 #include <X11/Xlib.h>
34 #include <X11/extensions/Xrandr.h>
35 #include <gdk/gdkx.h>
36 #include <gtk/gtk.h>
37 #include <string.h>
38 #include <sys/time.h>
39 #include <sys/types.h>
40 #ifdef HAVE_UNISTD_H
41 #include <unistd.h>
42 #endif /* HAVE_UNISTD_H */
43 
44 #include "egg-discrete.h"
45 
46 #include "gpm-brightness.h"
47 #include "gpm-common.h"
48 #include "gpm-marshal.h"
49 
50 #define GPM_SOLE_SETTER_USE_CACHE	TRUE	/* this may be insanity */
51 
52 struct GpmBrightnessPrivate
53 {
54 	gboolean		 has_changed_events;
55 	gboolean		 cache_trusted;
56 	guint			 cache_percentage;
57 	guint			 last_set_hw;
58 	Atom			 backlight;
59 	Display			*dpy;
60 	GdkWindow		*root_window;
61 	guint			 shared_value;
62 	gboolean		 has_extension;
63 	gboolean		 hw_changed;
64 	/* A cache of XRRScreenResources is used as XRRGetScreenResources is expensive */
65 	GPtrArray		*resources;
66 	gint			 extension_levels;
67 	gint			 extension_current;
68 };
69 
70 enum {
71 	BRIGHTNESS_CHANGED,
72 	LAST_SIGNAL
73 };
74 
75 typedef enum {
76 	ACTION_BACKLIGHT_GET,
77 	ACTION_BACKLIGHT_SET,
78 	ACTION_BACKLIGHT_INC,
79 	ACTION_BACKLIGHT_DEC
80 } GpmXRandROp;
81 
82 G_DEFINE_TYPE_WITH_PRIVATE (GpmBrightness, gpm_brightness, G_TYPE_OBJECT)
83 
84 static guint signals [LAST_SIGNAL] = { 0 };
85 static gpointer gpm_brightness_object = NULL;
86 
87 /**
88  * gpm_brightness_helper_strtoint:
89  * @text: The text to be converted
90  * @value: The return numeric return value
91  *
92  * Convert a string to a signed integer value in a safe way.
93  *
94  * Return value: %TRUE if the string was converted correctly
95  **/
96 static gboolean
gpm_brightness_helper_strtoint(const gchar * text,gint * value)97 gpm_brightness_helper_strtoint (const gchar *text, gint *value)
98 {
99 	gchar *endptr = NULL;
100 	gint64 value_raw;
101 
102 	if (text == NULL)
103 		return FALSE;
104 
105 	value_raw = g_ascii_strtoll (text, &endptr, 10);
106 
107 	if (endptr == text)
108 		return FALSE;
109 	if (errno == ERANGE || value_raw > G_MAXINT || value_raw < G_MININT)
110 		return FALSE;
111 
112 	*value = (gint) value_raw;
113 	return TRUE;
114 }
115 
116 /**
117  * gpm_brightness_helper_get_value:
118  **/
119 static gint
gpm_brightness_helper_get_value(const gchar * argument)120 gpm_brightness_helper_get_value (const gchar *argument)
121 {
122 	gboolean ret;
123 	GError *error = NULL;
124 	gchar *stdout_data = NULL;
125 	gint exit_status = 0;
126 	gint value = -1;
127 	gchar *command = NULL;
128 
129 	/* get the data */
130 	command = g_strdup_printf (SBINDIR "/mate-power-backlight-helper --%s", argument);
131 	ret = g_spawn_command_line_sync (command,
132 					 &stdout_data, NULL, &exit_status, &error);
133 	if (!ret) {
134 		g_error ("failed to get value: %s", error->message);
135 		g_error_free (error);
136 		goto out;
137 	}
138 	g_debug ("executing %s retval: %i", command, exit_status);
139 
140 	/* parse for a number */
141 	ret = gpm_brightness_helper_strtoint (stdout_data, &value);
142 	if (!ret)
143 		goto out;
144 out:
145 	g_free (command);
146 	g_free (stdout_data);
147 	return value;
148 }
149 
150 /**
151  * gpm_brightness_helper_set_value:
152  **/
153 static gboolean
gpm_brightness_helper_set_value(const gchar * argument,gint value)154 gpm_brightness_helper_set_value (const gchar *argument, gint value)
155 {
156 	gboolean ret;
157 	GError *error = NULL;
158 	gint exit_status = 0;
159 	gchar *command = NULL;
160 
161 	/* get the data */
162 	command = g_strdup_printf ("pkexec " SBINDIR "/mate-power-backlight-helper --%s %i", argument, value);
163 	ret = g_spawn_command_line_sync (command, NULL, NULL, &exit_status, &error);
164 	if (!ret) {
165 		g_error ("failed to get value: %s", error->message);
166 		g_error_free (error);
167 		goto out;
168 	}
169 	g_debug ("executing %s retval: %i", command, exit_status);
170 out:
171 	g_free (command);
172 	return ret;
173 }
174 
175 /**
176  * gpm_brightness_get_step:
177  * @levels: The number of levels supported
178  * Return value: the amount of hardware steps to do on each increment or decrement
179  **/
180 static guint
gpm_brightness_get_step(guint levels)181 gpm_brightness_get_step (guint levels)
182 {
183 	/* macbook pro has a bazzillion brightness levels, do in 5% steps */
184 	if (levels > 20)
185 		return levels / 20;
186 	return 1;
187 }
188 
189 /**
190  * gpm_brightness_output_get_internal:
191  **/
192 static gboolean
gpm_brightness_output_get_internal(GpmBrightness * brightness,RROutput output,guint * cur)193 gpm_brightness_output_get_internal (GpmBrightness *brightness, RROutput output, guint *cur)
194 {
195 	unsigned long nitems;
196 	unsigned long bytes_after;
197 	guint *prop;
198 	Atom actual_type;
199 	int actual_format;
200 	gboolean ret = FALSE;
201 
202 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
203 
204 	if (brightness->priv->backlight == None)
205 		return FALSE;
206 
207 	if (XRRGetOutputProperty (brightness->priv->dpy, output, brightness->priv->backlight,
208 				  0, 4, False, False, None,
209 				  &actual_type, &actual_format,
210 				  &nitems, &bytes_after, ((unsigned char **)&prop)) != Success) {
211 		g_debug ("failed to get property");
212 		return FALSE;
213 	}
214 	if (actual_type == XA_INTEGER && nitems == 1 && actual_format == 32) {
215 		memcpy (cur, prop, sizeof (guint));
216 		ret = TRUE;
217 	}
218 	XFree (prop);
219 	return ret;
220 }
221 
222 /**
223  * gpm_brightness_output_set_internal:
224  **/
225 static gboolean
gpm_brightness_output_set_internal(GpmBrightness * brightness,RROutput output,guint value)226 gpm_brightness_output_set_internal (GpmBrightness *brightness, RROutput output, guint value)
227 {
228 	GdkDisplay *display;
229 
230 	gboolean ret = TRUE;
231 
232 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
233 
234 	/* don't abort on error */
235 	display = gdk_display_get_default ();
236 	gdk_x11_display_error_trap_push (display);
237 	XRRChangeOutputProperty (brightness->priv->dpy, output, brightness->priv->backlight, XA_INTEGER, 32,
238 				 PropModeReplace, (unsigned char *) &value, 1);
239 	XFlush (brightness->priv->dpy);
240 	gdk_display_flush (display);
241 	if (gdk_x11_display_error_trap_pop (display)) {
242 		g_warning ("failed to XRRChangeOutputProperty for brightness %i", value);
243 		ret = FALSE;
244 	}
245 	/* we changed the hardware */
246 	if (ret)
247 		brightness->priv->hw_changed = TRUE;
248 	return ret;
249 }
250 
251 /**
252  * gpm_brightness_setup_display:
253  **/
254 static gboolean
gpm_brightness_setup_display(GpmBrightness * brightness)255 gpm_brightness_setup_display (GpmBrightness *brightness)
256 {
257 	gint major, minor;
258 
259 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
260 
261 	/* get the display */
262 	brightness->priv->dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default());
263 	if (!brightness->priv->dpy) {
264 		g_error ("Cannot open display");
265 		return FALSE;
266 	}
267 	/* is XRandR new enough? */
268 	if (!XRRQueryVersion (brightness->priv->dpy, &major, &minor)) {
269 		g_debug ("RandR extension missing");
270 		return FALSE;
271 	}
272 	if (major < 1 || (major == 1 && minor < 3)) {
273 		g_debug ("RandR version %d.%d too old", major, minor);
274 		return FALSE;
275 	}
276 	/* Can we support "Backlight" */
277 	brightness->priv->backlight = XInternAtom (brightness->priv->dpy, "Backlight", True);
278 	if (brightness->priv->backlight == None) {
279 		/* Do we support "BACKLIGHT" (legacy) */
280 		brightness->priv->backlight = XInternAtom (brightness->priv->dpy, "BACKLIGHT", True);
281 		if (brightness->priv->backlight == None) {
282 			g_debug ("No outputs have backlight property");
283 			return FALSE;
284 		}
285 	}
286 	return TRUE;
287 }
288 
289 /**
290  * gpm_brightness_output_get_limits:
291  **/
292 static gboolean
gpm_brightness_output_get_limits(GpmBrightness * brightness,RROutput output,guint * min,guint * max)293 gpm_brightness_output_get_limits (GpmBrightness *brightness, RROutput output,
294 					 guint *min, guint *max)
295 {
296 	XRRPropertyInfo *info;
297 	gboolean ret = TRUE;
298 
299 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
300 
301 	info = XRRQueryOutputProperty (brightness->priv->dpy, output, brightness->priv->backlight);
302 	if (info == NULL) {
303 		g_debug ("could not get output property");
304 		return FALSE;
305 	}
306 	if (!info->range || info->num_values != 2) {
307 		g_debug ("was not range");
308 		ret = FALSE;
309 		goto out;
310 	}
311 	*min = info->values[0];
312 	*max = info->values[1];
313 out:
314 	XFree (info);
315 	return ret;
316 }
317 
318 /**
319  * gpm_brightness_output_get_percentage:
320  **/
321 static gboolean
gpm_brightness_output_get_percentage(GpmBrightness * brightness,RROutput output)322 gpm_brightness_output_get_percentage (GpmBrightness *brightness, RROutput output)
323 {
324 	guint cur;
325 	gboolean ret;
326 	guint min, max;
327 	guint percentage;
328 
329 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
330 
331 	ret = gpm_brightness_output_get_internal (brightness, output, &cur);
332 	if (!ret)
333 		return FALSE;
334 	ret = gpm_brightness_output_get_limits (brightness, output, &min, &max);
335 	if (!ret || min == max)
336 		return FALSE;
337 	g_debug ("hard value=%i, min=%i, max=%i", cur, min, max);
338 	percentage = egg_discrete_to_percent (cur, (max-min)+1);
339 	g_debug ("percentage %i", percentage);
340 	brightness->priv->shared_value = percentage;
341 	return TRUE;
342 }
343 
344 /**
345  * gpm_brightness_output_down:
346  **/
347 static gboolean
gpm_brightness_output_down(GpmBrightness * brightness,RROutput output)348 gpm_brightness_output_down (GpmBrightness *brightness, RROutput output)
349 {
350 	guint cur;
351 	guint step;
352 	gboolean ret;
353 	guint min, max;
354 
355 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
356 
357 	ret = gpm_brightness_output_get_internal (brightness, output, &cur);
358 	if (!ret)
359 		return FALSE;
360 	ret = gpm_brightness_output_get_limits (brightness, output, &min, &max);
361 	if (!ret || min == max)
362 		return FALSE;
363 	g_debug ("hard value=%i, min=%i, max=%i", cur, min, max);
364 	if (cur == min) {
365 		g_debug ("already min");
366 		return TRUE;
367 	}
368 	step = gpm_brightness_get_step ((max-min)+1);
369 	if (cur < step) {
370 		g_debug ("truncating to %i", min);
371 		cur = min;
372 	} else {
373 		cur -= step;
374 	}
375 	ret = gpm_brightness_output_set_internal (brightness, output, cur);
376 	return ret;
377 }
378 
379 /**
380  * gpm_brightness_output_up:
381  **/
382 static gboolean
gpm_brightness_output_up(GpmBrightness * brightness,RROutput output)383 gpm_brightness_output_up (GpmBrightness *brightness, RROutput output)
384 {
385 	guint cur;
386 	gboolean ret;
387 	guint min, max;
388 
389 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
390 
391 	ret = gpm_brightness_output_get_internal (brightness, output, &cur);
392 	if (!ret)
393 		return FALSE;
394 	ret = gpm_brightness_output_get_limits (brightness, output, &min, &max);
395 	if (!ret || min == max)
396 		return FALSE;
397 	g_debug ("hard value=%i, min=%i, max=%i", cur, min, max);
398 	if (cur == max) {
399 		g_debug ("already max");
400 		return TRUE;
401 	}
402 	cur += gpm_brightness_get_step ((max-min)+1);
403 	if (cur > max) {
404 		g_debug ("truncating to %i", max);
405 		cur = max;
406 	}
407 	ret = gpm_brightness_output_set_internal (brightness, output, cur);
408 	return ret;
409 }
410 
411 /**
412  * gpm_brightness_output_set:
413  **/
414 static gboolean
gpm_brightness_output_set(GpmBrightness * brightness,RROutput output)415 gpm_brightness_output_set (GpmBrightness *brightness, RROutput output)
416 {
417 	guint cur;
418 	gboolean ret;
419 	guint min, max;
420 	gint i;
421 	gint shared_value_abs;
422 	guint step;
423 
424 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
425 
426 	ret = gpm_brightness_output_get_internal (brightness, output, &cur);
427 	if (!ret)
428 		return FALSE;
429 	ret = gpm_brightness_output_get_limits (brightness, output, &min, &max);
430 	if (!ret || min == max)
431 		return FALSE;
432 
433 	shared_value_abs = egg_discrete_from_percent (brightness->priv->shared_value, (max-min)+1);
434 	g_debug ("percent=%i, absolute=%i", brightness->priv->shared_value, shared_value_abs);
435 
436 	g_debug ("hard value=%i, min=%i, max=%i", cur, min, max);
437 	if (shared_value_abs > (gint) max)
438 		shared_value_abs = max;
439 	if (shared_value_abs < (gint) min)
440 		shared_value_abs = min;
441 	if ((gint) cur == shared_value_abs) {
442 		g_debug ("already set %i", cur);
443 		return TRUE;
444 	}
445 
446 	/* step the correct way */
447 	if ((gint) cur < shared_value_abs) {
448 
449 		/* some adaptors have a large number of steps */
450 		step = gpm_brightness_get_step (shared_value_abs - cur);
451 		g_debug ("using step of %i", step);
452 
453 		/* going up */
454 		for (i=cur; i<=shared_value_abs; i+=step) {
455 			ret = gpm_brightness_output_set_internal (brightness, output, i);
456 			if (!ret)
457 				break;
458 			if ((gint) cur != shared_value_abs)
459 				g_usleep (1000 * GPM_BRIGHTNESS_DIM_INTERVAL);
460 		}
461 	} else {
462 
463 		/* some adaptors have a large number of steps */
464 		step = gpm_brightness_get_step (cur - shared_value_abs);
465 		g_debug ("using step of %i", step);
466 
467 		/* going down */
468 		for (i=cur; i>=shared_value_abs; i-=step) {
469 			ret = gpm_brightness_output_set_internal (brightness, output, i);
470 			if (!ret)
471 				break;
472 			if ((gint) cur != shared_value_abs)
473 				g_usleep (1000 * GPM_BRIGHTNESS_DIM_INTERVAL);
474 		}
475 	}
476 	return TRUE;
477 }
478 
479 /**
480  * gpm_brightness_foreach_resource:
481  **/
482 static gboolean
gpm_brightness_foreach_resource(GpmBrightness * brightness,GpmXRandROp op,XRRScreenResources * resources)483 gpm_brightness_foreach_resource (GpmBrightness *brightness, GpmXRandROp op, XRRScreenResources *resources)
484 {
485 	gint i;
486 	gboolean ret;
487 	gboolean success_any = FALSE;
488 	RROutput output;
489 
490 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
491 
492 	/* do for each output */
493 	for (i=0; i<resources->noutput; i++) {
494 		output = resources->outputs[i];
495 		g_debug ("resource %i of %i", i+1, resources->noutput);
496 		if (op==ACTION_BACKLIGHT_GET) {
497 			ret = gpm_brightness_output_get_percentage (brightness, output);
498 		} else if (op==ACTION_BACKLIGHT_INC) {
499 			ret = gpm_brightness_output_up (brightness, output);
500 		} else if (op==ACTION_BACKLIGHT_DEC) {
501 			ret = gpm_brightness_output_down (brightness, output);
502 		} else if (op==ACTION_BACKLIGHT_SET) {
503 			ret = gpm_brightness_output_set (brightness, output);
504 		} else {
505 			ret = FALSE;
506 			g_warning ("op not known");
507 		}
508 		if (ret) {
509 			success_any = TRUE;
510 		}
511 	}
512 	return success_any;
513 }
514 
515 /**
516  * gpm_brightness_foreach_screen:
517  **/
518 static gboolean
gpm_brightness_foreach_screen(GpmBrightness * brightness,GpmXRandROp op)519 gpm_brightness_foreach_screen (GpmBrightness *brightness, GpmXRandROp op)
520 {
521 	guint i;
522 	guint length;
523 	XRRScreenResources *resource;
524 	gboolean ret;
525 	gboolean success_any = FALSE;
526 
527 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
528 
529 	/* Return immediately if we can't use XRandR */
530 	if (!brightness->priv->has_extension)
531 		return FALSE;
532 
533 	/* do for each screen */
534 	length = brightness->priv->resources->len;
535 	for (i=0; i<length; i++) {
536 		resource = (XRRScreenResources *) g_ptr_array_index (brightness->priv->resources, i);
537 		g_debug ("using resource %p", resource);
538 		ret = gpm_brightness_foreach_resource (brightness, op, resource);
539 		if (ret)
540 			success_any = TRUE;
541 	}
542 	XSync (brightness->priv->dpy, False);
543 	return success_any;
544 }
545 
546 /**
547  * gpm_brightness_trust_cache:
548  * @brightness: This brightness class instance
549  * Return value: if we can trust the cache
550  **/
551 static gboolean
gpm_brightness_trust_cache(GpmBrightness * brightness)552 gpm_brightness_trust_cache (GpmBrightness *brightness)
553 {
554 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
555 	/* only return the cached value if the cache is trusted and we have change events */
556 	if (brightness->priv->cache_trusted && brightness->priv->has_changed_events) {
557 		g_debug ("using cache for value %u (okay)", brightness->priv->cache_percentage);
558 		return TRUE;
559 	}
560 
561 	/* can we trust that if we set a value 5 minutes ago, will it still be valid now?
562 	 * if we have multiple things setting policy on the workstation, e.g. fast user switching
563 	 * or kpowersave, then this will be invalid -- this logic may be insane */
564 	if (GPM_SOLE_SETTER_USE_CACHE && brightness->priv->cache_trusted) {
565 		g_debug ("using cache for value %u (probably okay)", brightness->priv->cache_percentage);
566 		return TRUE;
567 	}
568 	return FALSE;
569 }
570 
571 /**
572  * gpm_brightness_set:
573  * @brightness: This brightness class instance
574  * @percentage: The percentage brightness
575  * @hw_changed: If the hardware was changed, i.e. the brightness changed
576  * Return value: %TRUE if success, %FALSE if there was an error
577  **/
578 gboolean
gpm_brightness_set(GpmBrightness * brightness,guint percentage,gboolean * hw_changed)579 gpm_brightness_set (GpmBrightness *brightness, guint percentage, gboolean *hw_changed)
580 {
581 	gboolean ret = FALSE;
582 	gboolean trust_cache;
583 
584 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
585 
586 	/* can we check the new value with the cache? */
587 	trust_cache = gpm_brightness_trust_cache (brightness);
588 	if (trust_cache && percentage == brightness->priv->cache_percentage) {
589 		g_debug ("not setting the same value %i", percentage);
590 		return TRUE;
591 	}
592 
593 	/* set the value we want */
594 	brightness->priv->shared_value = percentage;
595 
596 	/* reset to not-changed */
597 	brightness->priv->hw_changed = FALSE;
598 	ret = gpm_brightness_foreach_screen (brightness, ACTION_BACKLIGHT_SET);
599 
600 	/* legacy fallback */
601 	if (!ret) {
602 		if (brightness->priv->extension_levels < 0)
603 			brightness->priv->extension_levels = gpm_brightness_helper_get_value ("get-max-brightness");
604 		brightness->priv->extension_current = egg_discrete_from_percent (percentage, brightness->priv->extension_levels+1);
605 		ret = gpm_brightness_helper_set_value ("set-brightness", brightness->priv->extension_current);
606 	}
607 
608 	/* did the hardware have to be modified? */
609 	if (ret && hw_changed != NULL)
610 		*hw_changed = brightness->priv->hw_changed;
611 
612 	/* we did something to the hardware, so untrusted */
613 	if (ret)
614 		brightness->priv->cache_trusted = FALSE;
615 
616 	return ret;
617 }
618 
619 /**
620  * gpm_brightness_get:
621  * @brightness: This brightness class instance
622  * Return value: The percentage brightness, or -1 for no hardware or error
623  *
624  * Gets the current (or at least what this class thinks is current) percentage
625  * brightness. This is quick as no HAL inquiry is done.
626  **/
627 gboolean
gpm_brightness_get(GpmBrightness * brightness,guint * percentage)628 gpm_brightness_get (GpmBrightness *brightness, guint *percentage)
629 {
630 	gboolean ret = FALSE;
631 	gboolean trust_cache;
632 	guint percentage_local;
633 
634 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
635 	g_return_val_if_fail (percentage != NULL, FALSE);
636 
637 	/* can we use the cache? */
638 	trust_cache = gpm_brightness_trust_cache (brightness);
639 	if (trust_cache) {
640 		*percentage = brightness->priv->cache_percentage;
641 		return TRUE;
642 	}
643 
644 	/* get the brightness from hardware -- slow */
645 	ret = gpm_brightness_foreach_screen (brightness, ACTION_BACKLIGHT_GET);
646 	percentage_local = brightness->priv->shared_value;
647 
648 	/* legacy fallback */
649 	if (!ret) {
650 		if (brightness->priv->extension_levels < 0)
651 			brightness->priv->extension_levels = gpm_brightness_helper_get_value ("get-max-brightness");
652 		brightness->priv->extension_current = gpm_brightness_helper_get_value ("get-brightness");
653 		percentage_local = egg_discrete_to_percent (brightness->priv->extension_current, brightness->priv->extension_levels+1);
654 		ret = TRUE;
655 	}
656 
657 	/* valid? */
658 	if (percentage_local > 100) {
659 		g_warning ("percentage value of %i will be truncated", percentage_local);
660 		percentage_local = 100;
661 	}
662 
663 	/* a new value is always trusted if the method and checks succeed */
664 	if (ret) {
665 		brightness->priv->cache_percentage = percentage_local;
666 		brightness->priv->cache_trusted = TRUE;
667 		*percentage = percentage_local;
668 	} else {
669 		brightness->priv->cache_trusted = FALSE;
670 	}
671 	return ret;
672 }
673 
674 /**
675  * gpm_brightness_up:
676  * @brightness: This brightness class instance
677  * @hw_changed: If the hardware was changed, i.e. the brightness changed
678  * Return value: %TRUE if success, %FALSE if there was an error
679  *
680  * If possible, put the brightness of the LCD up one unit.
681  **/
682 gboolean
gpm_brightness_up(GpmBrightness * brightness,gboolean * hw_changed)683 gpm_brightness_up (GpmBrightness *brightness, gboolean *hw_changed)
684 {
685 	gboolean ret = FALSE;
686 	guint step;
687 
688 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
689 
690 	/* reset to not-changed */
691 	brightness->priv->hw_changed = FALSE;
692 	ret = gpm_brightness_foreach_screen (brightness, ACTION_BACKLIGHT_INC);
693 
694 	/* did the hardware have to be modified? */
695 	if (ret && hw_changed != NULL)
696 		*hw_changed = brightness->priv->hw_changed;
697 
698 	/* we did something to the hardware, so untrusted */
699 	if (ret)
700 		brightness->priv->cache_trusted = FALSE;
701 
702 	/* legacy fallback */
703 	if (!ret) {
704 		if (brightness->priv->extension_levels < 0)
705 			brightness->priv->extension_levels = gpm_brightness_helper_get_value ("get-max-brightness");
706 		brightness->priv->extension_current = gpm_brightness_helper_get_value ("get-brightness");
707 
708 		/* increase by the step, limiting to the maximum possible levels */
709 		if (brightness->priv->extension_current < brightness->priv->extension_levels) {
710 			step = gpm_brightness_get_step (brightness->priv->extension_levels);
711 			brightness->priv->extension_current += step;
712 			if (brightness->priv->extension_current > brightness->priv->extension_levels)
713 				brightness->priv->extension_current = brightness->priv->extension_levels;
714 			ret = gpm_brightness_helper_set_value ("set-brightness", brightness->priv->extension_current);
715 		}
716 		if (hw_changed != NULL)
717 			*hw_changed = ret;
718 		brightness->priv->cache_trusted = FALSE;
719 		goto out;
720 	}
721 out:
722 	return ret;
723 }
724 
725 /**
726  * gpm_brightness_down:
727  * @brightness: This brightness class instance
728  * @hw_changed: If the hardware was changed, i.e. the brightness changed
729  * Return value: %TRUE if success, %FALSE if there was an error
730  *
731  * If possible, put the brightness of the LCD down one unit.
732  **/
733 gboolean
gpm_brightness_down(GpmBrightness * brightness,gboolean * hw_changed)734 gpm_brightness_down (GpmBrightness *brightness, gboolean *hw_changed)
735 {
736 	gboolean ret = FALSE;
737 	guint step;
738 
739 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
740 
741 	/* reset to not-changed */
742 	brightness->priv->hw_changed = FALSE;
743 	ret = gpm_brightness_foreach_screen (brightness, ACTION_BACKLIGHT_DEC);
744 
745 	/* did the hardware have to be modified? */
746 	if (ret && hw_changed != NULL)
747 		*hw_changed = brightness->priv->hw_changed;
748 
749 	/* we did something to the hardware, so untrusted */
750 	if (ret)
751 		brightness->priv->cache_trusted = FALSE;
752 
753 	/* legacy fallback */
754 	if (!ret) {
755 		if (brightness->priv->extension_levels < 0)
756 			brightness->priv->extension_levels = gpm_brightness_helper_get_value ("get-max-brightness");
757 		brightness->priv->extension_current = gpm_brightness_helper_get_value ("get-brightness");
758 
759 		/* decrease by the step, limiting to zero */
760 		if (brightness->priv->extension_current > 0) {
761 			step = gpm_brightness_get_step (brightness->priv->extension_levels);
762 			brightness->priv->extension_current -= step;
763 			if (brightness->priv->extension_current < 0)
764 				brightness->priv->extension_current = 0;
765 			ret = gpm_brightness_helper_set_value ("set-brightness", brightness->priv->extension_current);
766 		}
767 		if (hw_changed != NULL)
768 			*hw_changed = ret;
769 		brightness->priv->cache_trusted = FALSE;
770 		goto out;
771 	}
772 out:
773 	return ret;
774 }
775 
776 
777 /**
778  * gpm_brightness_may_have_changed:
779  **/
780 static void
gpm_brightness_may_have_changed(GpmBrightness * brightness)781 gpm_brightness_may_have_changed (GpmBrightness *brightness)
782 {
783 	gboolean ret;
784 	guint percentage;
785 	ret = gpm_brightness_get (brightness, &percentage);
786 	if (!ret) {
787 		g_warning ("failed to get output");
788 		return;
789 	}
790 	g_debug ("emitting brightness-changed (%i)", percentage);
791 	g_signal_emit (brightness, signals [BRIGHTNESS_CHANGED], 0, percentage);
792 }
793 
794 /**
795  * gpm_brightness_filter_xevents:
796  **/
797 static GdkFilterReturn
gpm_brightness_filter_xevents(GdkXEvent * xevent,GdkEvent * event,gpointer data)798 gpm_brightness_filter_xevents (GdkXEvent *xevent, GdkEvent *event, gpointer data)
799 {
800 	GpmBrightness *brightness = GPM_BRIGHTNESS (data);
801 	if (event->type == GDK_NOTHING)
802 		return GDK_FILTER_CONTINUE;
803 	gpm_brightness_may_have_changed (brightness);
804 	return GDK_FILTER_CONTINUE;
805 }
806 
807 
808 static void gpm_brightness_update_cache (GpmBrightness *brightness);
809 
810 /**
811  * gpm_brightness_monitors_changed:
812  **/
813 static void
gpm_brightness_monitors_changed(GdkScreen * screen,GpmBrightness * brightness)814 gpm_brightness_monitors_changed (GdkScreen *screen, GpmBrightness *brightness)
815 {
816 	g_return_if_fail (GPM_IS_BRIGHTNESS (brightness));
817 	gpm_brightness_update_cache (brightness);
818 }
819 
820 /**
821  * gpm_brightness_update_cache:
822  **/
823 static void
gpm_brightness_update_cache(GpmBrightness * brightness)824 gpm_brightness_update_cache (GpmBrightness *brightness)
825 {
826 	guint length;
827 	Window root;
828 	GdkScreen *gscreen;
829 	GdkDisplay *display;
830 	XRRScreenResources *resource;
831 
832 	g_return_if_fail (GPM_IS_BRIGHTNESS (brightness));
833 
834 	/* invalidate and remove all the previous entries */
835 	length = brightness->priv->resources->len;
836 	if (length > 0)
837 		g_ptr_array_set_size (brightness->priv->resources, 0);
838 
839 	display = gdk_display_get_default ();
840 	gscreen = gdk_display_get_default_screen (display);
841 
842 	/* if we have not setup the changed on the monitor, set it here */
843 	if (g_object_get_data (G_OBJECT (gscreen), "gpk-set-monitors-changed") == NULL) {
844 		g_debug ("watching ::monitors_changed on %p", gscreen);
845 		g_object_set_data (G_OBJECT (gscreen), "gpk-set-monitors-changed", (gpointer) "true");
846 		g_signal_connect (G_OBJECT (gscreen), "monitors_changed",
847 				  G_CALLBACK (gpm_brightness_monitors_changed), brightness);
848 	}
849 
850 	root = RootWindow (brightness->priv->dpy, 0);
851 
852 	gdk_x11_display_error_trap_push (display);
853 	resource = XRRGetScreenResourcesCurrent (brightness->priv->dpy, root);
854 	if (gdk_x11_display_error_trap_pop (display) || resource == NULL) {
855 		g_warning ("failed to XRRGetScreenResourcesCurrent");
856 	}
857 
858 	if (resource != NULL) {
859 		g_debug ("adding resource %p", resource);
860 		g_ptr_array_add (brightness->priv->resources, resource);
861 	}
862 }
863 
864 /**
865  * gpm_brightness_has_hw:
866  **/
867 gboolean
gpm_brightness_has_hw(GpmBrightness * brightness)868 gpm_brightness_has_hw (GpmBrightness *brightness)
869 {
870 	g_return_val_if_fail (GPM_IS_BRIGHTNESS (brightness), FALSE);
871 
872 	/* use XRandR first */
873 	if (brightness->priv->has_extension)
874 		return TRUE;
875 
876 	/* fallback to legacy access */
877 	if (brightness->priv->extension_levels < 0)
878 		brightness->priv->extension_levels = gpm_brightness_helper_get_value ("get-max-brightness");
879 	if (brightness->priv->extension_levels > 0)
880 		return TRUE;
881 	return FALSE;
882 }
883 
884 /**
885  * gpm_brightness_finalize:
886  **/
887 static void
gpm_brightness_finalize(GObject * object)888 gpm_brightness_finalize (GObject *object)
889 {
890 	GpmBrightness *brightness;
891 	g_return_if_fail (object != NULL);
892 	g_return_if_fail (GPM_IS_BRIGHTNESS (object));
893 	brightness = GPM_BRIGHTNESS (object);
894 	g_ptr_array_unref (brightness->priv->resources);
895 	gdk_window_remove_filter (brightness->priv->root_window,
896 				  gpm_brightness_filter_xevents, brightness);
897 	G_OBJECT_CLASS (gpm_brightness_parent_class)->finalize (object);
898 }
899 
900 /**
901  * gpm_brightness_class_init:
902  **/
903 static void
gpm_brightness_class_init(GpmBrightnessClass * klass)904 gpm_brightness_class_init (GpmBrightnessClass *klass)
905 {
906 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
907 	object_class->finalize = gpm_brightness_finalize;
908 
909 	signals [BRIGHTNESS_CHANGED] =
910 		g_signal_new ("brightness-changed",
911 			      G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
912 			      G_STRUCT_OFFSET (GpmBrightnessClass, brightness_changed),
913 			      NULL, NULL, g_cclosure_marshal_VOID__UINT,
914 			      G_TYPE_NONE, 1, G_TYPE_UINT);
915 }
916 
917 /**
918  * gpm_brightness_init:
919  * @brightness: This brightness class instance
920  **/
921 static void
gpm_brightness_init(GpmBrightness * brightness)922 gpm_brightness_init (GpmBrightness *brightness)
923 {
924 	GdkScreen *screen;
925 	GdkDisplay *display;
926 	int event_base;
927 	int ignore;
928 
929 	brightness->priv = gpm_brightness_get_instance_private (brightness);
930 
931 	brightness->priv->cache_trusted = FALSE;
932 	brightness->priv->has_changed_events = FALSE;
933 	brightness->priv->cache_percentage = 0;
934 	brightness->priv->hw_changed = FALSE;
935 	brightness->priv->extension_levels = -1;
936 	brightness->priv->resources = g_ptr_array_new_with_free_func ((GDestroyNotify) XRRFreeScreenResources);
937 
938 	/* can we do this */
939 	brightness->priv->has_extension = gpm_brightness_setup_display (brightness);
940 	if (brightness->priv->has_extension == FALSE)
941 		g_debug ("no XRANDR extension");
942 
943 	screen = gdk_screen_get_default ();
944 	brightness->priv->root_window = gdk_screen_get_root_window (screen);
945 	display = gdk_display_get_default ();
946 
947 	/* as we a filtering by a window, we have to add an event type */
948 	if (!XRRQueryExtension (GDK_DISPLAY_XDISPLAY (gdk_display_get_default()), &event_base, &ignore)) {
949 		g_warning ("can't get event_base for XRR");
950 	}
951 	gdk_x11_register_standard_event_type (display, event_base, RRNotify + 1);
952 	gdk_window_add_filter (brightness->priv->root_window,
953 			       gpm_brightness_filter_xevents, brightness);
954 
955 	/* don't abort on error */
956 	gdk_x11_display_error_trap_push (display);
957 	XRRSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default()),
958 			GDK_WINDOW_XID (brightness->priv->root_window),
959 			RRScreenChangeNotifyMask |
960 			RROutputPropertyNotifyMask); /* <--- the only one we need, but see rh:345551 */
961 	gdk_display_flush (display);
962 	if (gdk_x11_display_error_trap_pop (display))
963 		g_warning ("failed to select XRRSelectInput");
964 
965 	/* create cache of XRRScreenResources as XRRGetScreenResources() is slow */
966 	gpm_brightness_update_cache (brightness);
967 }
968 
969 /**
970  * gpm_brightness_new:
971  * Return value: A new brightness class instance.
972  * Can return NULL if no suitable hardware is found.
973  **/
974 GpmBrightness *
gpm_brightness_new(void)975 gpm_brightness_new (void)
976 {
977 	if (gpm_brightness_object != NULL) {
978 		g_object_ref (gpm_brightness_object);
979 	} else {
980 		gpm_brightness_object = g_object_new (GPM_TYPE_BRIGHTNESS, NULL);
981 		g_object_add_weak_pointer (gpm_brightness_object, &gpm_brightness_object);
982 	}
983 	return GPM_BRIGHTNESS (gpm_brightness_object);
984 }
985 
986