  /* 
     ikeyd - reacts on iBook2/Powerbooks hotkeys.

     Copyright (C) 2002 Stefan Pfetzing

     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2, or (at your option)
     any later version.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software Foundation,
     Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  
   */

/* Don't wonder about these triple "«" and "»", these are markers for vim's
   folding function. */

/* some includes ««« */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <linux/version.h>
#include <linux/input.h>
#include <linux/cdrom.h>
#include <linux/soundcard.h>
#include <linux/types.h>
#include <linux/cdrom.h>
#include <linux/fb.h>
#include <linux/pmu.h>
#include <linux/fd.h>
#include "config.h"
/* »»» */

/* define default settings ««« */
#define DEFAULT_MIXER "/dev/mixer"
#define DEFAULT_CDROM "/dev/cdrom"
#define DEFAULT_PMU "/dev/pmu"
#define DEFAULT_PIDFILE "/var/run/ikeyd.pid"
#define EVENT_PATH "/dev/input/event"
/* »»» */


static void usage (int status);

/* The name the program was run with, stripped of any leading path. */
char *program_name;

/* Option flags and variables ««« */
int want_quiet;			/* --quiet, --silent */
int want_verbose;		/* --verbose */
int want_nodaemon;		/* --verbose */
char *mixer;			/* --mixer */
char *cdrom;			/* --cdrom */
char *pidfile;			/* --pidfile */
/* »»» */

/* name of the event device */
char *event_device;

/* the structure of all long options for getopt ««« */
static struct option const long_options[] = {
  {"quiet", no_argument, 0, 'q'},
  {"silent", no_argument, 0, 'q'},
  {"verbose", no_argument, 0, 'v'},
  {"help", no_argument, 0, 'h'},
  {"version", no_argument, 0, 'V'},
  {"mixer", required_argument, 0, 'm'},
  {"cdrom", required_argument, 0, 'c'},
  {"pidfile", required_argument, 0, 'p'},
  {"nodaemon", no_argument, 0, 'n'},
  {NULL, 0, NULL, 0}
};

/* »»» */

/* prototypes of all functions ««« */
static int decode_switches (int argc, char **argv);
static int check_mixer ();
static int check_cdrom ();
static int check_pmu ();
static void check_pidfile ();
static int find_button_device ();
static void eject_cdrom ();
static void volume_change (int level);
static void remove_pidfile (int signum);
static void wait_child (int signum);
static int brightness_level (int brlevel);
static void brightness_down ();
static void brightness_up ();
/* »»» */

/* the structure for stat(...) */
struct stat stat_buf;

