#!/usr/bin/perl
# thinkpad-hotkey-mask -- compose new or display existing thinkpad hotkey mask
# Author: Noah Friedman <friedman@splode.com>
# Created: 2010-12-10
# Public domain

# $Id: thinkpad-hotkey-mask,v 1.4 2016/07/24 05:41:23 friedman Exp $

use strict;
use Symbol;

$^W = 1; # enable warnings

my $tp_acpi_dir = "/sys/devices/platform/thinkpad_acpi";

# Data lifted from linux 4.1.7 thinkpad_acpi.c module
# mute2 and later are only available on so-called "adaptive" keyboards.
my @tp_acpi_hotkey_enum =
  (qw(fnf1 fnf2 fnf3 fnf4 fnf5 fnf6 fnf7 fnf8 fnf9 fnf10 fnf11 fnf12
      fnbackspace fninsert fndelete fnhome fnend fnpageup fnpagedown fnspace
      volumeup volumedown mute thinkpad
      unk1 unk2 unk3 unk4 unk5 unk6 unk7 unk8

      mute2 brightness_zero clipping_tool cloud unk9 voice unk10 gestures
      unk11 unk12 unk13
      config new_tab reload back
      mic_down mic_up mic_cancellation
      camera_mode rotate_display));

# Not currently used
#my %tp_acpi_hkey_alias =
#    ( dispswtch => 'fnf7',
#      dispxpand => 'fnf8',
#      hibernate => 'fnf12',
#      brghtup   => 'fnhome',
#      brghtdwn  => 'fnend',
#      thnklght  => 'fnpageup',
#      zoom      => 'fnspace',
#     );

sub tp_acpi_get
{
  my $file = "$tp_acpi_dir/$_[0]";
  open (my $fh, $file) || die "open: $file: $!\n";
  local $/ = undef;
  local $_ = <$fh>;
  s/[\r\n]//g;
  return hex ($_);
}

sub trim_unsupported_mask
{
  my $all_mask = tp_acpi_get ("hotkey_all_mask");
  my ($my_mask, $i) = (0, 0);
  $my_mask |= 1 << $i++ while $my_mask < $all_mask;
  splice (@tp_acpi_hotkey_enum, $i) if $i < @tp_acpi_hotkey_enum;
}

my %tp_acpi_hkey_mask;
sub init_tp_acpi_hkey_mask
{
  for (my $i = 0; $i < @tp_acpi_hotkey_enum; $i++)
    {
      my $key = $tp_acpi_hotkey_enum[$i];
      $tp_acpi_hkey_mask{$key} = 1 << $i;
    }
}

sub get_hkey_mask
{
  local $_ = shift;

  return $tp_acpi_hkey_mask{$_} if exists $tp_acpi_hkey_mask{$_};
  return hex ($_)               if /^0x/;
  return $_                     if /^\d+$/;
  return tp_acpi_get ($_);
}

sub print_hkey_mask
{
  my $val = get_hkey_mask ($_[0]);
  printf ("%s:  0x%x  0%o  %b\n\n", "MASK", $val, $val, $val);

  my $w16 = display_width (scalar @tp_acpi_hotkey_enum, 16);
  my $wnm = max (map { length $_ } @tp_acpi_hotkey_enum);
  my $fmt = "%02d  0x%0${w16}x  %-${wnm}s  =  %s\n";
  for (my $i = 0; $i < @tp_acpi_hotkey_enum; $i++)
    {
      my $name = $tp_acpi_hotkey_enum[$i];
      my $mask = $tp_acpi_hkey_mask{$name};
      my $on = $val & $mask;
      printf ($fmt, $i, $mask, $name, ($on ? "ON" : ""));
    }
}

sub display_width
{
  my ($bits, $base) = @_;
  int (1 + (log (2**$bits) / log ($base)));
}

sub max
{
  my $x = shift;
  map { $x = $_ if $_ > $x } @_;
  return $x;
}

sub main
{
  trim_unsupported_mask  ();
  init_tp_acpi_hkey_mask ();

  my ($writep, $hexp) = (0, 0);
  if    (@_ && $_[0] =~ /^--hex/)   { $hexp   = 1; shift }
  elsif (@_ && $_[0] =~ /^--write/) { $writep = 1; shift }

  my $mask = get_hkey_mask ("hotkey_mask");
  for my $arg (@_)
    {
      if    ($arg =~ /^\+(.*)/) { $mask |=   get_hkey_mask ($1)   }
      elsif ($arg =~ /^-(.*)/)  { $mask &= ~(get_hkey_mask ($1))  }
      elsif ($arg =~ /^=(.*)/)  { $mask  =   get_hkey_mask ($1)   }
      else                      { $mask |=   get_hkey_mask ($arg) }
    }

  if ($writep)
    {
      my $file = "$tp_acpi_dir/hotkey_mask";
      open (my $fh, "> $file") || die "open: $file: $!\n";
      printf $fh ("0x%x\n", $mask);
    }
  elsif ($hexp) { printf ("0x%x\n", $mask) }
  else          { print_hkey_mask ($mask) }
}

main (@ARGV);

# eof
