App-DTWMIC
view release on metacpan or search on metacpan
#!/usr/bin/env 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 Getopt::Long;
use App::DTWMIC;
use Udev::FFI;
use YAML::Syck;
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;
my $udev;
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;
$name =~ s/^"(.*)"$/$1/;
return {
type => $device_type,
sysname => $parent->get_sysname(),
data => {
name => $name
}
};
}
sub _enable_touchpads($$) {
my $toushpads = shift;
my $is_enable = shift;
for my $toushpad (values(%$toushpads)) {
my $cmd = $is_enable ? $config->{'touchpadon'} : $config->{'touchpadoff'};
if (ref($cmd) eq '') { # legacy
$cmd =~ s/(?<=^|[^\\])\$TOUCHPAD_NAME(?=[^a-zA-Z_\d]|$)/$toushpad->{'name'}/;
$cmd = [$cmd];
}
elsif (ref($cmd) eq 'ARRAY') {
for (@$cmd) {
s/(?<=^|[^\\])\$TOUCHPAD_NAME(?=[^a-zA-Z_\d]|$)/$toushpad->{'name'}/;
}
}
else {
return;
}
system(@$cmd);
}
}
sub _update_state() {
my $touchpads = {};
my $has_mouse_devices = 0;
my $enumerate = $udev->new_enumerate() or
die("Can't create enumerate context: $@");
$enumerate->add_match_subsystem('input') or
die("Can't add match subsystem: $!");
$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;
if (DEVICE_INPUT_TOUCHPAD == $device_data->{'type'}) {
$touchpads->{ $device_data->{'sysname'} } = $device_data->{'data'};
}
elsif (DEVICE_INPUT_MOUSE == $device_data->{'type'}) {
++$has_mouse_devices;
}
}
}
_enable_touchpads($touchpads, 0 == $has_mouse_devices ? 1 : 0);
}
my $config_path;
{
my $cmd_flag_help;
my $cmd_flag_list;
my $cmd_flag_version;
GetOptions(
'h|help' => \$cmd_flag_help,
'v|version' => \$cmd_flag_version,
'l|list' => \$cmd_flag_list,
'f|config-file=s' => \$config_path,
)
or do { $cmd_flag_help = 1 };
if (defined($cmd_flag_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($cmd_flag_version)) {
print("dtwmic version is $App::DTWMIC::VERSION\n");
exit(0);
}
$udev = Udev::FFI->new() or
die("Can't initialize Udev::FFI library: $@");
if (defined($cmd_flag_list)) {
my $enumerate = $udev->new_enumerate() or
die("Can't create enumerate context: $@");
$enumerate->add_match_subsystem('input') or
die("Can't add match subsystem: $!");
$enumerate->scan_devices() or
die("Can't scan devices: $!");
my $devices = $enumerate->get_list_entries() or
die("Can't get devices: $!");
my $input_devices = {};
my $max_str_length = 0;
my $str_length;
for (keys(%$devices)) {
if (defined(my $device = $udev->new_device_from_syspath($_))) {
my $device_data = _get_device_data($device);
next
unless %$device_data;
$input_devices->{ $device_data->{'type'} }{ $device_data->{'sysname'} } = $device_data->{'data'};
$str_length = length($device_data->{'data'}{'name'});
if ($str_length > $max_str_length) {
$max_str_length = $str_length;
}
}
}
$max_str_length += 8 - ($max_str_length % 4);
for my $dev_type (@{ [[DEVICE_INPUT_MOUSE, 'MOUSE DEVICES'], [DEVICE_INPUT_TOUCHPAD, 'TOUCHPAD DEVICES']] }) {
print("$dev_type->[1]:\n");
while (my ($sysname, $device_data) = each(%{ $input_devices->{ $dev_type->[0] } })) {
print(' '.$device_data->{'name'}.(' ' x ($max_str_length - length($device_data->{'name'}))).
"sysname: $sysname\n");
}
print("\n");
}
exit(0);
}
}
if (!defined($config_path)) {
require File::HomeDir;
my $config_dir = File::HomeDir->my_home().'/.config/dtwmic/';
unless (-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';
unless (-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)) {
$synclient_path =~ s/"/\\"/;
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]
( run in 0.779 second using v1.01-cache-2.11-cpan-13bb782fe5a )