/* int main (int argc, char **argv) ««« */
int
main (int argc, char **argv)
{
  /* store status of cdrom and sound devices */
  int cdrom_avail = 0;
  int mixer_avail = 0;
  int pmu_avail = 0;
  /* declare some variables and structures ««« */
  /* the filedescriptor of the event interface */
  int eventfd = -1;
  /* the filedescriptor of the pidfile */
  FILE *pidstream;
  /* the pid of the child */
  pid_t childpid = 0;
  /* the structure for the input event, see input.h */
  struct input_event inp;
  /* the count of events caught */
  int count = 0;
  /* the keycode of the last key presssed */
  int last_keycode = 0;
  int i;
  /* the returnvalue from read */
  ssize_t ret;
  /* the sigaction handler for SIGTERM */
  struct sigaction sigterm = { {remove_pidfile}, {{0}}, SA_RESTART, 0 };
  /* the sigaction handler for SIGCHLD */
  struct sigaction sigchld = { {wait_child}, {{0}}, SA_RESTART, 0 };
  /* »»» */

  /* install the signal handlers ««« */
  if (sigaction (SIGINT, &sigterm, 0))
    {
      fprintf (stderr, "%s: could not install SIGINT handler\n", PACKAGE);
      exit (EXIT_FAILURE);
    }

  if (sigaction (SIGTERM, &sigterm, 0))
    {
      fprintf (stderr, "%s: could not install SIGTERM handler\n", PACKAGE);
      exit (EXIT_FAILURE);
    }

  if (sigaction (SIGCHLD, &sigchld, 0))
    {
      fprintf (stderr, "%s: could not install SIGCHLD handler\n", PACKAGE);
      exit (EXIT_FAILURE);
    }
  /* »»» */

  program_name = argv[0];

  i = decode_switches (argc, argv);

  /* check if the given mixer is correct */
  mixer_avail = check_mixer ();

  /* check if the given cdrom is correct */
  cdrom_avail = check_cdrom ();

  /* check if the pmu device is working */
  pmu_avail = check_pmu ();

  /* check if we could do anything */
  if ((cdrom_avail + mixer_avail + pmu_avail) == 0)
    {
      fprintf (stderr,
	       "What should I do? Neither mixer nor cdrom available!\n");
      exit (EXIT_FAILURE);
    }

  /* now search the button device */
  eventfd = find_button_device ();

  /* check the pidfile */
  check_pidfile ();

  if (!want_nodaemon)		/* ««« */
    {
      /* fork and write the PID of the child into
       * the pidfile ««« */
      childpid = fork ();
      if (childpid)
	{
	  pidstream = fopen (pidfile, "w+a");
	  if (pidstream != NULL)
	    {
	      fprintf (pidstream, "%i\n", childpid);
	    }
	  else
	    {
	      fprintf (stderr, "%s: warning, couldn't save the pid in %s!\n",
		       PACKAGE, pidfile);
	    }
	  fclose (pidstream);
	  exit (0);
	}			/* »»» */

      /* make sure the daemon process doesn't do
       * anything weired. ««« */
      fclose (stdin);
      fclose (stderr);
      fclose (stdout);

      chdir ("/");

      setsid ();		/* »»» */
    }				/* »»» */

  /* do the work ««« */
  while ((ret = read (eventfd, &inp, sizeof (inp))) > 0)
    {
	/* on keypress */
      if (want_verbose && want_nodaemon)
	printf ("%s: keycode %i value %i type %i\n", PACKAGE, inp.code, inp.value, inp.type);

      /* on keypress inp.value is set, on release it is not set
       * if (inp.value) ««« */
      if (inp.value)
	{
	  /* if the last key pressed is not the same as the
	   * current key, reset the count, else increment it. */
	  if (last_keycode != inp.code)
	    count = 1;
	  else
	    count++;
	  last_keycode = inp.code;

	  /* choose the action for the keycode */
	  switch (inp.code)
	    {
	    case KEY_BRIGHTNESSDOWN:
	      /* decrement the brightness level */
	      if (pmu_avail)
		brightness_down ();
	      break;
	    case KEY_BRIGHTNESSUP:
	      /* increment the brightness level */
	      if (pmu_avail)
		brightness_up ();
	      break;
	    case KEY_MUTE:
	      /* mute the mixer */
	      if (mixer_avail)
		volume_change (0);
	      break;
	    case KEY_VOLUMEDOWN:
	      /* decrement the volume */
	      if (mixer_avail)
		volume_change (-8);
	      break;
	    case KEY_VOLUMEUP:
	      /* increment the volume */
	      if (mixer_avail)
		volume_change (+8);
	      break;
	    case KEY_EJECTCD:
	      if (cdrom_avail)
		{
		  /* eject the cdrom */
		  if (count == 15)
		    {
		      eject_cdrom ();
		    }
		}
	      break;
	    }
	}			/* »»» */
      else if (inp.type)
	{			/* ««« */
	  /* on keyrelease */
	  if (cdrom_avail)
	    {
	      if (last_keycode == KEY_EJECTCD && count == 15)
		{
		  eject_cdrom ();
		}
	    }
	  last_keycode = 0;
	  count = 0;
	}			/* »»» */
    }				/* »»» */

  /* check the return value of read(...) ««« */
  if (ret < 0)
    {
      fprintf (stderr, "%s: Couldn't read from %s: %s\n", PACKAGE, event_device, strerror (errno));
    }				/* »»» */

  return 0;
}				/* »»» */

/* Set all the option flags according to the switches specified.
   Return the index of the first non-option argument.  */

