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