#!/usr/bin/env raku

#| Sets up buttons etc on drawing tablets
unit sub MAIN(
  #| Don't run the commands, just print them (implies -v)
  Bool :n(:$dry-run),
  #| Don't turn on the screens
  Bool :S(:$no-screens),
  #| Print each command before running it
  Bool :v(:$verbose) is copy,
  #| Print the output of queries too (implies -v)
  Bool :V(:$very-verbose),
);

$verbose ||= $dry-run || $very-verbose;


sub say-prefix(:$prefix = '### ', |c) {
  say $prefix, |c
}

sub say-vv(:$prefix = '### ', |c) {
  say-prefix :$prefix, |c if $very-verbose
}

sub say-v(:$prefix = '### ', |c) {
  say-prefix :$prefix, |c if $verbose
}

sub quote(Str() $_) {
  when ''   { '""' }
  when /\s/ { qq|"$_"| }
  default   { $_ }
}

sub say-cmd(*@cmd, Bool() :$query!) {
  my $prefix = $dry-run && !$query ?? "# " !! "> ";
  say-v :$prefix, @cmd».&quote.join(' ');
}

sub query(*@cmd) {
  say-cmd @cmd, :query;
  my @out = run(|@cmd, :out :!in).out.lines(:close);
  say-vv :prefix('< '), $_ for @out;
  @out
}

sub update(*@cmd) {
  say-cmd @cmd, :!query;
  unless $dry-run { run(|@cmd, :!out :!in :err) }
  Nil
}


class Subdev { ... }

class Tablet {
  has Str $.name;

  my @devices = lazy query <xsetwacom --list devices>;

  method Str { $.name }

  method find($name) {
    for @devices {
      when /^ $<name>=(.* $name .*) «Pen»/ {
        return self.new: name => $<name>.trim;
      }
    }
    fail "!!! Tablet '$name' not found";
  }

  method subdev($type, $name) { Subdev.new: tablet => self, :$type, :$name; }

  method pad()    { self.subdev: 'Pad', 'pad' }
  method stylus() { self.subdev: 'Pen', 'stylus' }
  method eraser() { self.subdev: 'Pen', 'eraser' }
  method cursor() { self.subdev: 'Pen', 'cursor' }
}

class Subdev {
  has Tablet $.tablet; has Str $.type; has Str $.name;

  method Str { "$.tablet $.type $.name" }

  method set(*%params-args) {
    for %params-args.kv -> $param, $args {
      update <xsetwacom --set>, self, $param, |$args;
    }
  }
}


sub key($k, *%mods where *.values.all) {
  my @mods = %mods.keys.map: {
    when /:i alt [_ | \-]? gr/ { 'ISO_Level3_Shift' }
    default { $_ }
  };
  "key " ~ reduce { "+$^b $^a -$^b" }, $k, |@mods
}


sub external-connected {
  for query <xrandr --listmonitors> {
    when /^\h* \d+ ':' \h* '+'? '*'? $<name>=(\S+)/ {
      my $name = ~$<name>;
      return $name unless $name ~~ /eDP/;
    }
  }
}

################################################################################

my $did-something = False;

my $external = external-connected;

with $external { say-v "using display '$_'" }

with Tablet.find: 'Intuos4' {
  say-v "Setting up \"$_\"";
  $did-something = True;

  my $screen-dir = query(<find /sys/devices -name wacom_led>)[0];
  with $screen-dir {
    say-v "wacom_led directory: {quote($screen-dir)}";
  } else {
    fail "!!! wacom_led directory not found";
  }

  for .stylus, .eraser, .cursor {
    .set: :Rotate<half> :Area<0 0 65024 36576>; # full 65024x40640

    if defined $external { .set: :MapToOutput($external); }
  }

  for .stylus, .eraser {
    .set: :Button(3, key(:ctrl, 'z'));
  }

  my %buttons = (
    # assuming left handed
    # circle
    1  => {map => key('tab')},
    # upper four, top to bottom
    13 => {map => key('shift'),    screen => 7, label => "🠙 \xF0E2"}, # rotate
    12 => {map => key('ctrl'),     screen => 6, label => "⌃ \xF002"}, # magnifying glass
    11 => {map => key('m'),        screen => 5, label => '⌽'},
    10 => {map => key('5'),        screen => 4, label => '⦜'},
    # lower four
    9  => {map => key('.'),        screen => 3, label => '●'},
    8  => {map => key(','),        screen => 2, label => '⋅'},
    3  => {map => key('insert'),   screen => 1, label => '⏥'},
    2  => {map => key(:ctrl, <'>), screen => 0, label => '𝞪'},
  );

  update <sudo i4oled-chgrp>, $screen-dir;

  spurt "$screen-dir/buttons_luminance", ($no-screens ?? 0 !! 8);

  given .pad {
    .set: :AbsWheelDown(key('shift')) :AbsWheelUp(key('shift'));

    for %buttons.kv -> $button, % (:$map, :$screen, :$label) {
      .set: :Button($button, $map);

      next if $no-screens or not defined $label & $screen;

      my $path = "$screen-dir/button{$screen}_rawimg";
      fail "!!! no such device $path" unless $path.IO.f;
      update <i4oled -l -t>, $label, '-d', $path;
    }
  }
}

with Tablet.find: 'H420' {
  say-v "Setting up \"$_\"";
  $did-something = True;

  given .stylus {
    .set: :Rotate<half> :Area<0 0 8320 4680> # full: 8340x4680
          :PressureCurve<0 50 50 100> :Button(3, key(:ctrl, 'z'));

    # FIXME do this properly
    .set: :MapToOutput<960x540+480+270>;

    if defined $external { .set: :MapToOutput($external); }
  }

  my %buttons = (
    # top to bottom assuming left handed
    3 => key(:ctrl, 'z'),
    2 => key(:ctrl:shift, 'z'),
    1 => key('tab'),
  );

  for %buttons.kv -> $b, $map {
    .pad.set: :Button($b, $map);
  }
}

say "Didn't find any tablets" unless $did-something;

# vim: set ft=raku :