This adds ddc monitor control and glues it into the backlight system. A result of this is now backlgiht control gadgets work screen by screen and even on desktop monitors as well as on a laptop panel. If you now put a backlight gadget on a shelf on each screen... it will control THAT screen's backlight. This requires libddcutil to be installed. That will require i2c modules (i2c-dev specifically). This means that this is likely not going to do anything useful on bsd's... unless libddcutil happens to work there by chance. so install ddcutil/libddcutil. ensure it's in ld.so.conf so setuid root processes find it (as LD_LIBRARY_PATH won't help) and enjoy your new funcky per-screen backlight controls... :) @featureenlightenment-0.24
parent
19fe001f4f
commit
eab2a34ef3
6 changed files with 705 additions and 18 deletions
@ -0,0 +1,598 @@ |
||||
#include "e_system.h" |
||||
|
||||
#include <stdint.h> |
||||
#include <stdbool.h> |
||||
|
||||
typedef struct |
||||
{ |
||||
char *edid; |
||||
int screen; |
||||
} Dev; |
||||
|
||||
typedef struct |
||||
{ |
||||
char *req, *params; // don't free - part of alloc for req struct at offset
|
||||
} Req; |
||||
|
||||
static Eina_Lock _devices_lock; |
||||
static Eina_List *_devices = NULL; |
||||
static Eina_List *_req = NULL; |
||||
static Eina_Semaphore _worker_sem; |
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// needed ddc types
|
||||
#define DDCA_EDID_MFG_ID_FIELD_SIZE 4 |
||||
#define DDCA_EDID_MODEL_NAME_FIELD_SIZE 14 |
||||
#define DDCA_EDID_SN_ASCII_FIELD_SIZE 14 |
||||
|
||||
typedef int DDCA_Status; |
||||
typedef void * DDCA_Display_Ref; |
||||
typedef void * DDCA_Display_Handle; |
||||
typedef uint8_t DDCA_Vcp_Feature_Code; |
||||
|
||||
typedef enum |
||||
{ |
||||
DDCA_IO_I2C, |
||||
DDCA_IO_ADL, |
||||
DDCA_IO_USB |
||||
} DDCA_IO_Mode; |
||||
|
||||
typedef struct |
||||
{ |
||||
int iAdapterIndex; |
||||
int iDisplayIndex; |
||||
} DDCA_Adlno; |
||||
|
||||
typedef struct |
||||
{ |
||||
DDCA_IO_Mode io_mode; |
||||
union { |
||||
int i2c_busno; |
||||
DDCA_Adlno adlno; |
||||
int hiddev_devno; |
||||
} path; |
||||
} DDCA_IO_Path; |
||||
|
||||
typedef struct |
||||
{ |
||||
uint8_t major; |
||||
uint8_t minor; |
||||
} DDCA_MCCS_Version_Spec; |
||||
|
||||
typedef struct |
||||
{ |
||||
char marker[4]; |
||||
int dispno; |
||||
DDCA_IO_Path path; |
||||
int usb_bus; |
||||
int usb_device; |
||||
char mfg_id[DDCA_EDID_MFG_ID_FIELD_SIZE]; |
||||
char model_name[DDCA_EDID_MODEL_NAME_FIELD_SIZE]; |
||||
char sn[DDCA_EDID_SN_ASCII_FIELD_SIZE]; |
||||
uint16_t product_code; |
||||
uint8_t edid_bytes[128]; |
||||
DDCA_MCCS_Version_Spec vcp_version; |
||||
DDCA_Display_Ref dref; |
||||
} DDCA_Display_Info; |
||||
|
||||
typedef struct |
||||
{ |
||||
int ct; |
||||
DDCA_Display_Info info[]; |
||||
} DDCA_Display_Info_List; |
||||
|
||||
typedef struct |
||||
{ |
||||
uint8_t mh; |
||||
uint8_t ml; |
||||
uint8_t sh; |
||||
uint8_t sl; |
||||
} DDCA_Non_Table_Vcp_Value; |
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// ddc feature codes we plan to support
|
||||
// 0x12 contrast (0->100, 75)
|
||||
// 0x16 video gain r (0->100, 50)
|
||||
// 0x18 video gain g (0->100, 50)
|
||||
// 0x1a video gain b (0->100, 50)
|
||||
// 0xaa screen rotation (read only)
|
||||
// {0x01, "0 degrees"},
|
||||
// {0x02, "90 degrees"},
|
||||
// {0x03, "180 degrees"},
|
||||
// {0x04, "270 degrees"},
|
||||
// {0xff, "Display cannot supply orientation"},
|
||||
// 0x6c video black level r (0->255, 128)
|
||||
// 0x6e video black level g (0->255, 128)
|
||||
// 0x70 video black level b (0->255, 128)
|
||||
// 0x6b backlight level w
|
||||
// 0x6d backlight level r
|
||||
// 0x6f backlight level g
|
||||
// 0x71 backlight level b
|
||||
// 0xd6 power mode (10x = on, 0x4 = off)
|
||||
// {0x01, "DPM: On, DPMS: Off"},
|
||||
// {0x02, "DPM: Off, DPMS: Standby"},
|
||||
// {0x03, "DPM: Off, DPMS: Suspend"},
|
||||
// {0x04, "DPM: Off, DPMS: Off" },
|
||||
// 0xb6 display tech (readonly) (0x03 = LCD active matrix)
|
||||
// {0x01, "CRT (shadow mask)"},
|
||||
// {0x02, "CRT (aperture grill)"},
|
||||
// {0x03, "LCD (active matrix)"}, // TFT in 2.0
|
||||
// {0x04, "LCos"},
|
||||
// {0x05, "Plasma"},
|
||||
// {0x06, "OLED"},
|
||||
// {0x07, "EL"},
|
||||
// {0x08, "Dynamic MEM"}, // MEM in 2.0
|
||||
// {0x09, "Static MEM"}, // not in 2.0
|
||||
// 0x62 speaker volume (0-100)
|
||||
// 0x8d audio mute (1=mute, 2=unmute)
|
||||
// 0xca OSD (1=disabled, 2=enabled)
|
||||
// 0xda scan mode
|
||||
// {0x00, "Normal operation"},
|
||||
// {0x01, "Underscan"},
|
||||
// {0x02, "Overscan"},
|
||||
// {0x03, "Widescreen" }
|
||||
// 0xdb image mode
|
||||
// {0x00, "No effect"},
|
||||
// {0x01, "Full mode"},
|
||||
// {0x02, "Zoom mode"},
|
||||
// {0x03, "Squeeze mode" },
|
||||
// {0x04, "Variable"},
|
||||
// 0x8d blank state (1=blank, 2 = unblack)
|
||||
// 0x82 horiz mirror (0=normal, 1 = mirror)
|
||||
// 0x84 vert mirror (0=normal, 1 = mirror)
|
||||
// 0x63 speaker sel (0=front l/r, 1=side l/r, 2=rear l/r, 3=subwoofer)
|
||||
// 0x86 scaling values
|
||||
// {0x01, "No scaling"},
|
||||
// {0x02, "Max image, no aspect ration distortion"},
|
||||
// {0x03, "Max vertical image, no aspect ratio distortion"},
|
||||
// {0x04, "Max horizontal image, no aspect ratio distortion"},
|
||||
// {0x05, "Max vertical image with aspect ratio distortion"},
|
||||
// {0x06, "Max horizontal image with aspect ratio distortion"},
|
||||
// {0x07, "Linear expansion (compression) on horizontal axis"}, // Full mode
|
||||
// {0x08, "Linear expansion (compression) on h and v axes"}, // Zoom mode
|
||||
// {0x09, "Squeeze mode"},
|
||||
// {0x0a, "Non-linear expansion"}, // Variable
|
||||
// 0x94 stereo mode
|
||||
// {0x00, "Speaker off/Audio not supported"},
|
||||
// {0x01, "Mono"},
|
||||
// {0x02, "Stereo"},
|
||||
// {0x03, "Stereo expanded"},
|
||||
// {0x11, "SRS 2.0"},
|
||||
// {0x12, "SRS 2.1"},
|
||||
// {0x13, "SRS 3.1"},
|
||||
// {0x14, "SRS 4.1"},
|
||||
// {0x15, "SRS 5.1"},
|
||||
// {0x16, "SRS 6.1"},
|
||||
// {0x17, "SRS 7.1"},
|
||||
// {0x21, "Dolby 2.0"},
|
||||
// {0x22, "Dolby 2.1"},
|
||||
// {0x23, "Dolby 3.1"},
|
||||
// {0x24, "Dolby 4.1"},
|
||||
// {0x25, "Dolby 5.1"},
|
||||
// {0x26, "Dolby 6.1"},
|
||||
// {0x27, "Dolby 7.1"},
|
||||
// {0x31, "THX 2.0"},
|
||||
// {0x32, "THX 2.1"},
|
||||
// {0x33, "THX 3.1"},
|
||||
// {0x34, "THX 4.1"},
|
||||
// {0x35, "THX 5.1"},
|
||||
// {0x36, "THX 6.1"},
|
||||
// {0x37, "THX 7.1"},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// ddc lib handle and func symbols
|
||||
static void *ddc_lib = NULL; |
||||
struct { |
||||
DDCA_Status (*ddca_get_display_info_list2) |
||||
(bool include_invalid_displays, DDCA_Display_Info_List **dlist_loc); |
||||
void (*ddca_free_display_info_list) |
||||
(DDCA_Display_Info_List *dlist); |
||||
DDCA_Status (*ddca_open_display2) |
||||
(DDCA_Display_Ref ddca_dref, bool wait, DDCA_Display_Handle *ddca_dh_loc); |
||||
DDCA_Status (*ddca_get_non_table_vcp_value) |
||||
(DDCA_Display_Handle ddca_dh, DDCA_Vcp_Feature_Code feature_code, DDCA_Non_Table_Vcp_Value *valrec); |
||||
DDCA_Status (*ddca_set_non_table_vcp_value) |
||||
(DDCA_Display_Handle ddca_dh, DDCA_Vcp_Feature_Code feature_code, uint8_t hi_byte, uint8_t lo_byte); |
||||
DDCA_Status (*ddca_close_display) |
||||
(DDCA_Display_Handle ddca_dh); |
||||
} ddc_func; |
||||
|
||||
static DDCA_Display_Info_List *ddc_dlist = NULL; |
||||
static DDCA_Display_Handle *ddc_dh = NULL; |
||||
|
||||
static void |
||||
_ddc_clean(void) |
||||
{ |
||||
int i; |
||||
|
||||
if (!ddc_lib) return; |
||||
if (ddc_dlist) |
||||
{ |
||||
if (ddc_dh) |
||||
{ |
||||
for (i = 0; i < ddc_dlist->ct; i++) |
||||
{ |
||||
ddc_func.ddca_close_display(ddc_dh[i]); |
||||
} |
||||
free(ddc_dh); |
||||
ddc_dh = NULL; |
||||
} |
||||
ddc_func.ddca_free_display_info_list(ddc_dlist); |
||||
ddc_dlist = NULL; |
||||
} |
||||
} |
||||
|
||||
static Eina_Bool |
||||
_ddc_probe(void) |
||||
{ |
||||
int i; |
||||
|
||||
if (!ddc_lib) return EINA_FALSE; |
||||
_ddc_clean(); |
||||
|
||||
// the below can be quite sluggish, so we don't want to do this
|
||||
// often, even though this is isolated in a worker thread. it will
|
||||
// block the ddc worker thread while this is done.
|
||||
if (ddc_func.ddca_get_display_info_list2(false, &ddc_dlist) != 0) |
||||
return EINA_FALSE; |
||||
if (!ddc_dlist) return EINA_FALSE; |
||||
ddc_dh = calloc(ddc_dlist->ct, sizeof(DDCA_Display_Handle)); |
||||
if (!ddc_dh) |
||||
{ |
||||
ddc_func.ddca_free_display_info_list(ddc_dlist); |
||||
ddc_dlist = NULL; |
||||
free(ddc_dh); |
||||
} |
||||
for (i = 0; i < ddc_dlist->ct; i++) |
||||
{ |
||||
DDCA_Display_Info *dinfo = &(ddc_dlist->info[i]); |
||||
DDCA_Display_Ref dref = dinfo->dref; |
||||
int j; |
||||
Dev *d; |
||||
|
||||
if (ddc_func.ddca_open_display2(dref, false, &(ddc_dh[i])) != 0) |
||||
{ |
||||
for (i = i - 1; i >= 0; i--) |
||||
{ |
||||
ddc_func.ddca_close_display(ddc_dh[i]); |
||||
} |
||||
free(ddc_dh); |
||||
ddc_dh = NULL; |
||||
ddc_func.ddca_free_display_info_list(ddc_dlist); |
||||
ddc_dlist = NULL; |
||||
return EINA_FALSE; |
||||
} |
||||
d = calloc(1, sizeof(Dev)); |
||||
d->edid = malloc((128 * 2) + 1); |
||||
if (d->edid) |
||||
{ |
||||
for (j = 0; j < 128; j++) |
||||
snprintf(&(d->edid[j * 2]), 3, "%02x", dinfo->edid_bytes[j]); |
||||
d->edid[j * 2] = 0; |
||||
d->screen = i; |
||||
eina_lock_take(&_devices_lock); |
||||
_devices = eina_list_append(_devices, d); |
||||
eina_lock_release(&_devices_lock); |
||||
} |
||||
} |
||||
return EINA_TRUE; |
||||
} |
||||
|
||||
static Eina_Bool |
||||
_ddc_init(void) |
||||
{ |
||||
ddc_lib = dlopen("libddcutil.so.2", RTLD_NOW | RTLD_LOCAL); |
||||
if (!ddc_lib) return EINA_FALSE; |
||||
#define SYM(_x) \ |
||||
do { \
|
||||
ddc_func._x = dlsym(ddc_lib, #_x); \
|
||||
if (!ddc_func._x) return EINA_FALSE; \
|
||||
} while (0) |
||||
SYM(ddca_get_display_info_list2); |
||||
SYM(ddca_free_display_info_list); |
||||
SYM(ddca_open_display2); |
||||
SYM(ddca_get_non_table_vcp_value); |
||||
SYM(ddca_set_non_table_vcp_value); |
||||
SYM(ddca_close_display); |
||||
|
||||
// brute force modprobe this as it likely is needed - probe will fail
|
||||
// if this doesn't work or find devices anyway
|
||||
system("modprobe i2c-dev"); |
||||
if (!_ddc_probe()) return EINA_FALSE; |
||||
|
||||
return EINA_TRUE; |
||||
} |
||||
|
||||
static Req * |
||||
_req_alloc(const char *req, const char *params) |
||||
{ |
||||
Req *r; |
||||
|
||||
if (!params) return NULL; |
||||
r = calloc(1, sizeof(Req) + strlen(req) + 1 + strlen(params) + 1); |
||||
if (!r) return NULL; |
||||
r->req = ((char *)r) + sizeof(Req); |
||||
strcpy(r->req, req); |
||||
r->params = r->req + strlen(r->req) + 1; |
||||
strcpy(r->params, params); |
||||
return r; |
||||
} |
||||
|
||||
static void |
||||
_request(const char *req, const char *params) |
||||
{ |
||||
Eina_List *l; |
||||
Req *r2, *r = _req_alloc(req, params); |
||||
if (!r) return; |
||||
|
||||
eina_lock_take(&_devices_lock); |
||||
EINA_LIST_FOREACH(_req, l, r2) |
||||
{ |
||||
if (!strcmp(r2->req, r->req)) |
||||
{ |
||||
if (!strcmp(req, "refresh")) |
||||
{ |
||||
_req = eina_list_remove_list(_req, l); |
||||
free(r2); |
||||
break; |
||||
} |
||||
else if ((!strcmp(req, "val-set")) || |
||||
(!strcmp(req, "val-get"))) |
||||
{ |
||||
if ((!strncmp(params, r2->params, 256)) && |
||||
(params[256] == ' ')) |
||||
{ |
||||
const char *space = strchr(params + 256 + 1, ' '); |
||||
if (!strncmp(params, r2->params, (space - params))) |
||||
{ |
||||
_req = eina_list_remove_list(_req, l); |
||||
free(r2); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
_req = eina_list_append(_req, r); |
||||
eina_semaphore_release(&_worker_sem, 1); |
||||
eina_lock_release(&_devices_lock); |
||||
} |
||||
|
||||
static void |
||||
_do_list(Ecore_Thread *th) |
||||
{ |
||||
Eina_List *l; |
||||
Dev *d; |
||||
Req *r; |
||||
Eina_Strbuf *sbuf; |
||||
|
||||
sbuf = eina_strbuf_new(); |
||||
if (sbuf) |
||||
{ |
||||
eina_lock_take(&_devices_lock); |
||||
EINA_LIST_FOREACH(_devices, l, d) |
||||
{ |
||||
eina_strbuf_append(sbuf, d->edid); |
||||
if (l->next) eina_strbuf_append(sbuf, " "); |
||||
} |
||||
eina_lock_release(&_devices_lock); |
||||
|
||||
r = _req_alloc("ddc-list", eina_strbuf_string_get(sbuf)); |
||||
if (r) ecore_thread_feedback(th, r); |
||||
eina_strbuf_free(sbuf); |
||||
} |
||||
} |
||||
|
||||
static Eina_Bool |
||||
_id_ok(int id) |
||||
{ |
||||
if (id == 0x10) return EINA_TRUE; // backlight allowed
|
||||
return EINA_FALSE; |
||||
} |
||||
|
||||
static Dev * |
||||
_dev_find(const char *edid) |
||||
{ |
||||
Eina_List *l; |
||||
Dev *d; |
||||
|
||||
EINA_LIST_FOREACH(_devices, l, d) |
||||
{ |
||||
if (!strcmp(d->edid, edid)) |
||||
{ |
||||
return d; |
||||
} |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
static void |
||||
_do_val_set(Ecore_Thread *th, const char *edid, int id, int val) |
||||
{ |
||||
Dev *d; |
||||
Req *r; |
||||
int screen; |
||||
char buf[512]; |
||||
|
||||
if (!ddc_lib) goto err; |
||||
if (!edid) goto err; |
||||
if (!_id_ok(id)) goto err; |
||||
if ((val < 0) || (val >= 65536)) goto err; |
||||
|
||||
eina_lock_take(&_devices_lock); |
||||
d = _dev_find(edid); |
||||
if (!d) |
||||
{ |
||||
eina_lock_release(&_devices_lock); |
||||
goto err; |
||||
} |
||||
screen = d->screen; |
||||
eina_lock_release(&_devices_lock); |
||||
|
||||
if (ddc_func.ddca_set_non_table_vcp_value |
||||
(ddc_dh[screen], id, (val >> 8) & 0xff, val & 0xff) == 0) |
||||
{ |
||||
snprintf(buf, sizeof(buf), "%s %i %i ok", edid, id, val); |
||||
} |
||||
else |
||||
{ |
||||
err: |
||||
snprintf(buf, sizeof(buf), "%s %i %i err", edid, id, val); |
||||
} |
||||
r = _req_alloc("ddc-val-set", buf); |
||||
if (r) ecore_thread_feedback(th, r); |
||||
} |
||||
|
||||
static void |
||||
_do_val_get(Ecore_Thread *th, const char *edid, int id) |
||||
{ |
||||
Dev *d; |
||||
Req *r; |
||||
int screen, val; |
||||
char buf[512]; |
||||
DDCA_Non_Table_Vcp_Value valrec; |
||||
|
||||
if (!ddc_lib) goto err; |
||||
if (!edid) goto err; |
||||
if (!_id_ok(id)) goto err; |
||||
|
||||
eina_lock_take(&_devices_lock); |
||||
d = _dev_find(edid); |
||||
if (!d) |
||||
{ |
||||
eina_lock_release(&_devices_lock); |
||||
goto err; |
||||
} |
||||
screen = d->screen; |
||||
eina_lock_release(&_devices_lock); |
||||
|
||||
if (ddc_func.ddca_get_non_table_vcp_value |
||||
(ddc_dh[screen], id, &valrec) == 0) |
||||
{ |
||||
val = valrec.sl | (valrec.sh << 8); |
||||
snprintf(buf, sizeof(buf), "%s %i %i", edid, id, val); |
||||
} |
||||
else |
||||
{ |
||||
err: |
||||
snprintf(buf, sizeof(buf), "%s %i -1", edid, id); |
||||
} |
||||
r = _req_alloc("ddc-val-get", buf); |
||||
if (r) ecore_thread_feedback(th, r); |
||||
} |
||||
|
||||
static void |
||||
_cb_worker(void *data EINA_UNUSED, Ecore_Thread *th) |
||||
{ |
||||
Req *r; |
||||
|
||||
_ddc_init(); |
||||
_do_list(th); |
||||
|
||||
for (;;) |
||||
{ |
||||
// wait for requests
|
||||
eina_semaphore_lock(&_worker_sem); |
||||
eina_lock_take(&_devices_lock); |
||||
if (_req) |
||||
{ |
||||
r = _req->data; |
||||
if (r) |
||||
{ |
||||
_req = eina_list_remove_list(_req, _req); |
||||
eina_lock_release(&_devices_lock); |
||||
if (!strcmp(r->req, "list")) |
||||
{ |
||||
_do_list(th); |
||||
} |
||||
else if (!strcmp(r->req, "refresh")) |
||||
{ |
||||
_ddc_probe(); |
||||
_do_list(th); |
||||
} |
||||
else if (!strcmp(r->req, "val-set")) |
||||
{ |
||||
int id, val; |
||||
char edid[257]; |
||||
if (sscanf(r->params, "%256s %i %i", edid, &id, &val) == 3) |
||||
_do_val_set(th, edid, id, val); |
||||
} |
||||
else if (!strcmp(r->req, "val-get")) |
||||
{ |
||||
int id; |
||||
char edid[257]; |
||||
if (sscanf(r->params, "%256s %i", edid, &id) == 2) |
||||
_do_val_get(th, edid, id); |
||||
} |
||||
free(r); |
||||
} |
||||
else eina_lock_release(&_devices_lock); |
||||
} |
||||
else eina_lock_release(&_devices_lock); |
||||
} |
||||
} |
||||
|
||||
static void |
||||
_cb_worker_message(void *data EINA_UNUSED, Ecore_Thread *th EINA_UNUSED, void *msg_data) |
||||
{ |
||||
Req *r = msg_data; |
||||
|
||||
if (!r) return; |
||||
e_system_inout_command_send(r->req, "%s", r->params); |
||||
free(r); |
||||
} |
||||
|
||||
static void |
||||
_cb_worker_end(void *data EINA_UNUSED, Ecore_Thread *th EINA_UNUSED) |
||||
{ |
||||
} |
||||
|
||||
static void |
||||
_cb_worker_cancel(void *data EINA_UNUSED, Ecore_Thread *th EINA_UNUSED) |
||||
{ |
||||
} |
||||
|
||||
static void |
||||
_cb_ddc_list(void *data EINA_UNUSED, const char *params) |
||||
{ |
||||
_request("list", params); |
||||
} |
||||
|
||||
static void |
||||
_cb_ddc_refresh(void *data EINA_UNUSED, const char *params) |
||||
{ |
||||
_request("refresh", params); |
||||
} |
||||
|
||||
static void |
||||
_cb_ddc_val_set(void *data EINA_UNUSED, const char *params) |
||||
{ |
||||
_request("val-set", params); |
||||
} |
||||
|
||||
static void |
||||
_cb_ddc_val_get(void *data EINA_UNUSED, const char *params) |
||||
{ |
||||
_request("val-get", params); |
||||
} |
||||
|
||||
void |
||||
e_system_ddc_init(void) |
||||
{ |
||||
eina_lock_new(&_devices_lock); |
||||
eina_semaphore_new(&_worker_sem, 0); |
||||
ecore_thread_feedback_run(_cb_worker, _cb_worker_message, |
||||
_cb_worker_end, _cb_worker_cancel, |
||||
NULL, EINA_TRUE); |
||||
e_system_inout_command_register("ddc-list", _cb_ddc_list, NULL); |
||||
e_system_inout_command_register("ddc-refresh", _cb_ddc_refresh, NULL); |
||||
e_system_inout_command_register("ddc-val-set", _cb_ddc_val_set, NULL); |
||||
e_system_inout_command_register("ddc-val-get", _cb_ddc_val_get, NULL); |
||||
} |
||||
|
||||
void |
||||
e_system_ddc_shutdown(void) |
||||
{ |
||||
// only shutdown things we really have to - no need to free mem etc.
|
||||
} |
Loading…
Reference in new issue