/* static int decode_switches (int argc, char **argv) ««« */
static int
decode_switches (int argc, char **argv)
{
  int c;


  while ((c = getopt_long (argc, argv, "q"	/* quiet or silent */
			   "v"	/* verbose */
			   "h"	/* help */
			   "V"	/* version */
			   "m:"	/* mixer */
			   "c:"	/* cdrom */
			   "p:"	/* pidfile */
			   "n",	/* nodaemon */
			   long_options, (int *) 0)) != EOF)
    {
      switch (c)
	{
	case 'q':		/* --quiet, --silent */
	  want_quiet = 1;
	  break;
	case 'v':		/* --verbose */
	  want_verbose = 1;
	  break;
	case 'm':		/* --mixer */
	  mixer = optarg;
	  break;
	case 'c':		/* --cdrom */
	  cdrom = optarg;
	  break;
	case 'p':		/* --pidfile */
	  pidfile = optarg;
	  break;
	case 'n':		/* --nodaemon */
	  want_nodaemon = 1;
	  break;
	case 'V':
	  printf ("%s %s\n", PACKAGE, VERSION);
	  exit (0);

	case 'h':
	  usage (0);

	default:
	  usage (EXIT_FAILURE);
	}
    }

  return optind;
}				/* »»» */

/* static void usage (int status) ««« */
static void
usage (int status)
{
  printf ("%s - \
reacts on iBook2/Powerbooks hotkeys.\n", program_name);
  printf ("Usage: %s [OPTION]... \n", program_name);
  printf ("\
Options:\n\
  -m, --mixer=MIXER          use alternative mixer-device,\n\
                             default is %s\n\
  -c, --cdrom=CDROM          use alternative cdrom-device,\n\
                             default is %s\n\
  -p, --pidfile=PIDFILE      use alternative pidfile,\n\
                             default is %s\n\
  -n, --nodaemon             do not run as a daemon\n\
  -q, --quiet, --silent      inhibit usual output\n\
  --verbose                  print more information\n\
  -h, --help                 display this help and exit\n\
  -V, --version              output version information and exit\n\
", DEFAULT_MIXER, DEFAULT_CDROM, DEFAULT_PIDFILE);
  exit (status);
}				/* »»» */

/* static int check_mixer () ««« */
static int
check_mixer ()
{
  /* set the default mixer */
  if (mixer == NULL)
    {
      mixer = DEFAULT_MIXER;
    }

  /* check if the given mixer device is correct */
  if (stat (mixer, &stat_buf))
    {
      /* an error while stating occured */
      fprintf (stderr, "%s: ", PACKAGE);
      fprintf (stderr, "could not open device %s\n", mixer);
      return 0;
    }
  else
    {
      /* the mixer does exist, now check the type of the file. */
      if (!S_ISCHR (stat_buf.st_mode))
	{
	  printf ("%s: mixer %s is not a character device!\n", PACKAGE,
		  mixer);
	  return 0;
	}
    }
  if (want_verbose)
    printf ("%s: Mixer is: %s\n", PACKAGE, mixer);
  return 1;
}				/* »»» */

/* static int check_cdrom () ««« */
static int
check_cdrom ()
{
  /* set the default cdrom */
  if (cdrom == NULL)
    cdrom = DEFAULT_CDROM;

  /* check if the given cdrom device is correct */
  if (stat (cdrom, &stat_buf))
    {
      /* an error while stating occured */
      if (strcmp (cdrom, DEFAULT_CDROM) == 0)
	{
	  fprintf (stderr, "Something went wrong with %s:\n", DEFAULT_CDROM);
	  fprintf (stderr, "%s: ", PACKAGE);
	  fprintf (stderr, "could not open device %s\n", cdrom);
	  return 0;
	}
      else
	{
	  fprintf (stderr, "%s: ", PACKAGE);
	  fprintf (stderr, "could not open device %s\n", cdrom);
	  return 0;
	}
    }
  else
    {
      /* the mixer does exist, now check the type of the file. */
      if (!S_ISBLK (stat_buf.st_mode))
	{
	  printf ("%s: cdrom %s is not a block device!\n", PACKAGE, cdrom);
	  return 0;
	}
    }
  if (want_verbose)
    printf ("%s: Cdrom is: %s\n", PACKAGE, cdrom);
  return 1;
}				/* »»» */

