1 /* tpb - program to use the IBM ThinkPad(tm) special keys
2  * Copyright (C) 2002-2005 Markus Braun <markus.braun@krawel.de>
3  *
4  * This file is part of tpb.
5  *
6  * tpb is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * tpb is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with tpb; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 
21 /* includes {{{ */
22 #include <ctype.h>
23 #include <fcntl.h>
24 #include <sys/ioctl.h>
25 #include <sys/wait.h>
26 #include <locale.h>
27 #include <signal.h>
28 #include <sys/soundcard.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <unistd.h>
35 #include "config.h"
36 
37 #ifdef __FreeBSD__
38 #include <sys/file.h>
39 #include <sys/ioctl.h>
40 #include <sys/types.h>
41 #include <sys/sysctl.h>
42 #ifdef __i386__
43 #include <machine/apm_bios.h>
44 #endif
45 #endif
46 
47 #if ENABLE_NLS
48 #include <libintl.h>
49 #endif /* ENABLE_NLS */
50 
51 #ifdef HAVE_LIBXOSD
52 #include <xosd.h>
53 #endif /* HAVE_LIBXOSD */
54 
55 #ifdef HAVE_LIBX11
56 #include <X11/Xlib.h>
57 #endif /* HAVE_LIBX11 */
58 
59 #include "cfg.h"
60 #include "tpb.h"
61 /* }}} */
62 
63 /* RCS version string for later identification using rcs ident {{{ */
64 #ifndef lint
65 static volatile const char *RCSid = "@(#) $Id: tpb.c,v 1.31 2005/07/18 14:15:59 mbr Exp $";
66 #endif /* lint */
67 /* }}} */
68 
69 /* global variables  {{{ */
70 extern config cfg;
71 /* }}} */
72 
73 /* function prototypes {{{ */
74 #ifdef ENABLE_NLS
75 void init_i18n(void);
76 #endif /* ENABLE_NLS */
77 void daemonize(void);
78 #ifdef HAVE_LIBXOSD
79 xosd *init_xosd(void);
80 #endif /* HAVE_LIBXOSD */
81 #ifdef HAVE_LIBX11
82 Display *init_xgrabkey(void);
83 #endif /* HAVE_LIBX11 */
84 int get_nvram_state(t_thinkpad_state *thinkpad_state);
85 int get_apm_state(t_thinkpad_state *thinkpad_state);
86 #ifdef HAVE_LIBX11
87 int xgrabkey(t_thinkpad_state *thinkpad_state, Display *display);
88 #endif /* HAVE_LIBX11 */
89 int fork_app(char * cmd);
90 void set_nvram_volume_level(t_thinkpad_state *thinkpad_state);
91 unsigned int change_volume(int change);
92 int apmiser_running(void);
93 #ifdef HAVE_LIBX11
94 int *xerrorhandler(Display *display, XErrorEvent *event);
95 #endif /* HAVE_LIBX11 */
96 void sig_chld_handler(int signo);
97 /* }}} */
98 
main(int argc,char ** argv)99 int main(int argc, char **argv) /* {{{ */
100 {
101   t_thinkpad_state thinkpad_state, last_thinkpad_state;
102   unsigned int vol = 0;
103   unsigned int display_state = 1;
104   char *home;
105   char *cfg_file;
106   char *msg = "";
107   char callback_cmd[CALLBACK_CMD_LENGTH];
108   struct sigaction signal_action;
109 #ifdef HAVE_LIBXOSD
110   xosd *osd_ptr = NULL;
111 #endif /* HAVE_LIBXOSD */
112 #ifdef HAVE_LIBX11
113   Display *display = NULL;
114 #endif /* HAVE_LIBX11 */
115 
116   /* zero thinkpad_state */
117   memset(&thinkpad_state, 0, sizeof(thinkpad_state));
118 
119   /* register my own handler for SIG_CHLD */
120   signal_action.sa_handler = sig_chld_handler;
121   signal_action.sa_flags = SA_NOCLDSTOP;
122   sigemptyset(&signal_action.sa_mask);
123   if (sigaction(SIGCHLD, &signal_action, NULL)  < 0)
124   {
125     fprintf(stderr, _("Unable to register signal handler:"));
126     perror(NULL);
127     _exit(-1);
128   }
129 
130   /* initialize i18n */
131 #ifdef ENABLE_NLS
132   init_i18n();
133 #endif /* ENABLE_NLS */
134 
135   /* initialize config */
136   init_cfg(&cfg);
137 
138   /* read global config file */
139   parse_cfg_file(&cfg, GLOBAL_CONFIG_FILE);
140 
141   /* read user config file */
142   if((home = getenv("HOME")) != NULL) {
143     cfg_file = (char *)malloc(strlen(home) + strlen("/") + strlen(PRIVATE_CONFIG_FILE) + 1);
144     strcpy(cfg_file, home);
145     strcat(cfg_file, "/");
146     strcat(cfg_file, PRIVATE_CONFIG_FILE);
147     parse_cfg_file(&cfg, cfg_file);
148     free(cfg_file);
149   }
150 
151   /* parse options */
152   parse_option(&cfg, argc, argv);
153 
154   /* become a daemon if requested */
155   if(cfg.daemon == STATE_ON) {
156     daemonize();
157   }
158 
159   /* initialize osd */
160 #ifdef HAVE_LIBXOSD
161   osd_ptr = init_xosd();
162 #endif /* HAVE_LIBXOSD */
163 
164 #ifdef HAVE_LIBX11
165   /* initialize key grabbing */
166   if(cfg.xevents == STATE_ON) {
167     display = init_xgrabkey();
168   }
169 #endif /* HAVE_LIBX11 */
170 
171   /* to initialize struct */
172   memset(&last_thinkpad_state, 0x00, sizeof(t_thinkpad_state));
173   if(get_nvram_state(&thinkpad_state) != 0) {
174 #ifdef __FreeBSD__
175     fprintf(stderr, _("Neither acpi_ibm(4) nor nvram(4) driver loaded. Exiting..."));
176 #endif
177     _exit(1);
178   }
179   if(cfg.apm == STATE_ON) {
180     get_apm_state(&thinkpad_state);
181   }
182 #ifdef HAVE_LIBX11
183   if(cfg.xevents == STATE_ON) {
184     xgrabkey(&thinkpad_state, display);
185   }
186 #endif /* HAVE_LIBX11 */
187 
188 
189   /* initialize volume */
190   if(cfg.mixer == STATE_ON) {
191     set_nvram_volume_level(&thinkpad_state);
192     vol = change_volume(0);
193   }
194   else {
195     vol = thinkpad_state.volume_level * 100 / 14;
196   }
197 
198   while(0 == 0) {
199     /* sleep for polltime */
200     usleep(cfg.polltime);
201 
202     /* save last state and get new one */
203     memcpy(&last_thinkpad_state, &thinkpad_state, sizeof(t_thinkpad_state));
204 
205     if(get_nvram_state(&thinkpad_state) != 0) {
206       _exit(1);
207     }
208     if(cfg.apm == STATE_ON) {
209       get_apm_state(&thinkpad_state);
210     }
211 #ifdef HAVE_LIBX11
212     if(cfg.xevents == STATE_ON) {
213       xgrabkey(&thinkpad_state, display);
214     }
215 #endif /* HAVE_LIBX11 */
216 
217     /* check if anything changed */
218     if(memcmp(&last_thinkpad_state, &thinkpad_state, sizeof(t_thinkpad_state)) == 0) {
219       continue;
220     }
221 
222     /* determine the state of the Thinkpad button  {{{ */
223     if(thinkpad_state.thinkpad_toggle != last_thinkpad_state.thinkpad_toggle &&
224        thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) {
225       if(cfg.verbose == STATE_ON) {
226 	puts(_("ThinkPad button pressed"));
227       }
228       if(cfg.tpb_cmd != NULL) {
229 	if(fork_app(cfg.tpb_cmd) != 0) {
230 	  _exit(0);
231 	}
232       }
233       if(cfg.callback != NULL) {
234 	snprintf(callback_cmd, sizeof(callback_cmd), "%s thinkpad pressed", cfg.callback);
235 	if(fork_app(callback_cmd) != 0) {
236 	  _exit(0);
237 	}
238       }
239     } /* }}} */
240 
241     /* determine the state of zoom  {{{ */
242     if(thinkpad_state.zoom_toggle != last_thinkpad_state.zoom_toggle) {
243       if(cfg.verbose == STATE_ON) {
244 	printf(_("Zoom is %s\n"), thinkpad_state.zoom_toggle == 1 ? _("on") : _("off"));
245       }
246       if(cfg.callback != NULL) {
247 	snprintf(callback_cmd, sizeof(callback_cmd), "%s zoom %s", cfg.callback, thinkpad_state.zoom_toggle == 1 ? "on" : "off");
248 	if(fork_app(callback_cmd) != 0) {
249 	  _exit(0);
250 	}
251       }
252 #ifdef HAVE_LIBXOSD
253       if(osd_ptr != NULL &&
254 	 ((cfg.osd == STATE_OFF && cfg.osdzoom == STATE_ON) || (cfg.osd == STATE_ON && cfg.osdzoom != STATE_OFF))) {
255 	xosd_display(osd_ptr, 0, XOSD_string, thinkpad_state.zoom_toggle == 1 ? _("Zoom on") : _("Zoom off"));
256 	xosd_display(osd_ptr, 1, XOSD_string, "");
257       }
258 #endif /* HAVE_LIBXOSD */
259     } /* }}} */
260 
261     /* determine the state of the home button  {{{ */
262     if(thinkpad_state.home_toggle != last_thinkpad_state.home_toggle &&
263        thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) {
264       if(cfg.verbose == STATE_ON) {
265 	puts(_("Home button pressed"));
266       }
267       if(cfg.home_cmd != NULL) {
268 	if(fork_app(cfg.home_cmd) != 0) {
269 	  _exit(0);
270 	}
271       }
272       if(cfg.callback != NULL) {
273 	snprintf(callback_cmd, sizeof(callback_cmd), "%s home pressed", cfg.callback);
274 	if(fork_app(callback_cmd) != 0) {
275 	  _exit(0);
276 	}
277       }
278     } /* }}} */
279 
280     /* determine the state of the search button  {{{ */
281     if(thinkpad_state.search_toggle != last_thinkpad_state.search_toggle &&
282        thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) {
283       if(cfg.verbose == STATE_ON) {
284 	puts(_("Search button pressed"));
285       }
286       if(cfg.search_cmd != NULL) {
287 	if(fork_app(cfg.search_cmd) != 0) {
288 	  _exit(0);
289 	}
290       }
291       if(cfg.callback != NULL) {
292 	snprintf(callback_cmd, sizeof(callback_cmd), "%s search pressed", cfg.callback);
293 	if(fork_app(callback_cmd) != 0) {
294 	  _exit(0);
295 	}
296       }
297     } /* }}} */
298 
299     /* determine the state of the mail button  {{{ */
300     if(thinkpad_state.mail_toggle != last_thinkpad_state.mail_toggle &&
301        thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) {
302       if(cfg.verbose == STATE_ON) {
303 	puts(_("Mail button pressed"));
304       }
305       if(cfg.mail_cmd != NULL) {
306 	if(fork_app(cfg.mail_cmd) != 0) {
307 	  _exit(0);
308 	}
309       }
310       if(cfg.callback != NULL) {
311 	snprintf(callback_cmd, sizeof(callback_cmd), "%s mail pressed", cfg.callback);
312 	if(fork_app(callback_cmd) != 0) {
313 	  _exit(0);
314 	}
315       }
316     } /* }}} */
317 
318     /* determine the state of the wireless button  {{{ */
319     if(thinkpad_state.wireless_toggle != last_thinkpad_state.wireless_toggle &&
320        thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) {
321       if(cfg.verbose == STATE_ON) {
322 	puts(_("Wireless button pressed"));
323       }
324       if(cfg.wireless_cmd != NULL) {
325 	if(fork_app(cfg.wireless_cmd) != 0) {
326 	  _exit(0);
327 	}
328       }
329       if(cfg.callback != NULL) {
330 	snprintf(callback_cmd, sizeof(callback_cmd), "%s wireless pressed", cfg.callback);
331 	if(fork_app(callback_cmd) != 0) {
332 	  _exit(0);
333 	}
334       }
335     } /* }}} */
336 
337     /* determine the state of the favorites button  {{{ */
338     if(thinkpad_state.favorites_toggle != last_thinkpad_state.favorites_toggle) {
339       if(cfg.verbose == STATE_ON) {
340 	puts(_("Favorites button pressed"));
341       }
342       if(cfg.favorites_cmd != NULL) {
343 	if(fork_app(cfg.favorites_cmd) != 0) {
344 	  _exit(0);
345 	}
346       }
347       if(cfg.callback != NULL) {
348 	snprintf(callback_cmd, sizeof(callback_cmd), "%s favorites pressed", cfg.callback);
349 	if(fork_app(callback_cmd) != 0) {
350 	  _exit(0);
351 	}
352       }
353     } /* }}} */
354 
355     /* determine the state of the reload button  {{{ */
356     if(thinkpad_state.reload_toggle != last_thinkpad_state.reload_toggle) {
357       if(cfg.verbose == STATE_ON) {
358 	puts(_("Reload button pressed"));
359       }
360       if(cfg.reload_cmd != NULL) {
361 	if(fork_app(cfg.reload_cmd) != 0) {
362 	  _exit(0);
363 	}
364       }
365       if(cfg.callback != NULL) {
366 	snprintf(callback_cmd, sizeof(callback_cmd), "%s reload pressed", cfg.callback);
367 	if(fork_app(callback_cmd) != 0) {
368 	  _exit(0);
369 	}
370       }
371     } /* }}} */
372 
373     /* determine the state of the abort button  {{{ */
374     if(thinkpad_state.abort_toggle != last_thinkpad_state.abort_toggle) {
375       if(cfg.verbose == STATE_ON) {
376 	puts(_("Abort button pressed"));
377       }
378       if(cfg.abort_cmd != NULL) {
379 	if(fork_app(cfg.abort_cmd) != 0) {
380 	  _exit(0);
381 	}
382       }
383       if(cfg.callback != NULL) {
384 	snprintf(callback_cmd, sizeof(callback_cmd), "%s abort pressed", cfg.callback);
385 	if(fork_app(callback_cmd) != 0) {
386 	  _exit(0);
387 	}
388       }
389     } /* }}} */
390 
391     /* determine the state of the backward button  {{{ */
392     if(thinkpad_state.backward_toggle != last_thinkpad_state.backward_toggle) {
393       if(cfg.verbose == STATE_ON) {
394 	puts(_("Backward button pressed"));
395       }
396       if(cfg.backward_cmd != NULL) {
397 	if(fork_app(cfg.backward_cmd) != 0) {
398 	  _exit(0);
399 	}
400       }
401       if(cfg.callback != NULL) {
402 	snprintf(callback_cmd, sizeof(callback_cmd), "%s backward pressed", cfg.callback);
403 	if(fork_app(callback_cmd) != 0) {
404 	  _exit(0);
405 	}
406       }
407     } /* }}} */
408 
409     /* determine the state of the forward button  {{{ */
410     if(thinkpad_state.forward_toggle != last_thinkpad_state.forward_toggle) {
411       if(cfg.verbose == STATE_ON) {
412 	puts(_("Forward button pressed"));
413       }
414       if(cfg.forward_cmd != NULL) {
415 	if(fork_app(cfg.forward_cmd) != 0) {
416 	  _exit(0);
417 	}
418       }
419       if(cfg.callback != NULL) {
420 	snprintf(callback_cmd, sizeof(callback_cmd), "%s forward pressed", cfg.callback);
421 	if(fork_app(callback_cmd) != 0) {
422 	  _exit(0);
423 	}
424       }
425     } /* }}} */
426 
427     /* determine the state of the fn button  {{{ */
428     if(thinkpad_state.fn_toggle != last_thinkpad_state.fn_toggle) {
429       if(cfg.verbose == STATE_ON) {
430 	puts(_("Fn button pressed"));
431       }
432       if(cfg.fn_cmd != NULL) {
433 	if(fork_app(cfg.fn_cmd) != 0) {
434 	  _exit(0);
435 	}
436       }
437       if(cfg.callback != NULL) {
438 	snprintf(callback_cmd, sizeof(callback_cmd), "%s fn pressed", cfg.callback);
439 	if(fork_app(callback_cmd) != 0) {
440 	  _exit(0);
441 	}
442       }
443     } /* }}} */
444 
445     /* determine the state of ThinkLight {{{ */
446     if(thinkpad_state.thinklight_toggle != last_thinkpad_state.thinklight_toggle) {
447       if(cfg.verbose == STATE_ON) {
448 	printf(_("ThinkLight is %s\n"), thinkpad_state.thinklight_toggle == 1 ? _("on") : _("off"));
449       }
450       if(cfg.callback != NULL) {
451 	snprintf(callback_cmd, sizeof(callback_cmd), "%s thinklight %s", cfg.callback, thinkpad_state.thinklight_toggle == 1 ? "on" : "off");
452 	if(fork_app(callback_cmd) != 0) {
453 	  _exit(0);
454 	}
455       }
456 #ifdef HAVE_LIBXOSD
457       if(osd_ptr != NULL &&
458 	 ((cfg.osd == STATE_OFF && cfg.osdthinklight == STATE_ON) || (cfg.osd == STATE_ON && cfg.osdthinklight != STATE_OFF))) {
459 	xosd_display(osd_ptr, 0, XOSD_string, thinkpad_state.thinklight_toggle == 1 ? _("ThinkLight on") : _("ThinkLight off"));
460 	xosd_display(osd_ptr, 1, XOSD_string, "");
461       }
462 #endif /* HAVE_LIBXOSD */
463     } /* }}} */
464 
465     /* determine the state of Bluetooth {{{ */
466     if(thinkpad_state.bluetooth_toggle != last_thinkpad_state.bluetooth_toggle) {
467       if(cfg.verbose == STATE_ON) {
468         printf(_("Bluetooth is %s\n"), thinkpad_state.bluetooth_toggle == 1 ? _("on") : _("off"));
469       }
470       if(cfg.callback != NULL) {
471         snprintf(callback_cmd, sizeof(callback_cmd), "%s bluetooth %s", cfg.callback, thinkpad_state.bluetooth_toggle == 1 ? "on" : "off");
472         if(fork_app(callback_cmd) != 0) {
473           _exit(0);
474         }
475       }
476 #ifdef HAVE_LIBXOSD
477       if(osd_ptr != NULL) {
478         xosd_display(osd_ptr, 0, XOSD_string, thinkpad_state.bluetooth_toggle == 1 ? _("Bluetooth enabled") : _("Bluetooth disabled"));
479         xosd_display(osd_ptr, 1, XOSD_string, "");
480       }
481 #endif /* HAVE_LIBXOSD */
482     } /* }}} */
483 
484     /* determine the state of display  {{{ */
485     if((thinkpad_state.display_toggle != last_thinkpad_state.display_toggle ||
486 	thinkpad_state.display_state != last_thinkpad_state.display_state) &&
487        thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) {
488 
489       /* Some thinkpads have no hardware support to switch lcd/crt. They also
490        * don't reflect the current state in thinkpad_state.display_state. So, if
491        * thinkpad_state.display_toggle changes, but thinkpad_state.display_state does
492        * not change, simulate the display state.
493        */
494       if(thinkpad_state.display_toggle != last_thinkpad_state.display_toggle &&
495 	 thinkpad_state.display_state == last_thinkpad_state.display_state) {
496 	display_state = display_state % 3 + 1;
497       }
498       else {
499 	display_state = thinkpad_state.display_state;
500       }
501 
502       switch (display_state & 0x03) {
503 	case 0x1:
504 	  msg = _("LCD on, CRT off");
505 	  snprintf(callback_cmd, sizeof(callback_cmd), "%s display lcd", cfg.callback);
506 	  break;
507 
508 	case 0x2:
509 	  msg = _("LCD off, CRT on");
510 	  snprintf(callback_cmd, sizeof(callback_cmd), "%s display crt", cfg.callback);
511 	  break;
512 
513 	case 0x3:
514 	  msg = _("LCD on, CRT on");
515 	  snprintf(callback_cmd, sizeof(callback_cmd), "%s display both", cfg.callback);
516 	  break;
517       }
518       if(cfg.verbose == STATE_ON) {
519 	printf(_("Display changed: %s\n"), msg);
520       }
521       if(cfg.callback != NULL) {
522 	if(fork_app(callback_cmd) != 0) {
523 	  _exit(0);
524 	}
525       }
526 #ifdef HAVE_LIBXOSD
527       if(osd_ptr != NULL &&
528 	 ((cfg.osd == STATE_OFF && cfg.osddisplay == STATE_ON) || (cfg.osd == STATE_ON && cfg.osddisplay != STATE_OFF))) {
529 	xosd_display(osd_ptr, 0, XOSD_string, _("Display"));
530 	xosd_display(osd_ptr, 1, XOSD_string, msg);
531       }
532 #endif /* HAVE_LIBXOSD */
533     } /* }}} */
534 
535     /* determine the state of hv expansion  {{{ */
536     if(thinkpad_state.expand_toggle != last_thinkpad_state.expand_toggle) {
537       if(cfg.verbose == STATE_ON) {
538 	printf(_("HV Expansion is %s\n"), (thinkpad_state.expand_toggle & 0x01) == 1 ? _("on") : _("off"));
539       }
540       if(cfg.callback != NULL) {
541 	snprintf(callback_cmd, sizeof(callback_cmd), "%s expand %s", cfg.callback, thinkpad_state.expand_toggle == 1 ? "on" : "off");
542 	if(fork_app(callback_cmd) != 0) {
543 	  _exit(0);
544 	}
545       }
546 #ifdef HAVE_LIBXOSD
547       if(osd_ptr != NULL &&
548 	 ((cfg.osd == STATE_OFF && cfg.osdhvexpansion == STATE_ON) || (cfg.osd == STATE_ON && cfg.osdhvexpansion != STATE_OFF))) {
549 	xosd_display(osd_ptr, 0, XOSD_string, thinkpad_state.expand_toggle == 1 ? _("HV Expansion on") : _("HV Expansion off"));
550 	xosd_display(osd_ptr, 1, XOSD_string, "");
551       }
552 #endif /* HAVE_LIBXOSD */
553     } /* }}} */
554 
555     /* determine the state of the brightness buttons {{{ */
556     if(thinkpad_state.brightness_level != last_thinkpad_state.brightness_level) {
557       if(cfg.verbose == STATE_ON) {
558 	printf(_("Brightness changed: Level %d\n"), thinkpad_state.brightness_level * 100 / 7);
559       }
560       if(cfg.callback != NULL) {
561 	snprintf(callback_cmd, sizeof(callback_cmd), "%s brightness %d", cfg.callback, thinkpad_state.brightness_level * 100 / 7);
562 	if(fork_app(callback_cmd) != 0) {
563 	  _exit(0);
564 	}
565       }
566     }
567 #ifdef HAVE_LIBXOSD
568     if(thinkpad_state.brightness_toggle != last_thinkpad_state.brightness_toggle) {
569       if(osd_ptr != NULL &&
570 	 ((cfg.osd == STATE_OFF && cfg.osdbrightness == STATE_ON) || (cfg.osd == STATE_ON && cfg.osdbrightness != STATE_OFF))) {
571 	xosd_display(osd_ptr, 0, XOSD_string, _("Brightness"));
572 	xosd_display(osd_ptr, 1, XOSD_percentage, thinkpad_state.brightness_level * 100 / 7);
573       }
574     }
575 #endif /* HAVE_LIBXOSD */ /* }}} */
576 
577     /* determine the state of the volume buttons {{{ */
578     if(thinkpad_state.volume_level != last_thinkpad_state.volume_level) {
579       if(cfg.mixer == STATE_ON) {
580 	/* if we use the DEFAULT_MIXERSTEPS, we don't need to modify the nvram */
581 	if(cfg.mixersteps == DEFAULT_MIXERSTEPS) {
582 	  vol = change_volume((1. * MAX_VOLUME / cfg.mixersteps * thinkpad_state.volume_level) - vol);
583 	}
584 	else
585 	{
586 	  vol = change_volume(1. * MAX_VOLUME / cfg.mixersteps * ((int)thinkpad_state.volume_level - (int)last_thinkpad_state.volume_level)); /* thinkpad_state.volume_level-last_thinkpad_state.volume_level gives the direction */
587 	  set_nvram_volume_level(&thinkpad_state);
588 	}
589       }
590       else {
591 	vol = thinkpad_state.volume_level * 100 / 14;
592       }
593       if(cfg.verbose == STATE_ON) {
594 	printf(_("Volume changed: Level %d\n"), vol);
595       }
596       if(cfg.callback != NULL) {
597 	snprintf(callback_cmd, sizeof(callback_cmd), "%s volume %d", cfg.callback, vol);
598 	if(fork_app(callback_cmd) != 0) {
599 	  _exit(0);
600 	}
601       }
602     }
603 #ifdef HAVE_LIBXOSD
604     /* show volume every time a volume button is pressed and not mute */
605     if(thinkpad_state.volume_toggle != last_thinkpad_state.volume_toggle &&
606        thinkpad_state.mute_toggle == 0) {
607       if(osd_ptr != NULL &&
608 	 ((cfg.osd == STATE_OFF && cfg.osdvolume == STATE_ON) || (cfg.osd == STATE_ON && cfg.osdvolume != STATE_OFF))) {
609 	xosd_display(osd_ptr, 0, XOSD_string, _("Volume"));
610 	xosd_display(osd_ptr, 1, XOSD_percentage, vol);
611       }
612     }
613 #endif /* HAVE_LIBXOSD */ /* }}} */
614 
615     /* determine the state of the mute button {{{ */
616     if(thinkpad_state.mute_toggle != last_thinkpad_state.mute_toggle) {
617       if(cfg.verbose == STATE_ON) {
618 	printf("%s\n", thinkpad_state.mute_toggle == 1 ? _("Mute on") : _("Mute off"));
619       }
620       if(cfg.callback != NULL) {
621 	snprintf(callback_cmd, sizeof(callback_cmd), "%s mute %s", cfg.callback, thinkpad_state.mute_toggle == 1 ? "on" : "off");
622 	if(fork_app(callback_cmd) != 0) {
623 	  _exit(0);
624 	}
625       }
626     }
627 #ifdef HAVE_LIBXOSD
628     if(thinkpad_state.mute_toggle != last_thinkpad_state.mute_toggle ||
629        (thinkpad_state.volume_toggle != last_thinkpad_state.volume_toggle && last_thinkpad_state.mute_toggle == 1)) {
630       if(cfg.mixer == STATE_ON) {
631 	if(thinkpad_state.mute_toggle == 1) {
632 	  change_volume(-vol); /* mute */
633 	}
634 	else {
635 	  change_volume(vol); /* unmute */
636 	}
637       }
638 
639       if(osd_ptr != NULL &&
640 	 ((cfg.osd == STATE_OFF && cfg.osdvolume == STATE_ON) || (cfg.osd == STATE_ON && cfg.osdvolume != STATE_OFF))) {
641 	if(thinkpad_state.mute_toggle == 1) {
642 	  xosd_display(osd_ptr, 0, XOSD_string, _("Mute on"));
643 	  xosd_display(osd_ptr, 1, XOSD_percentage, 0);
644 	}
645 	else {
646 	  xosd_display(osd_ptr, 0, XOSD_string, _("Mute off"));
647 	  xosd_display(osd_ptr, 1, XOSD_percentage, vol);
648 	}
649       }
650     }
651 #endif /* HAVE_LIBXOSD */ /* }}} */
652 
653     /* determine the state of power {{{ */
654     if(thinkpad_state.ac_state != last_thinkpad_state.ac_state) {
655       if(cfg.verbose == STATE_ON) {
656 	printf(_("Power line changed: %s\n"), thinkpad_state.ac_state == 1 ? _("AC connected") : _("AC disconnected"));
657       }
658       if(cfg.callback != NULL) {
659 	snprintf(callback_cmd, sizeof(callback_cmd), "%s ac_power %s", cfg.callback, thinkpad_state.ac_state == 1 ? "connected" : "disconnected");
660 	if(fork_app(callback_cmd) != 0) {
661 	  _exit(0);
662 	}
663       }
664 #ifdef HAVE_LIBXOSD
665       if(osd_ptr != NULL &&
666 	 ((cfg.osd == STATE_OFF && cfg.osdpowermgt == STATE_ON) || (cfg.osd == STATE_ON && cfg.osdpowermgt != STATE_OFF))) {
667 	xosd_display(osd_ptr, 0, XOSD_string, thinkpad_state.ac_state == 1 ? _("AC connected") : _("AC disconnected"));
668 	xosd_display(osd_ptr, 1, XOSD_string, "");
669       }
670 #endif /* HAVE_LIBXOSD */
671     } /* }}} */
672 
673     /* determine power management mode AC {{{ */
674     if(thinkpad_state.powermgt_ac != last_thinkpad_state.powermgt_ac) {
675       switch(thinkpad_state.powermgt_ac) {
676 	case 0x4:
677 	  msg = _("PM AC high");
678 	  snprintf(callback_cmd, sizeof(callback_cmd), "%s powermgt_ac high", cfg.callback);
679 	  break;
680 
681 	case 0x2:
682 	  msg = _("PM AC auto");
683 	  snprintf(callback_cmd, sizeof(callback_cmd), "%s powermgt_ac auto", cfg.callback);
684 	  break;
685 
686 	case 0x1:
687 	  msg = _("PM AC manual");
688 	  snprintf(callback_cmd, sizeof(callback_cmd), "%s powermgt_ac manual", cfg.callback);
689 	  break;
690 
691 	default:
692 	  msg = _("PM AC unknown");
693 	  break;
694       }
695       if(cfg.verbose == STATE_ON) {
696 	printf(_("Power management mode AC changed: %s\n"), msg);
697       }
698       if(cfg.callback != NULL) {
699 	if(fork_app(callback_cmd) != 0) {
700 	  _exit(0);
701 	}
702       }
703 #ifdef HAVE_LIBXOSD
704       if(osd_ptr != NULL &&
705 	 ((cfg.osd == STATE_OFF && cfg.osdpowermgt == STATE_ON) || (cfg.osd == STATE_ON && cfg.osdpowermgt != STATE_OFF))) {
706 	if (cfg.powermgt == STATE_ON ||  (cfg.powermgt == STATE_AUTO && apmiser_running() == 0)) {
707 	  xosd_display(osd_ptr, 0, XOSD_string, msg);
708 	  xosd_display(osd_ptr, 1, XOSD_string, "");
709 	}
710       }
711 #endif /* HAVE_LIBXOSD */
712     } /* }}} */
713 
714     /* determine power management mode battery {{{ */
715     if(thinkpad_state.powermgt_battery != last_thinkpad_state.powermgt_battery) {
716       switch(thinkpad_state.powermgt_battery) {
717 	case 0x4:
718 	  msg = _("PM battery high");
719 	  snprintf(callback_cmd, sizeof(callback_cmd), "%s powermgt_battery high", cfg.callback);
720 	  break;
721 
722 	case 0x2:
723 	  msg = _("PM battery auto");
724 	  snprintf(callback_cmd, sizeof(callback_cmd), "%s powermgt_battery auto", cfg.callback);
725 	  break;
726 
727 	case 0x1:
728 	  msg = _("PM battery manual");
729 	  snprintf(callback_cmd, sizeof(callback_cmd), "%s powermgt_battery manual", cfg.callback);
730 	  break;
731 
732 	default:
733 	  msg = _("PM battery unknown");
734 	  break;
735       }
736       if(cfg.verbose == STATE_ON) {
737 	printf(_("Power management mode battery changed: %s\n"), msg);
738       }
739       if(cfg.callback != NULL) {
740 	if(fork_app(callback_cmd) != 0) {
741 	  _exit(0);
742 	}
743       }
744 #ifdef HAVE_LIBXOSD
745       if(osd_ptr != NULL &&
746 	 ((cfg.osd == STATE_OFF && cfg.osdpowermgt == STATE_ON) || (cfg.osd == STATE_ON && cfg.osdpowermgt != STATE_OFF))) {
747 	if (cfg.powermgt == STATE_ON ||  (cfg.powermgt == STATE_AUTO && apmiser_running() == 0)) {
748 	  xosd_display(osd_ptr, 0, XOSD_string, msg);
749 	  xosd_display(osd_ptr, 1, XOSD_string, "");
750 	}
751       }
752 #endif /* HAVE_LIBXOSD */
753     } /* }}} */
754   }
755 
756   _exit(0); /* never reached */
757 } /* }}} */
758 
759 #ifdef ENABLE_NLS
760 /* initialize the i18n system */
init_i18n(void)761 void init_i18n(void) /* {{{ */
762 {
763   setlocale (LC_ALL, "");
764   bindtextdomain (PACKAGE, LOCALEDIR);
765   textdomain (PACKAGE);
766 
767   return;
768 } /* }}} */
769 #endif /* ENABLE_NLS */
770 
771 /* when called, the application becomes a daemon */
daemonize(void)772 void daemonize(void) /* {{{ */
773 {
774   int fdsc, fdsc_max;
775 
776   /* code inspired by comp.unix.programmer FAQ */
777   switch (fork())
778   {
779     case 0:
780       break;
781     case -1:
782       fprintf(stderr, _("Unable to fork daemon:"));
783       perror(NULL);
784       _exit(-1);
785     default:
786       _exit(0);          /* exit the original process */
787   }
788 
789   /* create a new session */
790   if (setsid() < 0) {
791     _exit(-1);
792   }
793 
794   switch (fork())
795   {
796     case 0:
797       break;
798     case -1:
799       fprintf(stderr, _("Unable to fork daemon:"));
800       perror(NULL);
801       _exit(-1);
802     default:
803       _exit(0);
804   }
805 
806   if(cfg.verbose == STATE_ON) {
807     puts(_("Daemon started."));
808   }
809 
810   /* change to root directory */
811   chdir("/");
812 
813   /* close all file descriptors */
814   for (fdsc = 0, fdsc_max = sysconf(_SC_OPEN_MAX); fdsc < fdsc_max; fdsc++) {
815     close(fdsc);
816   }
817 
818   /* open /dev/null as file descriptor 0 */
819   open("/dev/null",O_RDWR);
820 
821   /* duplicate file descriptor 0 to file descriptor 1 (next free) */
822   dup(0);
823 
824   /* duplicate file descriptor 0 to file descriptor 2 (next free) */
825   dup(0);
826 
827   return;
828 } /* }}} */
829 
830 #ifdef HAVE_LIBXOSD
831 /* initialize the on-screen-display */
init_xosd(void)832 xosd *init_xosd(void) /* {{{ */
833 {
834   xosd *osd_ptr = NULL;
835 
836 #if HAVE_LIBXOSD_VERSION >= 20000
837   osd_ptr = xosd_create(DEFAULT_OSDLINES);
838 #elif HAVE_LIBXOSD_VERSION >= 10000
839   osd_ptr = xosd_init(DEFAULT_OSDFONT, DEFAULT_OSDCOLOR, DEFAULT_OSDTIMEOUT,
840 		      DEFAULT_OSDPOS, DEFAULT_OSDVERTICAL, DEFAULT_OSDSHADOW, DEFAULT_OSDLINES);
841 #elif HAVE_LIBXOSD_VERSION >= 700
842   osd_ptr = xosd_init(DEFAULT_OSDFONT, DEFAULT_OSDCOLOR, DEFAULT_OSDTIMEOUT,
843 		      DEFAULT_OSDPOS, DEFAULT_OSDVERTICAL, DEFAULT_OSDSHADOW);
844 #endif /* HAVE_LIBXOSD_VERSION */
845 
846   if(osd_ptr == NULL) {
847     fprintf(stderr, _("Unable to initialize xosd. Running without onsceen display.\n"));
848   }
849   else {
850     /* initialize font */
851     if(xosd_set_font(osd_ptr, cfg.osdfont) != 0) {
852       if(xosd_set_font(osd_ptr, DEFAULT_OSDFONT) != 0) {
853 	/* even the default font is not available */
854 	fprintf(stderr, _("Invalid xosd font \"%s\". Even the default font \"%s\" "),
855 		cfg.osdfont, DEFAULT_OSDFONT);
856 	fprintf(stderr, _("is not available. Please add an existing font to your %s file.\n"),
857 		GLOBAL_CONFIG_FILE);
858 	fprintf(stderr, _("Running without onsceen display.\n"));
859 
860 #if HAVE_LIBXOSD_VERSION >= 20000
861 	/* Disabled because of a bug in xosd lib
862 	 * xosd_destroy(osd_ptr);
863 	 */
864 #else /* HAVE_LIBXOSD_VERSION */
865 	/* Disabled because of a bug in xosd lib
866 	 * xosd_uninit(osd_ptr);
867 	 */
868 #endif /* HAVE_LIBXOSD_VERSION */
869 
870 	return NULL;
871       }
872       else {
873 	/* we run with the default font */
874 	fprintf(stderr, _("Invalid xosd font \"%s\". Running with the default \"%s\".\n"),
875 		cfg.osdfont, DEFAULT_OSDFONT);
876       }
877     }
878 
879     /* initialize color */
880     if(xosd_set_colour(osd_ptr, cfg.osdcolor) != 0) {
881       fprintf(stderr, _("Invalid xosd color \"%s\". Running with the default \"%s\".\n"),
882 	      cfg.osdcolor, DEFAULT_OSDCOLOR);
883       xosd_set_colour(osd_ptr, DEFAULT_OSDCOLOR);
884     }
885 
886     /* initialize timeout */
887     if(xosd_set_timeout(osd_ptr, cfg.osdtimeout) != 0) {
888       fprintf(stderr, _("Invalid xosd timeout \"%d\". Running with the default \"%d\".\n"),
889 	      cfg.osdtimeout, DEFAULT_OSDTIMEOUT);
890       xosd_set_timeout(osd_ptr, DEFAULT_OSDTIMEOUT);
891     }
892 
893     /* initialize position */
894     if(xosd_set_pos(osd_ptr, cfg.osdpos) != 0) {
895       fprintf(stderr, _("Invalid xosd position \"%d\". Running with the default \"%d\".\n"),
896 	      cfg.osdpos, DEFAULT_OSDPOS);
897       xosd_set_pos(osd_ptr, DEFAULT_OSDPOS);
898     }
899 
900 #if HAVE_LIBXOSD_VERSION >= 20000
901     /* initialize horizontal offset */
902     if(xosd_set_horizontal_offset(osd_ptr, cfg.osdhorizontal) != 0) {
903       fprintf(stderr, _("Invalid horizontal xosd offset \"%d\". Running with the default \"%d\".\n"),
904 	      cfg.osdhorizontal, DEFAULT_OSDHORIZONTAL);
905       xosd_set_horizontal_offset(osd_ptr, DEFAULT_OSDHORIZONTAL);
906     }
907 
908     /* initialize vertical offset */
909     if(xosd_set_vertical_offset(osd_ptr, cfg.osdvertical) != 0) {
910       fprintf(stderr, _("Invalid vertical xosd offset \"%d\". Running with the default \"%d\".\n"),
911 	      cfg.osdvertical, DEFAULT_OSDVERTICAL);
912       xosd_set_vertical_offset(osd_ptr, DEFAULT_OSDVERTICAL);
913     }
914 #else /* HAVE_LIBXOSD_VERSION */
915     /* initialize vertical offset */
916     if(xosd_set_offset(osd_ptr, cfg.osdvertical) != 0) {
917       fprintf(stderr, _("Invalid vertical xosd offset \"%d\". Running with the default \"%d\".\n"),
918 	      cfg.osdvertical, DEFAULT_OSDVERTICAL);
919       xosd_set_offset(osd_ptr, DEFAULT_OSDVERTICAL);
920     }
921 #endif /* HAVE_LIBXOSD_VERSION */
922 
923     /* initialize shadow offset */
924     if(xosd_set_shadow_offset(osd_ptr, cfg.osdshadow) != 0) {
925       fprintf(stderr, _("Invalid shadow xosd offset \"%d\". Running with the default \"%d\".\n"),
926 	      cfg.osdshadow, DEFAULT_OSDSHADOW);
927       xosd_set_shadow_offset(osd_ptr, DEFAULT_OSDSHADOW);
928     }
929 
930 #if HAVE_LIBXOSD_VERSION >= 20200
931     /* initialize shadow color */
932     if(xosd_set_shadow_colour(osd_ptr, cfg.osdshadowcolor) != 0) {
933       fprintf(stderr, _("Invalid xosd shadow color \"%s\". Running with the default \"%s\".\n"),
934 	      cfg.osdshadowcolor, DEFAULT_OSDSHADOWCOLOR);
935       xosd_set_shadow_colour(osd_ptr, DEFAULT_OSDSHADOWCOLOR);
936     }
937 
938     /* initialize outline offset */
939     if(xosd_set_outline_offset(osd_ptr, cfg.osdoutline) != 0) {
940       fprintf(stderr, _("Invalid outline xosd width \"%d\". Running with the default \"%d\".\n"),
941 	      cfg.osdoutline, DEFAULT_OSDOUTLINE);
942       xosd_set_outline_offset(osd_ptr, DEFAULT_OSDOUTLINE);
943     }
944 
945     /* initialize outline color */
946     if(xosd_set_outline_colour(osd_ptr, cfg.osdoutlinecolor) != 0) {
947       fprintf(stderr, _("Invalid xosd outline color \"%s\". Running with the default \"%s\".\n"),
948 	      cfg.osdoutlinecolor, DEFAULT_OSDOUTLINECOLOR);
949       xosd_set_outline_colour(osd_ptr, DEFAULT_OSDOUTLINECOLOR);
950     }
951 #endif /* HAVE_LIBXOSD_VERSION */
952 
953 #if HAVE_LIBXOSD_VERSION >= 10000
954     /* initialize alignment */
955     if(xosd_set_align(osd_ptr, cfg.osdalign) != 0) {
956       fprintf(stderr, _("Invalid xosd alignment \"%d\". Running with default \"%d\".\n"),
957 	      cfg.osdalign, DEFAULT_OSDALIGN);
958       xosd_set_align(osd_ptr, DEFAULT_OSDALIGN);
959     }
960 #endif /* HAVE_LIBXOSD_VERSION */
961   }
962 
963   return osd_ptr;
964 } /* }}} */
965 #endif /* HAVE_LIBXOSD */
966 
967 #ifdef HAVE_LIBX11
968 /* initialize X for grabbing key events */
init_xgrabkey(void)969 Display *init_xgrabkey(void) /* {{{ */
970 {
971   char *display_name = NULL;
972   Display *display;
973 
974   /* set up our own error handler */
975   XSetErrorHandler ((XErrorHandler) xerrorhandler);
976 
977   /* get the name of the current X display */
978   display_name = XDisplayName(NULL);
979   if(display_name == NULL) {
980     fprintf(stderr, _("Can't get display name, X may not be running!\n"));
981     _exit(1);
982   }
983 
984   /* open the X display */
985   display = XOpenDisplay(display_name);
986   if(!display) {
987     fprintf(stderr, _("Can't open display, X may not be running!\n"));
988     _exit(1);
989   }
990 
991   /* allow X events */
992   XAllowEvents(display, AsyncKeyboard, CurrentTime);
993 
994   /* select the input */
995   XSelectInput (display, DefaultRootWindow(display), KeyPressMask);
996 
997   /* set up the keys to grab */
998   if ((cfg.home_cmd      != NULL) || (cfg.callback != NULL)) XGrabKey(display, KEYCODE_HOME        , AnyModifier, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync);
999   if ((cfg.search_cmd    != NULL) || (cfg.callback != NULL)) XGrabKey(display, KEYCODE_SEARCH      , AnyModifier, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync);
1000   if ((cfg.mail_cmd      != NULL) || (cfg.callback != NULL)) XGrabKey(display, KEYCODE_MAIL        , AnyModifier, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync);
1001   if ((cfg.favorites_cmd != NULL) || (cfg.callback != NULL)) XGrabKey(display, KEYCODE_FAVORITES   , AnyModifier, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync);
1002   if ((cfg.reload_cmd    != NULL) || (cfg.callback != NULL)) XGrabKey(display, KEYCODE_RELOAD      , AnyModifier, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync);
1003   if ((cfg.abort_cmd     != NULL) || (cfg.callback != NULL)) XGrabKey(display, KEYCODE_ABORT       , AnyModifier, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync);
1004   if ((cfg.backward_cmd  != NULL) || (cfg.callback != NULL)) XGrabKey(display, KEYCODE_BACKWARD    , AnyModifier, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync);
1005   if ((cfg.forward_cmd   != NULL) || (cfg.callback != NULL)) XGrabKey(display, KEYCODE_FORWARD     , AnyModifier, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync);
1006   if ((cfg.fn_cmd        != NULL) || (cfg.callback != NULL)) XGrabKey(display, KEYCODE_FN_KEY      , AnyModifier, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync);
1007 
1008   return(display);
1009 } /* }}} */
1010 #endif /* HAVE_LIBX11 */
1011 
1012 /* get the current state from the nvram */
get_nvram_state(t_thinkpad_state * thinkpad_state)1013 int get_nvram_state(t_thinkpad_state *thinkpad_state) /* {{{ */
1014 {
1015   static int fdsc = -1; /* -1 -> file not opened */
1016 #ifdef __FreeBSD__
1017   u_int n = 0;
1018   size_t len = sizeof(n);
1019   if ( fdsc != -1 || sysctlbyname("dev.acpi_ibm.0.hotkey", &n, &len, NULL, 0) == -1 ) {
1020 #endif
1021   unsigned char buffer[114];
1022   struct {
1023     int pos;
1024     int len;
1025   } pos_len[] = {
1026     { 0x39, 0x01 },
1027     { 0x56, 0x04 },
1028     { 0x5e, 0x01 },
1029     { 0x60, 0x01 },
1030     { 0x00, 0x00 } /* end */
1031   };
1032   int pos_len_idx = 0;
1033 
1034   /* open nvram for reading */
1035   if(fdsc == -1) { /* if not already opened, open nvram */
1036     if((fdsc=open(cfg.nvram, O_RDONLY|O_NONBLOCK)) == -1) {
1037       fprintf(stderr, _("Unable to open device %s: "), cfg.nvram);
1038       perror(NULL);
1039       return -1;
1040     }
1041   }
1042 
1043   /* Read only the interesting bytes from nvram to reduce the CPU consupmtion of tpb */
1044   /* The kernel nvram driver reads byte-by-byte from nvram, so just reading interesting bytes reduces the amount of inb() calls */
1045   while (pos_len[pos_len_idx].pos != 0x0) {
1046     if(lseek(fdsc, pos_len[pos_len_idx].pos, SEEK_SET) != pos_len[pos_len_idx].pos ) {
1047       fprintf(stderr, _("Unable to seek in device %s: "), cfg.nvram);
1048       perror(NULL);
1049       return -1;
1050     }
1051     if(read(fdsc, buffer+pos_len[pos_len_idx].pos, pos_len[pos_len_idx].len) != pos_len[pos_len_idx].len) {
1052 
1053       fprintf(stderr, _("Unable to read from device %s: "), cfg.nvram);
1054       perror(NULL);
1055       return -1;
1056     }
1057     pos_len_idx++;
1058   }
1059 
1060   thinkpad_state->thinkpad_toggle   = (thinkpad_state->thinkpad_toggle   & ~0x01) | (( buffer[0x57] & 0x08) >> 3);
1061   thinkpad_state->zoom_toggle       = (thinkpad_state->zoom_toggle       & ~0x01) | ((~buffer[0x57] & 0x20) >> 5);
1062   thinkpad_state->display_toggle    = (thinkpad_state->display_toggle    & ~0x01) | (( buffer[0x57] & 0x40) >> 6);
1063   thinkpad_state->home_toggle       = (thinkpad_state->home_toggle       & ~0x01) | (( buffer[0x56] & 0x01)     );
1064   thinkpad_state->search_toggle     = (thinkpad_state->search_toggle     & ~0x01) | (( buffer[0x56] & 0x02) >> 1);
1065   thinkpad_state->mail_toggle       = (thinkpad_state->mail_toggle       & ~0x01) | (( buffer[0x56] & 0x04) >> 2);
1066   thinkpad_state->wireless_toggle   = (thinkpad_state->wireless_toggle   & ~0x01) | (( buffer[0x56] & 0x20) >> 5);
1067   thinkpad_state->thinklight_toggle = (thinkpad_state->thinklight_toggle & ~0x01) | (( buffer[0x58] & 0x10) >> 4);
1068   thinkpad_state->hibernate_toggle  = (thinkpad_state->hibernate_toggle  & ~0x01) | (( buffer[0x58] & 0x01)     );
1069   thinkpad_state->display_state     =                                               (( buffer[0x59] & 0x03)     );
1070   thinkpad_state->expand_toggle     = (thinkpad_state->expand_toggle     & ~0x01) | (( buffer[0x59] & 0x10) >> 4);
1071   thinkpad_state->brightness_level  =                                               (( buffer[0x5E] & 0x07)     );
1072   thinkpad_state->brightness_toggle = (thinkpad_state->brightness_toggle & ~0x01) | (( buffer[0x5E] & 0x20) >> 5);
1073   thinkpad_state->volume_level      =                                               (( buffer[0x60] & 0x0f)     );
1074   thinkpad_state->volume_toggle     = (thinkpad_state->volume_toggle     & ~0x01) | (( buffer[0x60] & 0x80) >> 7);
1075   thinkpad_state->mute_toggle       = (thinkpad_state->mute_toggle       & ~0x01) | (( buffer[0x60] & 0x40) >> 6);
1076   thinkpad_state->powermgt_ac       =                                               (( buffer[0x39] & 0x07)     );
1077   thinkpad_state->powermgt_battery  =                                               (( buffer[0x39] & 0x38) >> 3);
1078 
1079   return 0;
1080 #ifdef __FreeBSD__
1081   }
1082   thinkpad_state->thinkpad_toggle   = (thinkpad_state->thinkpad_toggle   & ~0x01) | (( n & (1<<3)) >> 3);
1083   thinkpad_state->zoom_toggle       = (thinkpad_state->zoom_toggle       & ~0x01) | (( n & (1<<4)) >> 4);
1084   thinkpad_state->display_toggle    = (thinkpad_state->display_toggle    & ~0x01) | (( n & (1<<6)) >> 6);
1085   thinkpad_state->home_toggle       = (thinkpad_state->home_toggle       & ~0x01) | (( n & (1<<0))     );
1086   thinkpad_state->search_toggle     = (thinkpad_state->search_toggle     & ~0x01) | (( n & (1<<1)) >> 1);
1087   thinkpad_state->mail_toggle       = (thinkpad_state->mail_toggle       & ~0x01) | (( n & (1<<2)) >> 2);
1088   thinkpad_state->hibernate_toggle  = (thinkpad_state->hibernate_toggle  & ~0x01) | (( n & (1<<7)) >> 7);
1089   thinkpad_state->expand_toggle     = (thinkpad_state->expand_toggle     & ~0x01) | (( n & (1<<9)) >> 9);
1090   thinkpad_state->brightness_toggle = (thinkpad_state->brightness_toggle & ~0x01) | (( n & (1<<10)) >> 10);
1091   thinkpad_state->volume_toggle     = (thinkpad_state->volume_toggle     & ~0x01) | (( n & (1<<11)) >> 11);
1092 
1093   /* Don't fail if the wlan, bluetooth or thinklight sysctls are not present. They are generated dynamically */
1094   if ( sysctlbyname("dev.acpi_ibm.0.wlan", &n, &len, NULL, 0) != -1 )
1095     thinkpad_state->wireless_toggle = n;
1096 
1097   if ( sysctlbyname("dev.acpi_ibm.0.bluetooth", &n, &len, NULL, 0) != -1 )
1098     thinkpad_state->bluetooth_toggle = n;
1099 
1100   if ( sysctlbyname("dev.acpi_ibm.0.thinklight", &n, &len, NULL, 0) != -1 )
1101     thinkpad_state->thinklight_toggle = n;
1102 
1103   if ( sysctlbyname("dev.acpi_ibm.0.lcd_brightness", &n, &len, NULL, 0) == -1 )
1104     return -1;
1105   thinkpad_state->brightness_level = n;
1106 
1107   if ( sysctlbyname("dev.acpi_ibm.0.volume", &n, &len, NULL, 0) == -1 )
1108     return -1;
1109   thinkpad_state->volume_level = n;
1110 
1111   if ( sysctlbyname("dev.acpi_ibm.0.mute", &n, &len, NULL, 0) == -1 )
1112     return -1;
1113   thinkpad_state->mute_toggle = n;
1114 
1115   return 0;
1116 #endif
1117 } /* }}} */
1118 
1119 /* get the current state from the apm subsystem */
get_apm_state(t_thinkpad_state * thinkpad_state)1120 int get_apm_state(t_thinkpad_state *thinkpad_state) /* {{{ */
1121 {
1122   static int fdsc = -1; /* -1 -> file not opened */
1123 #ifndef __FreeBSD__
1124   unsigned int i;
1125   char buffer[38];
1126   char *tokens[9];
1127 
1128   /* Read the state of the ac line from proc filesystem.
1129    * Documentation of /proc/apm from linux kernel (/usr/src/linux/arch/i386/kernel/apm.c)
1130    *
1131    * 0) Linux driver version (this will change if format changes)
1132    * 1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
1133    * 2) APM flags from APM Installation Check (0x00):
1134    *    bit 0: APM_16_BIT_SUPPORT
1135    *    bit 1: APM_32_BIT_SUPPORT
1136    *    bit 2: APM_IDLE_SLOWS_CLOCK
1137    *    bit 3: APM_BIOS_DISABLED
1138    *    bit 4: APM_BIOS_DISENGAGED
1139    * 3) AC line status
1140    *    0x00: Off-line
1141    *    0x01: On-line
1142    *    0x02: On backup power (BIOS >= 1.1 only)
1143    *    0xff: Unknown
1144    * 4) Battery status
1145    *    0x00: High
1146    *    0x01: Low
1147    *    0x02: Critical
1148    *    0x03: Charging
1149    *    0x04: Selected battery not present (BIOS >= 1.2 only)
1150    *    0xff: Unknown
1151    * 5) Battery flag
1152    *    bit 0: High
1153    *    bit 1: Low
1154    *    bit 2: Critical
1155    *    bit 3: Charging
1156    *    bit 7: No system battery
1157    *    0xff: Unknown
1158    * 6) Remaining battery life (percentage of charge):
1159    *    0-100: valid
1160    *    -1: Unknown
1161    * 7) Remaining battery life (time units):
1162    *    Number of remaining minutes or seconds
1163    *    -1: Unknown
1164    * 8) min = minutes; sec = seconds
1165    */
1166 
1167    /* open /proc/apm */
1168    if(fdsc == -1) { /* if not already opened, open apm */
1169      if((fdsc = open("/proc/apm", O_RDONLY)) == -1) {
1170        return -1;
1171      }
1172    }
1173 
1174    /* seek to the beginning of the file */
1175    if(lseek(fdsc, 0, SEEK_SET) != 0 ) {
1176      return -1;
1177    }
1178 
1179   /* read apm state */
1180   if(read(fdsc, buffer, sizeof(buffer)) != sizeof(buffer)) {
1181     return -1;
1182   }
1183 
1184   /* tokenize the apm string */
1185   tokens[0] = strtok(buffer, " ");
1186   for(i = 1 ; i < sizeof(tokens)/sizeof(char*) ; i++) {
1187     tokens[i] = strtok(NULL, " ");
1188   }
1189 
1190   /* determine the AC line status */
1191   switch(strtol(tokens[3], NULL, 16)) {
1192     case 0x00:
1193       thinkpad_state->ac_state = STATE_OFF;
1194       break;
1195 
1196     case 0x01:
1197       thinkpad_state->ac_state = STATE_ON;
1198       break;
1199   }
1200 #else
1201   u_long addr;
1202   size_t len = sizeof(addr);
1203 
1204   if ( fdsc != -1 || sysctlbyname("hw.acpi.acline", &addr, &len, NULL, 0) == -1 ) {
1205 #ifdef __i386__
1206     // Try APM
1207     if(fdsc == -1) { /* if not already opened, open apm */
1208       if((fdsc = open("/dev/apm", O_RDONLY)) == -1)
1209         return -1;
1210     }
1211 
1212     struct apm_info info;
1213     if (ioctl(fdsc, APMIO_GETINFO, &info) == -1)
1214       return -1;
1215 
1216     if (info.ai_acline > 2)
1217       return -1;
1218 
1219     addr = info.ai_acline;
1220 #endif
1221   }
1222   thinkpad_state->ac_state = (addr == 1) ? STATE_ON : STATE_OFF;
1223 #endif
1224   return 0;
1225 } /* }}} */
1226 
1227 #ifdef HAVE_LIBX11
1228 /* get key events from X */
xgrabkey(t_thinkpad_state * thinkpad_state,Display * display)1229 int xgrabkey(t_thinkpad_state *thinkpad_state, Display *display) /* {{{ */
1230 {
1231   XEvent event;
1232 
1233   while(XPending(display) > 0) {
1234     XNextEvent(display, &event);
1235 
1236     if (event.xkey.type == KeyPress) { /* needed, because KeyReleases are also received, even if XSelectInput only specifies KeyPressMask!! */
1237       switch(event.xkey.keycode) {
1238 	case KEYCODE_HOME:
1239 	  thinkpad_state->home_toggle ^= 0x02;
1240 	  break;
1241 
1242 	case KEYCODE_SEARCH:
1243 	  thinkpad_state->search_toggle ^= 0x02;
1244 	  break;
1245 
1246 	case KEYCODE_MAIL:
1247 	  thinkpad_state->mail_toggle ^= 0x02;
1248 	  break;
1249 
1250 	case KEYCODE_FAVORITES:
1251 	  thinkpad_state->favorites_toggle ^= 0x02;
1252 	  break;
1253 
1254 	case KEYCODE_RELOAD:
1255 	  thinkpad_state->reload_toggle ^= 0x02;
1256 	  break;
1257 
1258 	case KEYCODE_ABORT:
1259 	  thinkpad_state->abort_toggle ^= 0x02;
1260 	  break;
1261 
1262 	case KEYCODE_BACKWARD:
1263 	  thinkpad_state->backward_toggle ^= 0x02;
1264 	  break;
1265 
1266 	case KEYCODE_FORWARD:
1267 	  thinkpad_state->forward_toggle ^= 0x02;
1268 	  break;
1269 
1270 	case KEYCODE_FN_KEY:
1271 	  thinkpad_state->fn_toggle ^= 0x02;
1272 	  break;
1273 
1274 	default:
1275 	  break;
1276       }
1277     }
1278   }
1279 
1280   return 0;
1281 } /* }}} */
1282 #endif /* HAVE_LIBX11 */
1283 
1284 /* fork an application */
fork_app(char * cmd)1285 int fork_app(char * cmd) /* {{{ */
1286 {
1287   enum mode_t {SKIP, IN_QUOTED, IN_UNQUOTED};
1288 
1289   char *cmd_cpy;
1290   int cmd_len;
1291   char *args[CALLBACK_CMD_ARGS];
1292   int ptr;
1293   int argc;
1294   enum mode_t mode;
1295   char quote_char;
1296   int fdsc, fdsc_max;
1297 
1298   switch(fork()) {
1299     case -1:
1300       return -1;
1301       break;
1302 
1303     case 0:
1304       ptr = 0;
1305       argc = 0;
1306       mode = SKIP;
1307       quote_char = '"';
1308       memset(args, 0, sizeof(args));
1309       cmd_cpy = strdup(cmd);
1310       cmd_len = strlen(cmd_cpy);
1311 
1312       /* close ALL file descriptors except stdin, stdout and stderr */
1313       for (fdsc = 3, fdsc_max = sysconf(_SC_OPEN_MAX); fdsc < fdsc_max; fdsc++) {
1314         close(fdsc);
1315       }
1316 
1317       /* generate an array of cmd_cpy string */
1318       while (ptr < cmd_len) {
1319 	switch (mode) {
1320 	  /* skip leading whitespaces */
1321 	  case SKIP:
1322 	    if (cmd_cpy[ptr] != ' ' && cmd_cpy[ptr] != '\t') {
1323 	      if (cmd_cpy[ptr] == '"' || cmd_cpy[ptr] == '\'') {
1324 		mode = IN_QUOTED;
1325 		quote_char = cmd_cpy[ptr];
1326 		ptr++;
1327 	      }
1328 	      else {
1329 		mode = IN_UNQUOTED;
1330 	      }
1331 	      args[argc] = &cmd_cpy[ptr];
1332 	      argc++;
1333 	    }
1334 	    ptr++;
1335 	    break;
1336 
1337 	  /* within a quoted string */
1338 	  case IN_QUOTED:
1339 	    if (cmd_cpy[ptr] == quote_char) {
1340 	      cmd_cpy[ptr] = '\0';
1341 	      mode = SKIP;
1342 	    }
1343 	    ptr++;
1344 	    break;
1345 
1346 	  /* within a unquoted string */
1347 	  case IN_UNQUOTED:
1348 	    if (cmd_cpy[ptr] == ' ' || cmd_cpy[ptr] == '\t') { /* this is the end of the string */
1349 	      cmd_cpy[ptr] = '\0';
1350 	      mode = SKIP;
1351 	    }
1352 	    ptr++;
1353 	    break;
1354 	}
1355       }
1356 
1357       setsid(); /* children should not be killed if tpb ends */
1358       chdir(getenv("HOME")); /* try to change to users home directory */
1359       execv(args[0], args);
1360 
1361       /* should never be reached, execv only returns on error */
1362       fprintf(stderr, _("Unable to fork application \"%s\".\n"), args[0]);
1363       fprintf(stderr, _("HINT:\n"));
1364       fprintf(stderr, _("With tpb version 0.6.2 or later you need to specify applications\n"));
1365       fprintf(stderr, _("using the whole path to the executable program.\n"));
1366       free(cmd_cpy);
1367       _exit(0);
1368       return 127;
1369       break;
1370 
1371     default:
1372       return 0;
1373       break;
1374   }
1375   return -1; /* never reached */
1376 } /* }}} */
1377 
1378 /* set the volume level in the nvram */
set_nvram_volume_level(t_thinkpad_state * thinkpad_state)1379 void set_nvram_volume_level(t_thinkpad_state *thinkpad_state) /* {{{ */
1380 {
1381   int  fdsc;
1382   char buffer;
1383 
1384   /* only use writeback to nvram when cfg.mixersteps is different from DEFAULT_MIXERSTEPS */
1385   if(cfg.mixersteps != DEFAULT_MIXERSTEPS) {
1386 #ifdef __FreeBSD__
1387     u_int n = thinkpad_state->volume_level;
1388 
1389     if (sysctlbyname("dev.acpi_ibm.0.volume", NULL, NULL, &n, sizeof(n)) != 0)
1390       fprintf(stderr, _("Unable to set volume sysctl"));
1391     else {
1392 #endif
1393     /* open nvram */
1394     if((fdsc = open(cfg.nvram, O_RDWR|O_NONBLOCK)) == -1) {
1395       fprintf(stderr, _("Unable to open device %s: "), cfg.nvram);
1396       perror(NULL);
1397       fprintf(stderr, _("To use mixersteps other than %d you need write access to %s.\n"), cfg.mixersteps, cfg.nvram);
1398       _exit(1);
1399     }
1400 
1401     /* jump to volume section */
1402     if(lseek(fdsc, 0x60, SEEK_SET) == -1 ) {
1403       fprintf(stderr, _("Unable to seek device %s: "), cfg.nvram);
1404       perror(NULL);
1405       _exit(1);
1406     }
1407 
1408     /* read nvram */
1409     if(read(fdsc, &buffer,sizeof(buffer)) != sizeof(buffer)) {
1410       fprintf(stderr, _("Unable to read from device %s: "), cfg.nvram);
1411       perror(NULL);
1412       _exit(1);
1413     }
1414 
1415     thinkpad_state->volume_level = 0x07; /* set volume_level to the value we write back to nvram */
1416     buffer &= 0xf0;
1417     buffer |= thinkpad_state->volume_level;
1418 
1419     /* jump to volume section */
1420     if(lseek(fdsc, 0x60, SEEK_SET) == -1 ) {
1421       fprintf(stderr, _("Unable to seek device %s: "), cfg.nvram);
1422       perror(NULL);
1423       _exit(1);
1424     }
1425 
1426     /* write std value for volume */
1427     if(write(fdsc, &buffer, sizeof(buffer)) != sizeof(buffer)) {
1428       fprintf(stderr, _("Unable to write to device %s: "), cfg.nvram);
1429       perror(NULL);
1430       _exit(1);
1431     }
1432 
1433     close(fdsc);
1434 #ifdef __FreeBSD__
1435     }
1436 #endif
1437   }
1438   return;
1439 
1440 } /* }}} */
1441 
1442 /* change the volume level of the OSS mixer device */
change_volume(int change)1443 unsigned int change_volume(int change) /* {{{ */
1444 {
1445   int mixer;
1446   unsigned int volume;
1447   unsigned int left,right;
1448 
1449   /* open mixer */
1450   if((mixer = open(cfg.mixerdev, O_RDWR)) == -1) {
1451     fprintf(stderr, _("Unable to open mixer device %s: "), cfg.mixerdev);
1452     perror(NULL);
1453     _exit(1);
1454   }
1455 
1456   /* read mixer volume */
1457   if(ioctl(mixer, SOUND_MIXER_READ_VOLUME, &volume) == -1) {
1458     fprintf(stderr, _("Unable to read volume from mixer device %s: "), cfg.mixerdev);
1459     perror(NULL);
1460     _exit(1);
1461   }
1462 
1463   /* adjust volume */
1464   if(((int)(volume & 0xff) + change) < 0) {
1465     left = 0;
1466   }
1467   else {
1468     left = (volume & 0xff) + change;
1469     if(left > MAX_VOLUME) {
1470       left = MAX_VOLUME;
1471     }
1472   }
1473   if((((int)(volume >> 8) & 0xff) + change) < 0) {
1474     right = 0;
1475   }
1476   else {
1477     right = ((volume >> 8) & 0xff) + change;
1478     if(right > MAX_VOLUME) {
1479       right = MAX_VOLUME;
1480     }
1481   }
1482   volume = left | (right << 8);
1483 
1484   /* write volume back to mixer */
1485   if(ioctl(mixer, SOUND_MIXER_WRITE_VOLUME, &volume) == -1) {
1486     fprintf(stderr, _("Unable to write volume to mixer device %s: "), cfg.mixerdev);
1487     perror(NULL);
1488     _exit(1);
1489   }
1490 
1491   /* close mixer device */
1492   if(close(mixer) == -1 ) {
1493     fprintf(stderr, _("Unable to close mixer device %s: "), cfg.mixerdev);
1494     perror(NULL);
1495     _exit(1);
1496   }
1497 
1498   /* calc volume percentage and return it */
1499   return ((left + right) / 2) * 100 / MAX_VOLUME;
1500 
1501 } /* }}} */
1502 
1503 /* check if apmiser is running */
apmiser_running(void)1504 int apmiser_running(void) /* {{{ */
1505 {
1506   /* code inspired by comp.unix.programmer FAQ */
1507   char line[133];
1508   char *linep;
1509   char *token;
1510   char *cmd = NULL;
1511   FILE *fp;
1512 
1513   /* open command like a file */
1514   fp = popen("ps -e 2>/dev/null", "r");
1515   if (fp == NULL) {
1516     return 0;
1517   }
1518 
1519   /* get header */
1520   if (fgets(line, sizeof(line), fp) == NULL) {
1521     pclose(fp);
1522     return 0;
1523   }
1524 
1525   /* determine column of command name */
1526   linep = line;
1527   while(cmd == NULL)
1528   {
1529     if ((token = strtok(linep, " \t\n")) == NULL) {
1530       pclose(fp);
1531       return 0;
1532     }
1533     linep = NULL;
1534 
1535     if (strcmp("COMMAND", token) == 0 || strcmp("CMD", token) == 0) {
1536       cmd = token;
1537     }
1538   }
1539 
1540   /* check if any of the commands is apmiser */
1541   while(fgets(line, sizeof line, fp) != NULL) {
1542     if (strstr(strtok(cmd, " \t\n"), "apmiser") != NULL) {
1543       pclose(fp);
1544       return 1;
1545     }
1546   }
1547 
1548   pclose(fp);
1549 
1550   return 0;
1551 } /* }}} */
1552 
1553 #ifdef HAVE_LIBX11
1554 /* handler for X errors */
xerrorhandler(Display * display,XErrorEvent * event)1555 int *xerrorhandler(Display *display, XErrorEvent *event) /* {{{ */
1556 {
1557   static int called = 0;
1558 
1559   if (called == 0) {
1560     called = 1;
1561     fputs(_("There is already a application grabbing some ThinkPad keys.\n"), stderr);
1562     fputs(_("Possibly you restarted tpb too fast.\n"), stderr);
1563   }
1564 
1565   return(NULL);
1566 } /* }}} */
1567 #endif /* HAVE_LIBX11 */
1568 
1569 /* handler for SIG_CHLD */
sig_chld_handler(int signo)1570 void sig_chld_handler(int signo) /* {{{ */
1571 {
1572   int status;
1573   waitpid(-1, &status, WNOHANG);
1574   return;
1575 } /* }}} */
1576 
1577 /* vim600:set fen:set fdm=marker:set fdl=0: */
1578