1 #include "e.h"
2 
3 /* TODO:
4  *
5  * Sanatize data received from acpi for message status into something
6  * meaningful (ie: 00000002 == LID_CLOSED, etc, etc).
7  *
8  * Find someone with a WIFI that actually emits ACPI events and add/debug the
9  * E_EVENT_ACPI for wifi.
10  *
11  */
12 
13 /* local structures */
14 /* for simple acpi device mapping */
15 typedef struct _E_ACPI_Device_Simple       E_ACPI_Device_Simple;
16 typedef struct _E_ACPI_Device_Simple_State E_ACPI_Device_Simple_State;
17 typedef struct _E_ACPI_Device_Multiplexed  E_ACPI_Device_Multiplexed;
18 
19 struct _E_ACPI_Device_Simple
20 {
21    const char *name;
22    // ->
23    int         type;
24 };
25 
26 struct _E_ACPI_Device_Simple_State
27 {
28    const char *name;
29    const char *bus;
30    const char *state;
31    // ->
32    int         type;
33 };
34 
35 struct _E_ACPI_Device_Multiplexed
36 {
37    const char *name;
38    const char *bus;
39    int         status;
40    // ->
41    int         type;
42 };
43 
44 /* local function prototypes */
45 static Eina_Bool _e_acpi_cb_server_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
46 static Eina_Bool _e_acpi_cb_server_data(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
47 static void      _e_acpi_cb_event_free(void *data EINA_UNUSED, void *event);
48 static int       _e_acpi_lid_status_get(const char *device, const char *bus);
49 static Eina_Bool _e_acpi_cb_event(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
50 
51 /* local variables */
52 static int _e_acpi_events_frozen = 0;
53 static Ecore_Con_Server *_e_acpid = NULL;
54 static Eina_List *_e_acpid_hdls = NULL;
55 static Eina_Strbuf *acpibuf = NULL;
56 static int lid_is_closed = -1;
57 
58 static E_ACPI_Device_Simple _devices_simple[] =
59 {
60    /* NB: DO NOT TRANSLATE THESE. */
61    {"ac_adapter", E_ACPI_TYPE_AC_ADAPTER},
62    {"battery", E_ACPI_TYPE_BATTERY},
63    {"button/lid", E_ACPI_TYPE_LID},
64    {"button/power", E_ACPI_TYPE_POWER},
65    {"button/sleep", E_ACPI_TYPE_SLEEP},
66    {"button/volumedown", E_ACPI_TYPE_VOLUME_DOWN},
67    {"button/volumeup", E_ACPI_TYPE_VOLUME_UP},
68    {"button/mute", E_ACPI_TYPE_MUTE},
69    {"button/wlan", E_ACPI_TYPE_WIFI},
70    {"fan", E_ACPI_TYPE_FAN},
71    {"processor", E_ACPI_TYPE_PROCESSOR},
72    {"thermal_zone", E_ACPI_TYPE_THERMAL},
73    {"video", E_ACPI_TYPE_VIDEO},
74    {"video/brightnessdown", E_ACPI_TYPE_BRIGHTNESS_DOWN},
75    {"video/brightnessup", E_ACPI_TYPE_BRIGHTNESS_UP},
76    {"video/switchmode", E_ACPI_TYPE_VIDEO},
77    {"button/zoom", E_ACPI_TYPE_ZOOM},
78    {"button/screenlock", E_ACPI_TYPE_SCREENLOCK},
79    {"button/battery", E_ACPI_TYPE_BATTERY_BUTTON},
80    {"video/tabletmode", E_ACPI_TYPE_TABLET},
81 
82    {NULL, E_ACPI_TYPE_UNKNOWN}
83 };
84 
85 static E_ACPI_Device_Simple_State _devices_simple_state[] =
86 {
87    /* NB: DO NOT TRANSLATE THESE. */
88    {"video/tabletmode", "TBLT", "on", E_ACPI_TYPE_TABLET_ON},
89    {"video/tabletmode", "TBLT", "off", E_ACPI_TYPE_TABLET_OFF},
90 
91    {NULL, NULL, NULL, E_ACPI_TYPE_UNKNOWN}
92 };
93 
94 static E_ACPI_Device_Multiplexed _devices_multiplexed[] =
95 {
96    /* NB: DO NOT TRANSLATE THESE. */
97 /* Sony VAIO - VPCF115FM / PCG-81114L - nvidia gfx */
98    {"sony/hotkey", NULL, 0x10, E_ACPI_TYPE_BRIGHTNESS_DOWN},
99    {"sony/hotkey", NULL, 0x11, E_ACPI_TYPE_BRIGHTNESS_UP},
100    {"sony/hotkey", NULL, 0x12, E_ACPI_TYPE_VIDEO},
101    {"sony/hotkey", NULL, 0x14, E_ACPI_TYPE_ZOOM_OUT},
102    {"sony/hotkey", NULL, 0x15, E_ACPI_TYPE_ZOOM_IN},
103    {"sony/hotkey", NULL, 0x17, E_ACPI_TYPE_HIBERNATE},
104    {"sony/hotkey", NULL, 0xa6, E_ACPI_TYPE_ASSIST},
105    {"sony/hotkey", NULL, 0x20, E_ACPI_TYPE_S1},
106    {"sony/hotkey", NULL, 0xa5, E_ACPI_TYPE_VAIO},
107 
108 /* Sony VAIO - X505 - intel gfx */
109    {"sony/hotkey", NULL, 0x0e, E_ACPI_TYPE_MUTE},
110    {"sony/hotkey", NULL, 0x0f, E_ACPI_TYPE_VOLUME},
111    {"sony/hotkey", NULL, 0x10, E_ACPI_TYPE_BRIGHTNESS},
112    {"sony/hotkey", NULL, 0x12, E_ACPI_TYPE_VIDEO},
113 
114 /* HP Compaq Presario - CQ61 - intel gfx */
115 /** interesting these get auto-mapped to keys in x11. here for documentation
116 ** but not enabled as we can use regular keybinds for these
117    {"video",       "DD03", 0x87, E_ACPI_TYPE_BRIGHTNESS_DOWN},
118    {"video",       "DD03", 0x86, E_ACPI_TYPE_BRIGHTNESS_UP},
119    {"video",       "OVGA", 0x80, E_ACPI_TYPE_VIDEO},
120 */
121 /* END */
122    {NULL, NULL, 0x00, E_ACPI_TYPE_UNKNOWN}
123 };
124 
125 /* public variables */
126 E_API int E_EVENT_ACPI = 0;
127 
128 static Eina_Bool
_acpi_error_cb(void * data EINA_UNUSED)129 _acpi_error_cb(void *data EINA_UNUSED)
130 {
131    e_util_dialog_show
132      (_("ACPI Error"),
133       _("You seem to have an ACPI based system, but<br>"
134         "<hilight>acpid</hilight> does not seem to be running or<br>"
135         "contactable. Perhaps enable the <hilight>acpid</hilight><br>"
136         "service on your system?"));
137    return EINA_FALSE;
138 }
139 
140 /* public functions */
141 EINTERN int
e_acpi_init(void)142 e_acpi_init(void)
143 {
144    E_EVENT_ACPI = ecore_event_type_new();
145 
146    /* check for running acpid */
147    if (!ecore_file_exists("/var/run/acpid.socket"))
148      {
149         if (ecore_file_exists("/proc/acpi"))
150           ecore_timer_add(5.0, _acpi_error_cb, NULL);
151         return 1;
152      }
153 
154    /* try to connect to acpid socket */
155    _e_acpid = ecore_con_server_connect(ECORE_CON_LOCAL_SYSTEM,
156                                        "/var/run/acpid.socket", -1, NULL);
157    if (!_e_acpid) return 1;
158 
159    /* setup handlers */
160    _e_acpid_hdls =
161      eina_list_append(_e_acpid_hdls,
162                       ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DEL,
163                                               _e_acpi_cb_server_del, NULL));
164    _e_acpid_hdls =
165      eina_list_append(_e_acpid_hdls,
166                       ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DATA,
167                                               _e_acpi_cb_server_data, NULL));
168 
169    /* Add handlers for standard acpi events */
170    _e_acpid_hdls =
171      eina_list_append(_e_acpid_hdls,
172                       ecore_event_handler_add(E_EVENT_ACPI,
173                                               _e_acpi_cb_event, NULL));
174    return 1;
175 }
176 
177 EINTERN int
e_acpi_shutdown(void)178 e_acpi_shutdown(void)
179 {
180    Ecore_Event_Handler *hdl;
181 
182    /* cleanup event handlers */
183    EINA_LIST_FREE(_e_acpid_hdls, hdl)
184      ecore_event_handler_del(hdl);
185 
186    /* kill the server if existing */
187    if (_e_acpid)
188      {
189         ecore_con_server_del(_e_acpid);
190         _e_acpid = NULL;
191      }
192    return 1;
193 }
194 
195 EINTERN E_Acpi_Lid_Status
e_acpi_lid_status_get(void)196 e_acpi_lid_status_get(void)
197 {
198    int i;
199 
200    for (i = 0; _devices_simple[i].name; i++)
201      {
202         if (_devices_simple[i].type == E_ACPI_TYPE_LID)
203           {
204              /* TODO: Can bus be anything other than LID? */
205              return _e_acpi_lid_status_get(_devices_simple[i].name, "LID");
206           }
207      }
208 
209    return E_ACPI_LID_UNKNOWN;
210 }
211 
212 E_API Eina_Bool
e_acpi_lid_is_closed(void)213 e_acpi_lid_is_closed(void)
214 {
215    if (lid_is_closed == -1)
216      lid_is_closed = (e_acpi_lid_status_get() == E_ACPI_LID_CLOSED);
217    return lid_is_closed;
218 }
219 
220 E_API void
e_acpi_events_freeze(void)221 e_acpi_events_freeze(void)
222 {
223    _e_acpi_events_frozen++;
224 }
225 
226 E_API void
e_acpi_events_thaw(void)227 e_acpi_events_thaw(void)
228 {
229    _e_acpi_events_frozen--;
230    if (_e_acpi_events_frozen < 0) _e_acpi_events_frozen = 0;
231 }
232 
233 /* local functions */
234 static Eina_Bool
_e_acpi_cb_server_del(void * data EINA_UNUSED,int type EINA_UNUSED,void * event)235 _e_acpi_cb_server_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
236 {
237    Ecore_Con_Event_Server_Del *ev;
238    Ecore_Event_Handler *hdl;
239 
240    ev = event;
241    if (ev->server != _e_acpid) return ECORE_CALLBACK_PASS_ON;
242 
243    /* cleanup event handlers */
244    EINA_LIST_FREE(_e_acpid_hdls, hdl)
245      ecore_event_handler_del(hdl);
246 
247    /* kill the server if existing */
248    if (_e_acpid)
249      {
250         ecore_con_server_del(_e_acpid);
251         _e_acpid = NULL;
252      }
253    return ECORE_CALLBACK_PASS_ON;
254 }
255 
256 static Eina_Bool
_e_acpi_cb_server_data(void * data EINA_UNUSED,int type EINA_UNUSED,void * event)257 _e_acpi_cb_server_data(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
258 {
259    Ecore_Con_Event_Server_Data *ev;
260    E_Event_Acpi *acpi_event;
261    int sig, status, i, done = 0;
262    char device[1024], bus[1024], state[1024], *sdata;
263    const char *str, *p;
264 
265    ev = event;
266 
267    if ((!ev->data) || (ev->size < 1)) return ECORE_CALLBACK_PASS_ON;
268 
269    /* write out actual acpi received data to stdout for debugging
270       res = fwrite(ev->data, ev->size, 1, stdout);
271     */
272    /* data from a server isn't a string - its not 0 byte terminated. it's just
273     * a blob of data. copy to string and 0 byte terminate it so it can be
274     * string-swizzled/parsed etc. */
275    if (!acpibuf) acpibuf = eina_strbuf_new();
276    eina_strbuf_append_length(acpibuf, ev->data, ev->size);
277    str = eina_strbuf_string_get(acpibuf);
278    p = strchr(str, '\n');
279    if (!p) return ECORE_CALLBACK_PASS_ON;
280    while (p)
281      {
282         device[0] = bus[0] = state[0] = 0;
283         sdata = alloca(p - str + 1);
284         strncpy(sdata, str, (int)(p - str));
285         sdata[p - str] = 0;
286         /* parse out this acpi string into separate pieces */
287         if (sscanf(sdata, "%1023s %1023s %x %x",
288                    device, bus, &sig, &status) != 4)
289           {
290              sig = -1;
291              status = -1;
292              if (sscanf(sdata, "%1023s %1023s", device, bus) != 2)
293                {
294                   if (sscanf(sdata, "%1023s %1023s %1023s", device, bus, state) != 3)
295                     goto done_event;
296                }
297           }
298 
299         /* create new event structure to raise */
300         acpi_event = E_NEW(E_Event_Acpi, 1);
301         acpi_event->bus_id = eina_stringshare_add(bus);
302         acpi_event->signal = sig;
303         acpi_event->status = status;
304 
305         /* FIXME: add in a key faking layer */
306         if ((!done) && (sig >= 0) && (status >= 0))
307           {
308              for (i = 0; _devices_multiplexed[i].name; i++)
309                {
310                   // if device name matches
311                   if ((!strcmp(device, _devices_multiplexed[i].name)) &&
312                       // AND busname not set OR device name matches
313                       (!_devices_multiplexed[i].bus ||
314                        (_devices_multiplexed[i].bus &&
315                         (!strcmp(bus, _devices_multiplexed[i].bus)))) &&
316                       // AND status matches
317                       (_devices_multiplexed[i].status == status))
318                     {
319                        acpi_event->type = _devices_multiplexed[i].type;
320                        done = 1;
321                        break;
322                     }
323                }
324           }
325         if ((!done) && (state[0]))
326           {
327              for (i = 0; _devices_simple_state[i].name; i++)
328                {
329                   if ((!strcmp(device, _devices_simple_state[i].name)) &&
330                       ((!_devices_simple_state[i].bus) || (!strcmp(bus, _devices_simple_state[i].bus))) &&
331                       (!strcmp(state, _devices_simple_state[i].state)))
332                     {
333                        acpi_event->type =  _devices_simple_state[i].type;
334                        done = 1;
335                        break;
336                     }
337                }
338           }
339         if (!done)
340           {
341              // if device name matches
342              for (i = 0; _devices_simple[i].name; i++)
343                {
344                   if (!strcmp(device, _devices_simple[i].name))
345                     {
346                        acpi_event->type = _devices_simple[i].type;
347                        done = 1;
348                        break;
349                     }
350                }
351           }
352         if (!done)
353           {
354              free(acpi_event);
355              acpi_event = NULL;
356           }
357         else
358           {
359              switch (acpi_event->type)
360                {
361                 case E_ACPI_TYPE_LID:
362                   acpi_event->status =
363                     _e_acpi_lid_status_get(device, bus);
364                   printf("RRR: acpi event @%1.8f\n", ecore_time_get());
365                   /* no change in lid state */
366                   if (lid_is_closed == (acpi_event->status == E_ACPI_LID_CLOSED)) break;
367                   lid_is_closed = (acpi_event->status == E_ACPI_LID_CLOSED);
368                   printf("RRR: lid event for lid %i\n", lid_is_closed);
369                   if (!e_randr2_cfg->ignore_acpi_events)
370                     e_randr2_screen_refresh_queue(EINA_TRUE);
371                   if (!lid_is_closed) e_powersave_defer_cancel();
372                   break;
373 
374                 default:
375                   break;
376                }
377              /* actually raise the event */
378              ecore_event_add(E_EVENT_ACPI, acpi_event,
379                              _e_acpi_cb_event_free, NULL);
380           }
381 done_event:
382         str = p + 1;
383         p = strchr(str, '\n');
384      }
385    if (str[0] == 0)
386      {
387         eina_strbuf_free(acpibuf);
388         acpibuf = NULL;
389      }
390    else
391      {
392         Eina_Strbuf *newbuf;
393 
394         newbuf = eina_strbuf_new();
395         eina_strbuf_append(newbuf, str);
396         eina_strbuf_free(acpibuf);
397         acpibuf = newbuf;
398      }
399    return ECORE_CALLBACK_PASS_ON;
400 }
401 
402 static void
_e_acpi_cb_event_free(void * data EINA_UNUSED,void * event)403 _e_acpi_cb_event_free(void *data EINA_UNUSED, void *event)
404 {
405    E_Event_Acpi *ev;
406 
407    if (!(ev = event)) return;
408    if (ev->device) eina_stringshare_del(ev->device);
409    if (ev->bus_id) eina_stringshare_del(ev->bus_id);
410    E_FREE(ev);
411 }
412 
413 static int
_e_acpi_lid_status_get(const char * device,const char * bus)414 _e_acpi_lid_status_get(const char *device, const char *bus)
415 {
416    FILE *f;
417    int i = 0;
418    char buff[PATH_MAX], *ret;
419 
420    /* the acpi driver code in the kernel has a nice acpi function to return
421     * the lid status easily, but that function is not exposed for user_space
422     * so we need to check the proc fs to get the actual status */
423 
424    /* make sure we have a device and bus */
425    if ((!device) || (!bus)) return E_ACPI_LID_UNKNOWN;
426 
427    /* open the state file from /proc */
428    snprintf(buff, sizeof(buff), "/proc/acpi/%s/%s/state", device, bus);
429    if (!(f = fopen(buff, "r")))
430      {
431         /* hack around ppurka's Thinkpad (G460 + Linux) that reports lid
432          * state as "/proc/acpi/button/lid/LID0/state" but where the lid
433          * event says "button/lid LID close".
434          *
435          * so let's take the base device name "LID" and add a numeric like
436          * 0, 1, 2, 3 so we have LID0, LID1, LID2 etc. - try up to LID9
437          * and then give up.
438          */
439         for (i = 0; i < 10; i++)
440           {
441              snprintf(buff, sizeof(buff), "/proc/acpi/%s/%s%i/state",
442                       device, bus, i);
443              if ((f = fopen(buff, "r"))) break;
444              f = NULL;
445           }
446         if (!f) return E_ACPI_LID_UNKNOWN;
447      }
448 
449    /* read the line from state file */
450    ret = fgets(buff, sizeof(buff), f);
451    fclose(f);
452    if (!ret)
453      return E_ACPI_LID_UNKNOWN;
454 
455    /* parse out state file */
456    i = 0;
457    while (buff[i] != ':')
458      i++;
459    while (!isalnum(buff[i]))
460      i++;
461    ret = &(buff[i]);
462    while (isalnum(buff[i]))
463      i++;
464    buff[i] = 0;
465 
466    /* compare value from state file and return something sane */
467    if (!strcmp(ret, "open")) return E_ACPI_LID_OPEN;
468    else if (!strcmp(ret, "closed"))
469      return E_ACPI_LID_CLOSED;
470    else return E_ACPI_LID_UNKNOWN;
471 }
472 
473 static Eina_Bool
_e_acpi_cb_event(void * data EINA_UNUSED,int type EINA_UNUSED,void * event)474 _e_acpi_cb_event(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
475 {
476    E_Event_Acpi *ev;
477 
478    ev = event;
479    if (_e_acpi_events_frozen > 0) return ECORE_CALLBACK_PASS_ON;
480    e_bindings_acpi_event_handle(E_BINDING_CONTEXT_NONE, NULL, ev);
481    return ECORE_CALLBACK_PASS_ON;
482 }
483 
484