#!/usr/bin/perl
# App::DTWMIC - Copyright (C) 2019 Ilya Pavlov
# App::DTWMIC is licensed under the
# GNU Lesser General Public License v2.1

use strict;
use warnings;

use utf8;

use List::Util qw(max);
use Getopt::Long;

use App::DTWMIC;

use YAML::Tiny;
use Udev::FFI;
#use Gtk3 '-init';


use constant {
    DEVICE_INPUT_OTHER          => 0,
    DEVICE_INPUT_MOUSE          => 1,
    DEVICE_INPUT_TOUCHPAD       => 2,
    DEVICE_INPUT_TABLET         => 3,
    DEVICE_INPUT_TOUCHSCREEN    => 4,

    SYNCLIENT_LOCATIONS => [
        '/usr/bin/synclient'
    ]
};


my $config;



sub _get_device_type {
    my $device = shift;

    my $id_input_mouse          = $device->get_property_value('ID_INPUT_MOUSE');
    my $id_input_touchpad       = $device->get_property_value('ID_INPUT_TOUCHPAD');
    my $id_input_tablet         = $device->get_property_value('ID_INPUT_TABLET');
    my $id_input_touchscreen    = $device->get_property_value('ID_INPUT_TOUCHSCREEN');

    if (defined($id_input_mouse) && $id_input_mouse eq '1') {
        # ID_INPUT_MOUSE: Touchscreens and tablets have this flag as
        # well, since by the type of events they can produce they act as
        # a mouse.
        # https://askubuntu.com/questions/520359/how-to-detect-touchscreen-devices-from-a-script

        if (defined($id_input_touchpad) && $id_input_touchpad eq '1') {
            return DEVICE_INPUT_TOUCHPAD;
        }
        elsif (defined($id_input_tablet) && $id_input_tablet eq '1') {
            return DEVICE_INPUT_TABLET;
        }
        elsif (defined($id_input_touchscreen) && $id_input_touchscreen eq '1') {
            return DEVICE_INPUT_TOUCHSCREEN;
        }

        return DEVICE_INPUT_MOUSE;
    }
    elsif (defined($id_input_touchpad) && $id_input_touchpad eq '1') {
        return DEVICE_INPUT_TOUCHPAD;
    }
    elsif (defined($id_input_tablet) && $id_input_tablet eq '1') {
        return DEVICE_INPUT_TABLET;
    }
    elsif (defined($id_input_touchscreen) && $id_input_touchscreen eq '1') {
        return DEVICE_INPUT_TOUCHSCREEN;
    }


    return DEVICE_INPUT_OTHER;
}



sub _get_device_data {
    my $device = shift;

    my $parent = $device->get_parent_with_subsystem_devtype('input');

    return {}
        unless defined $parent;

    my $device_type = _get_device_type($device);

    my $name = $parent->get_property_value('NAME');
    $name = 'unknown'
        unless defined $name;

    return {
        type    => $device_type,
        sysname => $parent->get_sysname(),
        data    => {
            name    => $name
        }
    };
}



sub _enable_touchpads($$) {
    my $toushpads = shift;
    my $is_enable = shift;

    for (keys(%$toushpads)) {
        my $cmd = $is_enable ?$config->{touchpadon} :$config->{touchpadoff};
        my $tname = $toushpads->{$_}{name};
        $cmd =~ s/(?<=[^\\])\$TOUCHPAD_NAME(?=[^a-zA-Z_\d])/$tname/;

        system($cmd);
    }
}



my $config_path;
my $list;

{
    my $help;
    my $version;
    #my $configure;

    GetOptions(
        'h|help' => \$help,
        'v|version' => \$version,
        'l|list' => \$list,
        'f|config-file=s' => \$config_path,
    #    'c|configure' => \$configure
    )
    or do { $help = 1 };

    if (defined($help)) {
        print q{Usage:
    dtwmic [OPTIONS]
    dtwmic f|config-file=CUSTOM_CONFIG_FILE_PATH

    h | help    - show help
    v | version - show dtwmic version
    l | list    - list mouse/touchpad devices

    a default config file is created on the first run in ~/.config/dtwmic/config.yml (if not exists)
};
        exit(0);
    }

    if (defined($version)) {
        print "dtwmic version is $App::DTWMIC::VERSION\n";
        exit(0);
    }
}


my $udev = Udev::FFI->new() or
    die("Can't initialize Udev::FFI library: $@");

my $enumerate = $udev->new_enumerate() or
    die("Can't create enumerate context: $@");

$enumerate->add_match_subsystem('input') or
    die("Can't add match subsystem: $!");


my $mouse_devices = {};
my $touchpads = {};
my $tablets = {};

if (defined($list)) {
    $enumerate->scan_devices() or
        die("Can't scan devices: $!");

    my $devices = $enumerate->get_list_entries() or
        die "Can't get devices: $!";

    my @lengths;

    for (keys(%$devices)) {
        if (defined(my $device = $udev->new_device_from_syspath($_))) {
            my $device_data = _get_device_data($device);
            next
                unless %$device_data;

            my $p_devices;
            if (DEVICE_INPUT_MOUSE == $device_data->{type}) {
                $p_devices = $mouse_devices;
            }
            elsif (DEVICE_INPUT_TOUCHPAD == $device_data->{type}) {
                $p_devices = $touchpads;
            }
            elsif (DEVICE_INPUT_TABLET == $device_data->{type}) {
                $p_devices = $tablets;
            }
            else {
                next;
            }

            $p_devices->{ $device_data->{sysname} } = $device_data->{data};

            push(@lengths, length( $device_data->{data}{name} ));
        }
    }


    my $max_str_length = max(@lengths) + 8;
    $max_str_length -= ($max_str_length % 4);

    print "MOUSE DEVICES:\n";
    for (keys(%$mouse_devices)) {
        print '  '.$mouse_devices->{$_}{name}.(' ' x ($max_str_length - length($mouse_devices->{$_}{name}))).'sysname: '.$_."\n";
    }

    print "TOUCHPAD DEVICES:\n";
    for (keys(%$touchpads)) {
        print '  '.$touchpads->{$_}{name}.(' ' x ($max_str_length - length($touchpads->{$_}{name}))).'sysname: '.$_."\n";
    }


    exit(0);
}


