Add input-switch
Moving common stuff for DDC/CI communicating programs to ddc-ci.c.
This commit is contained in:
parent
dba39bb612
commit
fee703567f
|
@ -42,6 +42,9 @@ add_threads (wmstatus)
|
||||||
add_executable (brightness brightness.c)
|
add_executable (brightness brightness.c)
|
||||||
target_link_libraries (brightness ${project_libraries})
|
target_link_libraries (brightness ${project_libraries})
|
||||||
|
|
||||||
|
add_executable (input-switch input-switch.c)
|
||||||
|
target_link_libraries (input-switch ${project_libraries})
|
||||||
|
|
||||||
add_executable (fancontrol-ng fancontrol-ng.c)
|
add_executable (fancontrol-ng fancontrol-ng.c)
|
||||||
target_link_libraries (fancontrol-ng ${project_libraries})
|
target_link_libraries (fancontrol-ng ${project_libraries})
|
||||||
|
|
||||||
|
@ -76,7 +79,7 @@ if (WITH_GDM)
|
||||||
install (TARGETS gdm-switch-user DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install (TARGETS gdm-switch-user DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
endif (WITH_GDM)
|
endif (WITH_GDM)
|
||||||
|
|
||||||
install (TARGETS wmstatus brightness fancontrol-ng siprandom
|
install (TARGETS wmstatus brightness input-switch fancontrol-ng siprandom
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install (PROGRAMS shellify DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install (PROGRAMS shellify DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||||
|
|
|
@ -9,6 +9,7 @@ to other people as well:
|
||||||
includes PulseAudio volume management and hand-written NUT and MPD clients,
|
includes PulseAudio volume management and hand-written NUT and MPD clients,
|
||||||
all in the name of liberation from GPL-licensed software of course.
|
all in the name of liberation from GPL-licensed software of course.
|
||||||
- 'brightness' allows me to change the brightness of w/e display device I have.
|
- 'brightness' allows me to change the brightness of w/e display device I have.
|
||||||
|
- 'input-switch' likewise switches the input source of external displays.
|
||||||
- 'fancontrol-ng' is a clone of fancontrol that can handle errors on resume
|
- 'fancontrol-ng' is a clone of fancontrol that can handle errors on resume
|
||||||
from suspend instead of setting fans to maximum speed and quitting;
|
from suspend instead of setting fans to maximum speed and quitting;
|
||||||
in general it doesn't handle everything the original does
|
in general it doesn't handle everything the original does
|
||||||
|
|
218
brightness.c
218
brightness.c
|
@ -17,51 +17,20 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 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
|
// This makes openat() available even though I set _POSIX_C_SOURCE and
|
||||||
// _XOPEN_SOURCE to a version of POSIX older than 2008
|
// _XOPEN_SOURCE to a version of POSIX older than 2008
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
// Undo some dwm Makefile damage and import my everything-library
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#undef PROGRAM_NAME
|
#undef PROGRAM_NAME
|
||||||
#define PROGRAM_NAME "brightness"
|
#define PROGRAM_NAME "brightness"
|
||||||
#include "liberty/liberty.c"
|
#include "liberty/liberty.c"
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
#include "ddc-ci.c"
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
#include <linux/i2c-dev.h>
|
|
||||||
#ifndef I2C_FUNC_I2C
|
|
||||||
// Fuck you, openSUSE, for fucking up the previous file, see e.g.
|
|
||||||
// https://github.com/solettaproject/soletta/commit/427f47f
|
|
||||||
#include <linux/i2c.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wait_ms (long ms)
|
|
||||||
{
|
|
||||||
struct timespec ts = { 0, ms * 1000 * 1000 };
|
|
||||||
nanosleep (&ts, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
xstrtol (const char *s, long *out)
|
xstrtol (const char *s, long *out)
|
||||||
{
|
{
|
||||||
|
@ -73,189 +42,16 @@ xstrtol (const char *s, long *out)
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
#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)
|
|
||||||
return error_set (e, "%s: %s", "ioctl", strerror (errno));
|
|
||||||
if (memcmp ("\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", buf, sizeof buf))
|
|
||||||
return error_set (e, "invalid EDID");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
is_a_display (int fd, struct error **e)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
if (fstat (fd, &st) < 0)
|
|
||||||
return error_set (e, "%s: %s", "fstat", strerror (errno));
|
|
||||||
|
|
||||||
unsigned long funcs;
|
|
||||||
if (!(st.st_mode & S_IFCHR)
|
|
||||||
|| ioctl (fd, I2C_FUNCS, &funcs) < 0
|
|
||||||
|| !(funcs & I2C_FUNC_I2C))
|
|
||||||
return error_set (e, "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)
|
|
||||||
return error_set (e, "%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)
|
|
||||||
return error_set (e, "%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))
|
|
||||||
return error_set (e, "invalid response");
|
|
||||||
if (!(length ^= 0x80))
|
|
||||||
return error_set (e, "NULL response");
|
|
||||||
|
|
||||||
// TODO: also check the checksum
|
|
||||||
|
|
||||||
*command = cmd;
|
|
||||||
memcpy (out_buf, unpacker.data + unpacker.offset, (*n_read = length - 1));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
set_brightness (int fd, long diff, struct error **e)
|
set_brightness (int fd, long diff, struct error **e)
|
||||||
{
|
{
|
||||||
uint8_t get_req[] = { VCP_BRIGHTNESS };
|
struct vcp_feature_readout readout;
|
||||||
if (!ddc_send (fd, DDC_GET_VCP_FEATURE, get_req, sizeof get_req, e))
|
if (!vcp_get_feature (fd, VCP_BRIGHTNESS, &readout, e))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
wait_ms (40);
|
int16_t req = (readout.cur * 100 + diff * readout.max + 50) / 100;
|
||||||
|
req = MIN (req, readout.max);
|
||||||
unsigned command = 0;
|
req = MAX (req, 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)
|
|
||||||
return error_set (e, "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)
|
|
||||||
return error_set (e, "error reported by monitor");
|
|
||||||
|
|
||||||
if (result != 0x00
|
|
||||||
|| vcp_opcode != VCP_BRIGHTNESS)
|
|
||||||
return error_set (e, "invalid response");
|
|
||||||
|
|
||||||
// These are unsigned but usually just one byte long
|
|
||||||
if (max < 0 || cur < 0)
|
|
||||||
return error_set (e, "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 };
|
uint8_t set_req[] = { VCP_BRIGHTNESS, req >> 8, req };
|
||||||
if (!ddc_send (fd, DDC_SET_VCP_FEATURE, set_req, sizeof set_req, e))
|
if (!ddc_send (fd, DDC_SET_VCP_FEATURE, set_req, sizeof set_req, e))
|
||||||
|
@ -263,7 +59,7 @@ set_brightness (int fd, long diff, struct error **e)
|
||||||
|
|
||||||
wait_ms (50);
|
wait_ms (50);
|
||||||
|
|
||||||
printf ("brightness set to %.2f%%\n", 100. * req / max);
|
printf ("brightness set to %.2f%%\n", 100. * req / readout.max);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
/*
|
||||||
|
* ddc-ci.c: DDC-CI utilities, Linux-only
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 - 2017, 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
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#include <linux/i2c-dev.h>
|
||||||
|
#ifndef I2C_FUNC_I2C
|
||||||
|
// Fuck you, openSUSE, for fucking up the previous file, see e.g.
|
||||||
|
// https://github.com/solettaproject/soletta/commit/427f47f
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
wait_ms (long ms)
|
||||||
|
{
|
||||||
|
struct timespec ts = { 0, ms * 1000 * 1000 };
|
||||||
|
nanosleep (&ts, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
#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
|
||||||
|
VCP_INPUT_SOURCE = 0x60 ///< Standard VCP opcode for input
|
||||||
|
};
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
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)
|
||||||
|
return error_set (e, "%s: %s", "ioctl", strerror (errno));
|
||||||
|
if (memcmp ("\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", buf, sizeof buf))
|
||||||
|
return error_set (e, "invalid EDID");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_a_display (int fd, struct error **e)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
if (fstat (fd, &st) < 0)
|
||||||
|
return error_set (e, "%s: %s", "fstat", strerror (errno));
|
||||||
|
|
||||||
|
unsigned long funcs;
|
||||||
|
if (!(st.st_mode & S_IFCHR)
|
||||||
|
|| ioctl (fd, I2C_FUNCS, &funcs) < 0
|
||||||
|
|| !(funcs & I2C_FUNC_I2C))
|
||||||
|
return error_set (e, "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)
|
||||||
|
return error_set (e, "%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)
|
||||||
|
return error_set (e, "%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))
|
||||||
|
return error_set (e, "invalid response");
|
||||||
|
if (!(length ^= 0x80))
|
||||||
|
return error_set (e, "NULL response");
|
||||||
|
|
||||||
|
// TODO: also check the checksum
|
||||||
|
|
||||||
|
*command = cmd;
|
||||||
|
memcpy (out_buf, unpacker.data + unpacker.offset, (*n_read = length - 1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
struct vcp_feature_readout
|
||||||
|
{
|
||||||
|
uint8_t type; ///< Type of the value
|
||||||
|
int16_t max; ///< Maximum value
|
||||||
|
int16_t cur; ///< Current value
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool
|
||||||
|
vcp_get_feature (int fd, uint8_t feature, struct vcp_feature_readout *out,
|
||||||
|
struct error **e)
|
||||||
|
{
|
||||||
|
uint8_t get_req[] = { feature };
|
||||||
|
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)
|
||||||
|
return error_set (e, "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);
|
||||||
|
msg_unpacker_u8 (&unpacker, &out->type);
|
||||||
|
msg_unpacker_i16 (&unpacker, &out->max);
|
||||||
|
msg_unpacker_i16 (&unpacker, &out->cur);
|
||||||
|
|
||||||
|
if (result == 0x01)
|
||||||
|
return error_set (e, "error reported by monitor");
|
||||||
|
|
||||||
|
if (result != 0x00
|
||||||
|
|| vcp_opcode != feature)
|
||||||
|
return error_set (e, "invalid response");
|
||||||
|
|
||||||
|
// These are unsigned but usually just one byte long
|
||||||
|
if (out->max < 0 || out->cur < 0)
|
||||||
|
return error_set (e, "capability range overflow");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
* input-switch.c: switches display input via DDC/CI
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017, 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#undef PROGRAM_NAME
|
||||||
|
#define PROGRAM_NAME "input-switch"
|
||||||
|
#include "liberty/liberty.c"
|
||||||
|
|
||||||
|
#include "ddc-ci.c"
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static bool
|
||||||
|
set_input_source (int fd, int input, struct error **e)
|
||||||
|
{
|
||||||
|
struct vcp_feature_readout readout;
|
||||||
|
if (!vcp_get_feature (fd, VCP_INPUT_SOURCE, &readout, e))
|
||||||
|
return false;
|
||||||
|
if (input < 0 || input > readout.max)
|
||||||
|
return error_set (e, "input index out of range");
|
||||||
|
|
||||||
|
uint8_t set_req[] = { VCP_INPUT_SOURCE, input >> 8, input };
|
||||||
|
if (!ddc_send (fd, DDC_SET_VCP_FEATURE, set_req, sizeof set_req, e))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wait_ms (50);
|
||||||
|
|
||||||
|
printf ("input set from %d to %d of %d\n", readout.cur, input, readout.max);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
i2c (int input)
|
||||||
|
{
|
||||||
|
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_input_source (fd, input, &e))
|
||||||
|
{
|
||||||
|
printf ("%s\n", e->message);
|
||||||
|
error_free (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
close (fd);
|
||||||
|
}
|
||||||
|
closedir (dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
// This list is from the MCCS 2.2a specification
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
int code; ///< Input code
|
||||||
|
const char *name; ///< Input name
|
||||||
|
int index; ///< Input index
|
||||||
|
}
|
||||||
|
g_inputs[] =
|
||||||
|
{
|
||||||
|
{ 0x01, "vga", 1, }, // Analog video (R/G/B) 1
|
||||||
|
{ 0x02, "vga", 2, }, // Analog video (R/G/B) 2
|
||||||
|
{ 0x03, "dvi", 1, }, // Digital video (TMDS) 1 DVI 1
|
||||||
|
{ 0x04, "dvi", 2, }, // Digital video (TMDS) 2 DVI 2
|
||||||
|
{ 0x05, "composite", 1, }, // Composite video 1
|
||||||
|
{ 0x06, "composite", 2, }, // Composite video 2
|
||||||
|
{ 0x07, "s-video", 1, }, // S-video 1
|
||||||
|
{ 0x08, "s-video", 2, }, // S-video 2
|
||||||
|
{ 0x09, "tuner", 1, }, // Tuner 1
|
||||||
|
{ 0x0A, "tuner", 2, }, // Tuner 2
|
||||||
|
{ 0x0B, "tuner", 3, }, // Tuner 3
|
||||||
|
{ 0x0C, "component", 1, }, // Component video (YPbPr/YCbCr) 1
|
||||||
|
{ 0x0D, "component", 2, }, // Component video (YPbPr/YCbCr) 2
|
||||||
|
{ 0x0E, "component", 3, }, // Component video (YPbPr/YCbCr) 3
|
||||||
|
{ 0x0F, "dp", 1, }, // DisplayPort 1
|
||||||
|
{ 0x10, "dp", 2, }, // DisplayPort 2
|
||||||
|
{ 0x11, "hdmi", 1, }, // Digital Video (TMDS) 3 HDMI 1
|
||||||
|
{ 0x12, "hdmi", 2, }, // Digital Video (TMDS) 4 HDMI 2
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
g_log_message_real = log_message_custom;
|
||||||
|
|
||||||
|
if (argc <= 1)
|
||||||
|
{
|
||||||
|
printf ("Usage: %s <input> [<index>]\n", argv[0]);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long input_source = 0;
|
||||||
|
if (xstrtoul (&input_source, argv[1], 10))
|
||||||
|
{
|
||||||
|
i2c (input_source);
|
||||||
|
exit (EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long index = 1;
|
||||||
|
if (argc > 2 && !xstrtoul (&index, argv[2], 10))
|
||||||
|
exit_fatal ("given index is not a number: %s", argv[2]);
|
||||||
|
for (size_t i = 0; i < N_ELEMENTS (g_inputs); i++)
|
||||||
|
if (!strcasecmp_ascii (g_inputs[i].name, argv[1])
|
||||||
|
&& g_inputs[i].index == (int) index)
|
||||||
|
input_source = g_inputs[i].code;
|
||||||
|
if (!input_source)
|
||||||
|
exit_fatal ("unknown input_source: %s %lu", argv[1], index);
|
||||||
|
|
||||||
|
i2c (input_source);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue