enlightenment/src/bin/e_acpi.c

466 lines
14 KiB
C

#include "e.h"
/* TODO:
*
* Sanatize data received from acpi for message status into something
* meaningful (ie: 00000002 == LID_CLOSED, etc, etc).
*
* Find someone with a WIFI that actually emits ACPI events and add/debug the
* E_EVENT_ACPI for wifi.
*
*/
/* local structures */
/* for simple acpi device mapping */
typedef struct _E_ACPI_Device_Simple E_ACPI_Device_Simple;
typedef struct _E_ACPI_Device_Simple_State E_ACPI_Device_Simple_State;
typedef struct _E_ACPI_Device_Multiplexed E_ACPI_Device_Multiplexed;
struct _E_ACPI_Device_Simple
{
const char *name;
// ->
int type;
};
struct _E_ACPI_Device_Simple_State
{
const char *name;
const char *bus;
const char *state;
// ->
int type;
};
struct _E_ACPI_Device_Multiplexed
{
const char *name;
const char *bus;
int status;
// ->
int type;
};
/* local function prototypes */
static Eina_Bool _e_acpi_cb_server_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
static Eina_Bool _e_acpi_cb_server_data(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
static void _e_acpi_cb_event_free(void *data EINA_UNUSED, void *event);
static int _e_acpi_lid_status_get(const char *device, const char *bus);
static Eina_Bool _e_acpi_cb_event(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
/* local variables */
static int _e_acpi_events_frozen = 0;
static Ecore_Con_Server *_e_acpid = NULL;
static Eina_List *_e_acpid_hdls = NULL;
static Eina_Strbuf *acpibuf = NULL;
static int lid_is_closed = -1;
static E_ACPI_Device_Simple _devices_simple[] =
{
/* NB: DO NOT TRANSLATE THESE. */
{"ac_adapter", E_ACPI_TYPE_AC_ADAPTER},
{"battery", E_ACPI_TYPE_BATTERY},
{"button/lid", E_ACPI_TYPE_LID},
{"button/power", E_ACPI_TYPE_POWER},
{"button/sleep", E_ACPI_TYPE_SLEEP},
{"button/volumedown", E_ACPI_TYPE_VOLUME_DOWN},
{"button/volumeup", E_ACPI_TYPE_VOLUME_UP},
{"button/mute", E_ACPI_TYPE_MUTE},
{"button/wlan", E_ACPI_TYPE_WIFI},
{"fan", E_ACPI_TYPE_FAN},
{"processor", E_ACPI_TYPE_PROCESSOR},
{"thermal_zone", E_ACPI_TYPE_THERMAL},
{"video", E_ACPI_TYPE_VIDEO},
{"video/brightnessdown", E_ACPI_TYPE_BRIGHTNESS_DOWN},
{"video/brightnessup", E_ACPI_TYPE_BRIGHTNESS_UP},
{"video/switchmode", E_ACPI_TYPE_VIDEO},
{"button/zoom", E_ACPI_TYPE_ZOOM},
{"button/screenlock", E_ACPI_TYPE_SCREENLOCK},
{"button/battery", E_ACPI_TYPE_BATTERY_BUTTON},
{"video/tabletmode", E_ACPI_TYPE_TABLET},
{NULL, E_ACPI_TYPE_UNKNOWN}
};
static E_ACPI_Device_Simple_State _devices_simple_state[] =
{
/* NB: DO NOT TRANSLATE THESE. */
{"video/tabletmode", "TBLT", "on", E_ACPI_TYPE_TABLET_ON},
{"video/tabletmode", "TBLT", "off", E_ACPI_TYPE_TABLET_OFF},
{NULL, NULL, NULL, E_ACPI_TYPE_UNKNOWN}
};
static E_ACPI_Device_Multiplexed _devices_multiplexed[] =
{
/* NB: DO NOT TRANSLATE THESE. */
/* Sony VAIO - VPCF115FM / PCG-81114L - nvidia gfx */
{"sony/hotkey", NULL, 0x10, E_ACPI_TYPE_BRIGHTNESS_DOWN},
{"sony/hotkey", NULL, 0x11, E_ACPI_TYPE_BRIGHTNESS_UP},
{"sony/hotkey", NULL, 0x12, E_ACPI_TYPE_VIDEO},
{"sony/hotkey", NULL, 0x14, E_ACPI_TYPE_ZOOM_OUT},
{"sony/hotkey", NULL, 0x15, E_ACPI_TYPE_ZOOM_IN},
{"sony/hotkey", NULL, 0x17, E_ACPI_TYPE_HIBERNATE},
{"sony/hotkey", NULL, 0xa6, E_ACPI_TYPE_ASSIST},
{"sony/hotkey", NULL, 0x20, E_ACPI_TYPE_S1},
{"sony/hotkey", NULL, 0xa5, E_ACPI_TYPE_VAIO},
/* Sony VAIO - X505 - intel gfx */
{"sony/hotkey", NULL, 0x0e, E_ACPI_TYPE_MUTE},
{"sony/hotkey", NULL, 0x0f, E_ACPI_TYPE_VOLUME},
{"sony/hotkey", NULL, 0x10, E_ACPI_TYPE_BRIGHTNESS},
{"sony/hotkey", NULL, 0x12, E_ACPI_TYPE_VIDEO},
/* HP Compaq Presario - CQ61 - intel gfx */
/** interesting these get auto-mapped to keys in x11. here for documentation
** but not enabled as we can use regular keybinds for these
{"video", "DD03", 0x87, E_ACPI_TYPE_BRIGHTNESS_DOWN},
{"video", "DD03", 0x86, E_ACPI_TYPE_BRIGHTNESS_UP},
{"video", "OVGA", 0x80, E_ACPI_TYPE_VIDEO},
*/
/* END */
{NULL, NULL, 0x00, E_ACPI_TYPE_UNKNOWN}
};
/* public variables */
E_API int E_EVENT_ACPI = 0;
/* public functions */
EINTERN int
e_acpi_init(void)
{
E_EVENT_ACPI = ecore_event_type_new();
/* check for running acpid */
if (!ecore_file_exists("/var/run/acpid.socket")) return 1;
/* try to connect to acpid socket */
_e_acpid = ecore_con_server_connect(ECORE_CON_LOCAL_SYSTEM,
"/var/run/acpid.socket", -1, NULL);
if (!_e_acpid) return 1;
/* setup handlers */
_e_acpid_hdls =
eina_list_append(_e_acpid_hdls,
ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DEL,
_e_acpi_cb_server_del, NULL));
_e_acpid_hdls =
eina_list_append(_e_acpid_hdls,
ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DATA,
_e_acpi_cb_server_data, NULL));
/* Add handlers for standard acpi events */
_e_acpid_hdls =
eina_list_append(_e_acpid_hdls,
ecore_event_handler_add(E_EVENT_ACPI,
_e_acpi_cb_event, NULL));
return 1;
}
EINTERN int
e_acpi_shutdown(void)
{
Ecore_Event_Handler *hdl;
/* cleanup event handlers */
EINA_LIST_FREE(_e_acpid_hdls, hdl)
ecore_event_handler_del(hdl);
/* kill the server if existing */
if (_e_acpid)
{
ecore_con_server_del(_e_acpid);
_e_acpid = NULL;
}
return 1;
}
EINTERN E_Acpi_Lid_Status
e_acpi_lid_status_get(void)
{
int i;
for (i = 0; _devices_simple[i].name; i++)
{
if (_devices_simple[i].type == E_ACPI_TYPE_LID)
{
/* TODO: Can bus be anything other than LID? */
return _e_acpi_lid_status_get(_devices_simple[i].name, "LID");
}
}
return E_ACPI_LID_UNKNOWN;
}
EAPI Eina_Bool
e_acpi_lid_is_closed(void)
{
if (lid_is_closed == -1)
lid_is_closed = (e_acpi_lid_status_get() == E_ACPI_LID_CLOSED);
return lid_is_closed;
}
E_API void
e_acpi_events_freeze(void)
{
_e_acpi_events_frozen++;
}
E_API void
e_acpi_events_thaw(void)
{
_e_acpi_events_frozen--;
if (_e_acpi_events_frozen < 0) _e_acpi_events_frozen = 0;
}
/* local functions */
static Eina_Bool
_e_acpi_cb_server_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
Ecore_Con_Event_Server_Del *ev;
Ecore_Event_Handler *hdl;
ev = event;
if (ev->server != _e_acpid) return ECORE_CALLBACK_PASS_ON;
/* cleanup event handlers */
EINA_LIST_FREE(_e_acpid_hdls, hdl)
ecore_event_handler_del(hdl);
/* kill the server if existing */
if (_e_acpid)
{
ecore_con_server_del(_e_acpid);
_e_acpid = NULL;
}
return ECORE_CALLBACK_PASS_ON;
}
static Eina_Bool
_e_acpi_cb_server_data(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
Ecore_Con_Event_Server_Data *ev;
E_Event_Acpi *acpi_event;
int sig, status, i, done = 0;
char device[1024], bus[1024], state[1024], *sdata;
const char *str, *p;
ev = event;
if ((!ev->data) || (ev->size < 1)) return ECORE_CALLBACK_PASS_ON;
/* write out actual acpi received data to stdout for debugging
res = fwrite(ev->data, ev->size, 1, stdout);
*/
/* data from a server isn't a string - its not 0 byte terminated. it's just
* a blob of data. copy to string and 0 byte terminate it so it can be
* string-swizzled/parsed etc. */
if (!acpibuf) acpibuf = eina_strbuf_new();
eina_strbuf_append_length(acpibuf, ev->data, ev->size);
str = eina_strbuf_string_get(acpibuf);
p = strchr(str, '\n');
if (!p) return ECORE_CALLBACK_PASS_ON;
while (p)
{
device[0] = bus[0] = state[0] = 0;
sdata = alloca(p - str + 1);
strncpy(sdata, str, (int)(p - str));
sdata[p - str] = 0;
/* parse out this acpi string into separate pieces */
if (sscanf(sdata, "%1023s %1023s %x %x",
device, bus, &sig, &status) != 4)
{
sig = -1;
status = -1;
if (sscanf(sdata, "%1023s %1023s", device, bus) != 2)
{
if (sscanf(sdata, "%1023s %1023s %1023s", device, bus, state) != 3)
goto done_event;
}
}
/* create new event structure to raise */
acpi_event = E_NEW(E_Event_Acpi, 1);
acpi_event->bus_id = eina_stringshare_add(bus);
acpi_event->signal = sig;
acpi_event->status = status;
/* FIXME: add in a key faking layer */
if ((!done) && (sig >= 0) && (status >= 0))
{
for (i = 0; _devices_multiplexed[i].name; i++)
{
// if device name matches
if ((!strcmp(device, _devices_multiplexed[i].name)) &&
// AND busname not set OR device name matches
(!_devices_multiplexed[i].bus ||
(_devices_multiplexed[i].bus &&
(!strcmp(bus, _devices_multiplexed[i].bus)))) &&
// AND status matches
(_devices_multiplexed[i].status == status))
{
acpi_event->type = _devices_multiplexed[i].type;
done = 1;
break;
}
}
}
if ((!done) && (state[0]))
{
for (i = 0; _devices_simple_state[i].name; i++)
{
if ((!strcmp(device, _devices_simple_state[i].name)) &&
((!_devices_simple_state[i].bus) || (!strcmp(bus, _devices_simple_state[i].bus))) &&
(!strcmp(state, _devices_simple_state[i].state)))
{
acpi_event->type = _devices_simple_state[i].type;
done = 1;
break;
}
}
}
if (!done)
{
// if device name matches
for (i = 0; _devices_simple[i].name; i++)
{
if (!strcmp(device, _devices_simple[i].name))
{
acpi_event->type = _devices_simple[i].type;
done = 1;
break;
}
}
}
if (!done)
{
free(acpi_event);
acpi_event = NULL;
}
else
{
switch (acpi_event->type)
{
case E_ACPI_TYPE_LID:
acpi_event->status =
_e_acpi_lid_status_get(device, bus);
printf("RRR: acpi event\n");
/* no change in lid state */
if (lid_is_closed == (acpi_event->status == E_ACPI_LID_CLOSED)) break;
lid_is_closed = (acpi_event->status == E_ACPI_LID_CLOSED);
printf("RRR: lid event for lid %i\n", lid_is_closed);
if (!e_randr2_cfg->ignore_acpi_events)
e_randr2_screen_refresh_queue(EINA_TRUE);
break;
default:
break;
}
/* actually raise the event */
ecore_event_add(E_EVENT_ACPI, acpi_event,
_e_acpi_cb_event_free, NULL);
}
done_event:
str = p + 1;
p = strchr(str, '\n');
}
if (str[0] == 0)
{
eina_strbuf_free(acpibuf);
acpibuf = NULL;
}
else
{
Eina_Strbuf *newbuf;
newbuf = eina_strbuf_new();
eina_strbuf_append(newbuf, str);
eina_strbuf_free(acpibuf);
acpibuf = newbuf;
}
return ECORE_CALLBACK_PASS_ON;
}
static void
_e_acpi_cb_event_free(void *data EINA_UNUSED, void *event)
{
E_Event_Acpi *ev;
if (!(ev = event)) return;
if (ev->device) eina_stringshare_del(ev->device);
if (ev->bus_id) eina_stringshare_del(ev->bus_id);
E_FREE(ev);
}
static int
_e_acpi_lid_status_get(const char *device, const char *bus)
{
FILE *f;
int i = 0;
char buff[PATH_MAX], *ret;
/* the acpi driver code in the kernel has a nice acpi function to return
* the lid status easily, but that function is not exposed for user_space
* so we need to check the proc fs to get the actual status */
/* make sure we have a device and bus */
if ((!device) || (!bus)) return E_ACPI_LID_UNKNOWN;
/* open the state file from /proc */
snprintf(buff, sizeof(buff), "/proc/acpi/%s/%s/state", device, bus);
if (!(f = fopen(buff, "r")))
{
/* hack around ppurka's Thinkpad (G460 + Linux) that reports lid
* state as "/proc/acpi/button/lid/LID0/state" but where the lid
* event says "button/lid LID close".
*
* so let's take the base device name "LID" and add a numeric like
* 0, 1, 2, 3 so we have LID0, LID1, LID2 etc. - try up to LID9
* and then give up.
*/
for (i = 0; i < 10; i++)
{
snprintf(buff, sizeof(buff), "/proc/acpi/%s/%s%i/state",
device, bus, i);
if ((f = fopen(buff, "r"))) break;
f = NULL;
}
if (!f) return E_ACPI_LID_UNKNOWN;
}
/* read the line from state file */
ret = fgets(buff, sizeof(buff), f);
fclose(f);
if (!ret)
return E_ACPI_LID_UNKNOWN;
/* parse out state file */
i = 0;
while (buff[i] != ':')
i++;
while (!isalnum(buff[i]))
i++;
ret = &(buff[i]);
while (isalnum(buff[i]))
i++;
buff[i] = 0;
/* compare value from state file and return something sane */
if (!strcmp(ret, "open")) return E_ACPI_LID_OPEN;
else if (!strcmp(ret, "closed"))
return E_ACPI_LID_CLOSED;
else return E_ACPI_LID_UNKNOWN;
}
static Eina_Bool
_e_acpi_cb_event(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
E_Event_Acpi *ev;
ev = event;
if (_e_acpi_events_frozen > 0) return ECORE_CALLBACK_PASS_ON;
e_bindings_acpi_event_handle(E_BINDING_CONTEXT_NONE, NULL, ev);
return ECORE_CALLBACK_PASS_ON;
}