if (!defined($config_path)) {
    require File::HomeDir;

    my $config_dir = File::HomeDir->my_home.'/.config/dtwmic/';
    if (!-d $config_dir) {
        require File::Path;
        File::Path->import('make_path');

        make_path($config_dir, {error => \my $err});
        die("Can't create directory '$config_dir': ".$err->[0]."\n")
            if (@$err);
    }

    $config_path = $config_dir.'config.yml';

    if (!-f $config_path) {
        require File::Which;
        File::Which->import();

        my $synclient_path = which('synclient');
        if(!defined($synclient_path)) {
            for(@{ +SYNCLIENT_LOCATIONS }) {
                if(-f) {
                    $synclient_path = $_;
                    last;
                }
            }
        }

        my $fh;
        open($fh, '>', $config_path) or
            die("Can't create default config file $config_path: $!\n");

        if (defined($synclient_path)) {
            print $fh qq{# \$TOUCHPAD_NAME variable available here
touchpadon: $synclient_path touchpadoff=0
touchpadoff: $synclient_path touchpadoff=1
};
        }
        else {
            print $fh qq{# \$TOUCHPAD_NAME variable available here
touchpadon: xinput set-prop \$TOUCHPAD_NAME "Device Enabled" 1
touchpadoff: xinput set-prop \$TOUCHPAD_NAME "Device Enabled" 0
};
        }

        close($fh);
    }
}


eval {
    $config = YAML::Tiny->read($config_path)->[0];
};
if ($@) {
    die("Can't load config file $config_path: $@\n");
}


my $monitor = $udev->new_monitor() or
    die "Can't create udev monitor: $@";

$monitor->filter_by_subsystem_devtype('input') or
    die "Can't add filter to udev monitor: $!";

$monitor->start() or
    die "Can't start udev monitor: $!";


$enumerate->scan_devices() or
    die("Can't scan devices: $!");

my $devices = $enumerate->get_list_entries() or
    die "Can't get devices: $!";


for (keys(%$devices)) {
    if (defined(my $device = $udev->new_device_from_syspath($_))) {
        my $device_data = _get_device_data($device);
        next
            unless %$device_data;

        my $p_devices;
        if (DEVICE_INPUT_MOUSE == $device_data->{type}) {
            $p_devices = $mouse_devices;
        }
        elsif (DEVICE_INPUT_TOUCHPAD == $device_data->{type}) {
            $p_devices = $touchpads;
        }
        elsif (DEVICE_INPUT_TABLET == $device_data->{type}) {
            $p_devices = $tablets;
        }
        else {
            next;
        }

        $p_devices->{ $device_data->{sysname} } = $device_data->{data};
    }
}


if (%$mouse_devices) {
    _enable_touchpads($touchpads, 0);
}
else {
    _enable_touchpads($touchpads, 1);
}


my $has_mouse_devices = %$mouse_devices ?1 :0;

for (;;) {
    my $device = $monitor->poll();
    my $action = $device->get_action();

    my $device_data = _get_device_data($device);
    next
        unless %$device_data;

    my $has_mouse_devices_old;

    my $p_devices;
    if (DEVICE_INPUT_MOUSE == $device_data->{type}) {
        $p_devices = $mouse_devices;
    }
    elsif (DEVICE_INPUT_TOUCHPAD == $device_data->{type}) {
        $p_devices = $touchpads;
    }
    elsif (DEVICE_INPUT_TABLET == $device_data->{type}) {
        $p_devices = $tablets;
    }
    else {
        next;
    }


    if ($action eq 'add') {
        $p_devices->{ $device_data->{sysname} } = $device_data->{data};

        if (0 == $has_mouse_devices && %$mouse_devices) {
            _enable_touchpads($touchpads, 0);
            $has_mouse_devices = 1;
        }
    }
    elsif ($action eq 'remove') {
        delete $p_devices->{ $device_data->{sysname} };

        if (0 != $has_mouse_devices && !%$mouse_devices) {
            _enable_touchpads($touchpads, 1);
            $has_mouse_devices = 0;
        }
    }
}



__END__

=encoding utf-8

=head1 NAME

dtwmic - disable touchpad when a mouse is connected

=head1 INSTALL

    curl -L https://cpanmin.us | perl - App::DTWMIC

Note: you may wish to install a package like libffi-platypus-perl on your distro
to fast installation

=head1 SYNOPSIS

    dtwmic [OPTIONS]
    dtwmic f | config-file=CUSTOM_CONFIG_FILE_PATH
    
    h | help    - show help
    v | version - show dtwmic version
    l | list    - list mouse/touchpad devices

=head1 DESCRIPTION

Add dtwmic to your X window manager autostart.

A default configuration file is created on the first run in
~/.config/dtwmic/config.yml (if not exists).

You need synclient or xinput by default otherwise edit the config.

=head1 BUGS

Please report any bugs through the web interface at
L<https://github.com/Ilya33/App-DTWMIC/issues>. Patches are always welcome.

=head1 AUTHOR

Ilya Pavlov E<lt>ilux@cpan.orgE<gt>

=head1 COPYRIGHT

Copyright 2019- Ilya Pavlov

=head1 LICENSE

GNU Lesser General Public License v2.1

=cut