aboutsummaryrefslogtreecommitdiff
path: root/brightness.c
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2016-01-10 02:22:43 +0100
committerPřemysl Janouch <p.janouch@gmail.com>2016-01-10 03:12:47 +0100
commita91e24930cec67804db411b31a0a6eee411077eb (patch)
tree73081bc68721c5a2f9c32ef1e8489d5998110e90 /brightness.c
downloaddesktop-tools-a91e24930cec67804db411b31a0a6eee411077eb.tar.gz
desktop-tools-a91e24930cec67804db411b31a0a6eee411077eb.tar.xz
desktop-tools-a91e24930cec67804db411b31a0a6eee411077eb.zip
Initial commit
These used to be part of my dwm fork repository.
Diffstat (limited to 'brightness.c')
-rw-r--r--brightness.c445
1 files changed, 445 insertions, 0 deletions
diff --git a/brightness.c b/brightness.c
new file mode 100644
index 0000000..f30a0c9
--- /dev/null
+++ b/brightness.c
@@ -0,0 +1,445 @@
+/*
+ * brightness.c: set display brightness via DDC/CI - Linux only
+ *
+ * Copyright (c) 2015, Přemysl Janouch <p.janouch@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+// Sources: ddcciv1r1.pdf, i2c-dev.c in Linux, ddccontrol source code,
+// http://www.boichat.ch/nicolas/ddcci/specs.html was also helpful
+
+// This makes openat() available even though I set _POSIX_C_SOURCE and
+// _XOPEN_SOURCE to a version of POSIX older than 2008
+#define _GNU_SOURCE
+
+// Undo some dwm Makefile damage and import my everything-library
+#include "config.h"
+#undef PROGRAM_NAME
+#define PROGRAM_NAME "brightness"
+#include "liberty/liberty.c"
+
+#include <sys/ioctl.h>
+#include <dirent.h>
+
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+log_message_custom (void *user_data, const char *quote, const char *fmt,
+ va_list ap)
+{
+ (void) user_data;
+ FILE *stream = stdout;
+
+ fprintf (stream, PROGRAM_NAME ": ");
+ fputs (quote, stream);
+ vfprintf (stream, fmt, ap);
+ fputs ("\n", stream);
+}
+
+#define FAIL(...) \
+ BLOCK_START \
+ error_set (e, __VA_ARGS__); \
+ return false; \
+ BLOCK_END
+
+static void
+wait_ms (long ms)
+{
+ struct timespec ts = { 0, ms * 1000 * 1000 };
+ nanosleep (&ts, NULL);
+}
+
+static bool
+xstrtol (const char *s, long *out)
+{
+ char *end;
+ errno = 0;
+ *out = strtol (s, &end, 10);
+ return errno == 0 && !*end && end != s;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#define DDC_LENGTH_XOR 0x80
+
+enum
+{
+ DDC_ADDRESS_HOST = 0x50, ///< Bus master's base address
+ DDC_ADDRESS_DISPLAY = 0x6E ///< The display's base address
+};
+
+enum { I2C_WRITE, I2C_READ };
+
+enum
+{
+ DDC_GET_VCP_FEATURE = 0x01, ///< Request info about a feature
+ DDC_GET_VCP_FEATURE_REPLY = 0x02, ///< Feature info response
+ DDC_SET_VCP_FEATURE = 0x03 ///< Set or activate a feature
+};
+
+enum
+{
+ VCP_BRIGHTNESS = 0x10, ///< Standard VCP opcode for brightness
+ VCP_CONTRAST = 0x12 ///< Standard VCP opcode for contrast
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+check_edid (int fd, struct error **e)
+{
+ uint8_t edid_req[] = { 0x00 };
+ uint8_t buf[8] = "";
+ struct i2c_msg bufs[] =
+ {
+ { .addr = 0x50, .flags = 0,
+ .len = 1, .buf = edid_req },
+ { .addr = 0x50, .flags = I2C_M_RD,
+ .len = sizeof buf, .buf = buf },
+ };
+
+ struct i2c_rdwr_ioctl_data data;
+ data.msgs = bufs;
+ data.nmsgs = 2;
+
+ if (ioctl (fd, I2C_RDWR, &data) < 0)
+ FAIL ("%s: %s", "ioctl", strerror (errno));
+ if (memcmp ("\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", buf, sizeof buf))
+ FAIL ("invalid EDID");
+ return true;
+}
+
+static bool
+is_a_display (int fd, struct error **e)
+{
+ struct stat st;
+ if (fstat (fd, &st) < 0)
+ FAIL ("%s: %s", "fstat", strerror (errno));
+
+ unsigned long funcs;
+ if (!(st.st_mode & S_IFCHR)
+ || ioctl (fd, I2C_FUNCS, &funcs) < 0
+ || !(funcs & I2C_FUNC_I2C))
+ FAIL ("not an I2C device");
+
+ return check_edid (fd, e);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+ddc_send (int fd, unsigned command, void *args, size_t args_len,
+ struct error **e)
+{
+ struct str buf;
+ str_init (&buf);
+ str_pack_u8 (&buf, DDC_ADDRESS_HOST | I2C_READ);
+ str_pack_u8 (&buf, DDC_LENGTH_XOR | (args_len + 1));
+ str_pack_u8 (&buf, command);
+ str_append_data (&buf, args, args_len);
+
+ unsigned xor = DDC_ADDRESS_DISPLAY;
+ for (size_t i = 0; i < buf.len; i++)
+ xor ^= buf.str[i];
+ str_pack_u8 (&buf, xor);
+
+ struct i2c_msg msg =
+ {
+ // The driver unshifts it back
+ .addr = DDC_ADDRESS_DISPLAY >> 1, .flags = 0,
+ .len = buf.len, .buf = (uint8_t *) buf.str,
+ };
+
+ struct i2c_rdwr_ioctl_data data;
+ data.msgs = &msg;
+ data.nmsgs = 1;
+
+ bool failed = ioctl (fd, I2C_RDWR, &data) < 0;
+ str_free (&buf);
+ if (failed)
+ FAIL ("%s: %s", "ioctl", strerror (errno));
+ return true;
+}
+
+static bool
+ddc_read (int fd, unsigned *command, void *out_buf, size_t *n_read,
+ struct error **e)
+{
+ uint8_t buf[128] = "";
+ struct i2c_msg msg =
+ {
+ // The driver unshifts it back
+ .addr = DDC_ADDRESS_DISPLAY >> 1, .flags = I2C_M_RD,
+ .len = sizeof buf, .buf = buf,
+ };
+
+ struct i2c_rdwr_ioctl_data data;
+ data.msgs = &msg;
+ data.nmsgs = 1;
+
+ if (ioctl (fd, I2C_RDWR, &data) < 0)
+ FAIL ("%s: %s", "ioctl", strerror (errno));
+
+ struct msg_unpacker unpacker;
+ msg_unpacker_init (&unpacker, buf, sizeof buf);
+
+ uint8_t sender, length, cmd;
+ (void) msg_unpacker_u8 (&unpacker, &sender);
+ (void) msg_unpacker_u8 (&unpacker, &length);
+ (void) msg_unpacker_u8 (&unpacker, &cmd);
+
+ if (sender != (DDC_ADDRESS_DISPLAY | I2C_WRITE) || !(length & 0x80))
+ FAIL ("invalid response");
+ if (!(length ^= 0x80))
+ FAIL ("NULL response");
+
+ // TODO: also check the checksum
+
+ *command = cmd;
+ memcpy (out_buf, unpacker.data + unpacker.offset, (*n_read = length - 1));
+ return true;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+set_brightness (int fd, long diff, struct error **e)
+{
+ uint8_t get_req[] = { VCP_BRIGHTNESS };
+ if (!ddc_send (fd, DDC_GET_VCP_FEATURE, get_req, sizeof get_req, e))
+ return false;
+
+ wait_ms (40);
+
+ unsigned command = 0;
+ uint8_t buf[128] = "";
+ size_t len = 0;
+ if (!ddc_read (fd, &command, buf, &len, e))
+ return false;
+
+ if (command != DDC_GET_VCP_FEATURE_REPLY || len != 7)
+ FAIL ("invalid response");
+
+ struct msg_unpacker unpacker;
+ msg_unpacker_init (&unpacker, buf, len);
+
+ uint8_t result; msg_unpacker_u8 (&unpacker, &result);
+ uint8_t vcp_opcode; msg_unpacker_u8 (&unpacker, &vcp_opcode);
+ uint8_t type; msg_unpacker_u8 (&unpacker, &type);
+ int16_t max; msg_unpacker_i16 (&unpacker, &max);
+ int16_t cur; msg_unpacker_i16 (&unpacker, &cur);
+
+ if (result == 0x01)
+ FAIL ("error reported by monitor");
+
+ if (result != 0x00
+ || vcp_opcode != VCP_BRIGHTNESS)
+ FAIL ("invalid response");
+
+ // These are unsigned but usually just one byte long
+ if (max < 0 || cur < 0)
+ FAIL ("capability range overflow");
+
+ int16_t req = (cur * 100 + diff * max + 50) / 100;
+ if (req > max) req = max;
+ if (req < 0) req = 0;
+
+ uint8_t set_req[] = { VCP_BRIGHTNESS, req >> 8, req };
+ if (!ddc_send (fd, DDC_SET_VCP_FEATURE, set_req, sizeof set_req, e))
+ return false;
+
+ wait_ms (50);
+
+ printf ("brightness set to %.2f%%\n", 100. * req / max);
+ return true;
+}
+
+static void
+i2c (long diff)
+{
+ DIR *dev = opendir ("/dev");
+ if (!dev)
+ {
+ print_error ("cannot access %s: %s: %s",
+ "/dev", "opendir", strerror (errno));
+ return;
+ }
+
+ struct dirent *entry;
+ while ((entry = readdir (dev)))
+ {
+ if (strncmp (entry->d_name, "i2c-", 4))
+ continue;
+
+ printf ("Trying %s... ", entry->d_name);
+ int fd = openat (dirfd (dev), entry->d_name, O_RDONLY);
+ if (fd < 0)
+ {
+ print_error ("%s: %s", "openat", strerror (errno));
+ continue;
+ }
+
+ struct error *e = NULL;
+ if (!is_a_display (fd, &e)
+ || !set_brightness (fd, diff, &e))
+ {
+ printf ("%s\n", e->message);
+ error_free (e);
+ }
+
+ close (fd);
+ }
+ closedir (dev);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static long
+read_value (int dir, const char *filename, struct error **e)
+{
+ int fd = openat (dir, filename, O_RDONLY);
+ if (fd < 0)
+ {
+ error_set (e, "%s: %s: %s", filename, "openat", strerror (errno));
+ return -1;
+ }
+
+ FILE *fp = fdopen (fd, "r");
+ if (!fp)
+ {
+ error_set (e, "%s: %s: %s", filename, "fdopen", strerror (errno));
+ close (fd);
+ return -1;
+ }
+
+ struct str s;
+ str_init (&s);
+
+ long value;
+ if (!read_line (fp, &s)
+ || !xstrtol (s.str, &value))
+ {
+ value = -1;
+ error_set (e, "%s: %s", filename, "failed reading an integer value");
+ }
+
+ str_free (&s);
+ fclose (fp);
+ return value;
+}
+
+static bool
+set_backlight (int dir, long diff, struct error **e)
+{
+ long cur, max;
+ struct error *error = NULL;
+ if ((cur = read_value (dir, "brightness", &error), error)
+ || (max = read_value (dir, "max_brightness", &error), error))
+ {
+ error_propagate (e, error);
+ return false;
+ }
+
+ if (cur < 0 || max < 0)
+ {
+ error_set (e, "invalid range or current value");
+ return false;
+ }
+
+ long req = (cur * 100 + diff * max + 50) / 100;
+ if (req > max) req = max;
+ if (req < 0) req = 0;
+
+ int fd = openat (dir, "brightness", O_WRONLY);
+ if (fd < 0)
+ {
+ error_set (e, "%s: %s: %s", "brightness", "openat", strerror (errno));
+ return false;
+ }
+
+ struct str s;
+ str_init (&s);
+ str_append_printf (&s, "%ld", req);
+ bool result = write (fd, s.str, s.len) == s.len;
+ str_free (&s);
+
+ if (!result)
+ error_set (e, "%s: %s: %s", "brightness", "write", strerror (errno));
+
+ close (fd);
+ printf ("brightness set to %.2f%%\n", 100. * req / max);
+ return result;
+}
+
+static void
+backlight (long diff)
+{
+ DIR *backlight = opendir ("/sys/class/backlight");
+ if (!backlight)
+ {
+ print_error ("cannot access %s: %s: %s",
+ "/sys/class/backlight", "opendir", strerror (errno));
+ return;
+ }
+
+ struct dirent *entry;
+ while ((entry = readdir (backlight)))
+ {
+ const char *device_name = entry->d_name;
+ if (device_name[0] == '.')
+ continue;
+
+ printf ("Trying %s... ", entry->d_name);
+ int dir = openat (dirfd (backlight), entry->d_name, O_RDONLY);
+ if (dir < 0)
+ {
+ print_error ("%s: %s", "openat", strerror (errno));
+ continue;
+ }
+
+ struct error *e = NULL;
+ if (!set_backlight (dir, diff, &e))
+ {
+ printf ("%s\n", e->message);
+ error_free (e);
+ }
+
+ close (dir);
+ }
+ closedir (backlight);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+int
+main (int argc, char *argv[])
+{
+ g_log_message_real = log_message_custom;
+
+ long diff = 0;
+ if (argc > 1 && !xstrtol (argv[1], &diff))
+ {
+ printf ("Usage: %s <percentage diff>\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ i2c (diff);
+ backlight (diff);
+ return 0;
+}
+