/* static int check_pmu () ««« */
static int
check_pmu ()
{
  static int pmu_fd = -1;
  pmu_fd = open (DEFAULT_PMU, O_RDONLY);
  if (pmu_fd < 0)
    {
      fprintf (stderr, "%s: %s: %s\n", PACKAGE, DEFAULT_PMU, strerror(errno));
      return 0;
    }
  close (pmu_fd);
  return 1;
}			/* »»» */

/* static void check_pidfile () ««« */
static void
check_pidfile ()
{
  /* set the default pidfile */
  if (pidfile == NULL)
    pidfile = DEFAULT_PIDFILE;

  /* check if the given pidfile is correct */
  if (stat (pidfile, &stat_buf))
    {
      if (errno != ENOENT)
	{
	  /* an error while stating occured */
	  fprintf (stderr, "%s: %s: %s \n", PACKAGE, pidfile, strerror (errno));
	  exit (EXIT_FAILURE);
	}
    }
  else
    {
      fprintf (stderr, "%s: %s exists. %s seems to be running!\n", PACKAGE,
	       pidfile, PACKAGE);
      exit (EXIT_FAILURE);
    }

  if (want_verbose)
    printf ("%s: pidfile is: %s\n", PACKAGE, pidfile);
}				/* »»» */

/* static int find_button_device () ««« */
static int
find_button_device ()
{
  /* the filename of the event interface */
  char *filename;
  /* the filedescriptor of the event interface */
  int eventfd = -1;
  struct input_id id;
  char name[256] = "UNKNOWN";
  int i;

  for (i = 0; i < 32; i++)
    {
      filename = malloc (strlen (EVENT_PATH) + 3);
      sprintf (filename, "%s%i", EVENT_PATH, i);
      /* try to open the device */
      eventfd = open (filename, O_RDONLY);
      /* if opening suceeded */
      if (eventfd >= 0)
	{
	  /* check the id of the event interface */
	  ioctl (eventfd, EVIOCGID, &id);
	  if (want_verbose)
	    {
	      ioctl (eventfd, EVIOCGNAME(sizeof(name)), name);
	      printf("%s: input: %s name: %s bustype: %d vendor: %d product: %d version: %d\n",
		  PACKAGE, filename, name, id.bustype, id.vendor, id.product, id.version);
	    }
	  
	  if ((id.product & 0xfff) == 0x71f)
	    {
	      if (!want_quiet)
		printf ("%s: Button device found at %s\n", PACKAGE, filename);
	      event_device = filename;
	      break;
	    }
	  else
	    {
	      close (eventfd);
	      eventfd = -1;
	    }
	}
    }

  /* when no event filedesctiptor is set e.g. the event device could not be found */
  if (!(eventfd >= 0))
    {
      free (filename);

      /* check if we are root */
      if (geteuid () != 0)
	{
	  printf
	    ("%s: Couldn't access /dev/input/event* perhaps you are not root?\n",
	     PACKAGE);
	  exit (0);
	}

      /* check if the kernel module is loaded */
      if (!system ("lsmod | grep evdev"))
	{
	  printf
	    ("%s: The evdev kernel module is already loaded, so it seems\n",
	     PACKAGE);
	  printf ("%s: something else is going wrong.\n", PACKAGE);
	  exit (0);
	}

      /* when the kernel module is not loaded and we are not root
       * try to load the kernel module... */
      printf ("%s: I' will try to load some modules...\n", PACKAGE);
      printf ("%s: loading evdev...\n", PACKAGE);
      if (!system ("modprobe evdev"))
	{
	  printf ("%s: loading succeeded.\n", PACKAGE);
	  return find_button_device ();
	}
      else
	printf ("%s: Something wrent wong loading the evdev module.\n",
		PACKAGE);

      exit (0);
    }

  return eventfd;
}				/* »»» */

/* static void eject_cdrom () ««« */
static void
eject_cdrom ()
{
  pid_t cdromchild = fork ();
  char *command;
  if (cdromchild == 0)
    {
      /* the filedescriptor of the cdrom */
      int cdromfd = -1;

      /* the command to be executed */
      command = malloc (strlen ("/bin/umount ") + strlen (cdrom) + 1);
      sprintf (command, "/bin/umount %s", cdrom);

      system (command);

      cdromfd = open (cdrom, O_RDONLY | O_NDELAY);
      if (cdromfd < 0)
	{
	  /* an error while opening occured */
	  fprintf (stderr, "%s: %s: %s", PACKAGE, cdrom, strerror (errno));
	}
      if (want_verbose && want_nodaemon)
	printf ("%s: ejecting the cdrom\n", PACKAGE);
#ifdef CDROMEJECT
      ioctl (cdromfd, CDROMEJECT);
#endif
#ifndef CDROMEJECT
      if (want_verbose && want_nodaemon)
	printf ("%s: ejecting not possible.\n", PACKAGE);
#endif
      close (cdromfd);
      _exit (0);
    }
}				/* »»» */

/* static int brightness_level () ««« */
static int
brightness_level (int brlevel)
{
  static int pmu_fd = -1;
  if (pmu_fd < 0)
    {
      pmu_fd = open (DEFAULT_PMU, O_RDONLY);
      if (pmu_fd < 0)
	{
	  fprintf (stderr, "%s: %s: %s", PACKAGE, DEFAULT_PMU, strerror (errno));
	  return 0;
	}
    }
  ioctl (pmu_fd, ( brlevel < 0  || brlevel > 15 ) ? PMU_IOC_GET_BACKLIGHT : PMU_IOC_SET_BACKLIGHT, &brlevel);
  if (want_verbose)
    printf ("%s: brightness level: %d\n", PACKAGE, brlevel);
  return brlevel;
}			/* »»» */

/* static void brightness_down () ««« */
static void
brightness_down ()
{
  if (want_verbose)
    printf ("%s: brightness down\n", PACKAGE);
  brightness_level (brightness_level (-1) - 1);
}			/* »»» */

/* static void brightness_up () ««« */
static void
brightness_up ()
{
  if (want_verbose)
    printf ("%s: brightness up\n", PACKAGE);
  brightness_level (brightness_level (-1) + 1);
}			/* »»» */

/* changes the volume level, if level is 0 then it will mute the volume *
 * static void volume_change (int level) ««« */
static void
volume_change (int level)
{
  static int old_volume = 0;
  int mixerfd = -1;
  int volume;
  int temp;

  mixerfd = open (mixer, O_RDONLY);

  ioctl (mixerfd, MIXER_READ (SOUND_MIXER_VOLUME), &volume);

  if (level == 0)
    {
      temp = volume;
      volume = old_volume;
      old_volume = temp;
    }
  else
    {
      volume = (volume & 0xff) + level;
      if (volume < 0)
	volume = 0;
      if (volume > 100)
	volume = 100;
      volume |= volume << 8;
      old_volume = 0;
    }

  ioctl (mixerfd, MIXER_WRITE (SOUND_MIXER_VOLUME), &volume);

  close (mixerfd);
}				/* »»» */

/* signal handler for SIGKILL *
 * static void remove_pidfile (int signum) ««« */
static void
remove_pidfile (int signum)
{
  /* remove the pidfile */
  unlink (pidfile);
  exit (0);
}				/* »»» */

/* signal handler for SIGCHLD
 * SIGCHLD is sent to the parent by a child when the child exits *
 * static void wait_child (int signum) ««« */
static void
wait_child (int signum)
{
  /* make sure wait is called when a client exits, to prevent defunct
   * processes */
  pid_t wpid;
  int stat_loc;
  wpid = waitpid (-1, &stat_loc, WNOHANG);
}				/* »»» */

/* vim600:set fmr=«««,»»» fdm=marker: */
