#!/usr/bin/env perl use v5.36; use strict; use X11::GUITest qw[GetMousePos MoveMouseAbs ClickMouseButton :CONST]; use Imager::Screenshot qw[screenshot]; use Carp; use Time::HiRes qw(sleep); use IO::Socket::UNIX; my $keepRunning = 1; $SIG{CHLD} = sub { print "Child exit!\n"; $keepRunning = 0; }; ################# TUNEABLES ############# my $w = 1920; # Screen width my $h = 1080; # Screen height my $thingmakerx = 55; # leftmost X position of "Thing maker" list my $idealisterx = 1380; # leftmost X position of "Idea lister" list my $liststarty = 490; # Top Y of lists my $listendy = 1048; # Bottom Y of lists my $buttonx = 186; # Power button thingy X my $buttony = 369; # Power button thingy Y my $socketpath = 'clicker.socket'; ################# TUNEABLES ############# my $lastx = -1; my $lasty = -1; unlink $socketpath; my $childpid = fork(); if(!defined($childpid)) { croak("Fork failed"); } if($childpid) { # Parent MouseClicker(); } else { ScreenLooker(); } exit(0); sub MouseClicker() { # MouseClicker is the server my $server = IO::Socket::UNIX->new( Type => SOCK_STREAM, Local => $socketpath, Listen => 1, ) or croak($!); $server->blocking(0); my $socket; while(!defined($socket)) { $socket = $server->accept(); } $socket->blocking(0); print "Screenlooker connection established!\n"; while($keepRunning) { if(!click($buttonx, $buttony)) { # USER ABORT print "USER ABORT (mouse moved manually)\n"; syswrite($socket, "ABORT\n"); last; } my $getline = readSocket($socket); if($getline ne '') { my ($itemx, $itemy) = split/\§/, $getline; print "Item click on $itemx / $itemy\n"; click($itemx, $itemy); } } print "Mail loop exit\n"; while($keepRunning) { print "Waiting for ScreenLooker to exit...\n"; sleep(0.1); } exit(0); } sub ScreenLooker() { # ScreenLooker is the client # Give server time to set up socket server sleep(1); my $socket = IO::Socket::UNIX->new( Type => SOCK_STREAM, Peer => $socketpath, ) or croak($!); $socket->blocking(0); while($keepRunning) { my $img = screenshot(); # Check idea lister my $ideasfound = findClickableStuff($img, $idealisterx, 'Idea lister', 1, $socket); if($ideasfound) { # Don't spend watts on thing maker when we still have ideas #print "Still have ideas left!\n"; } else { # Check thing maker findClickableStuff($img, $thingmakerx, 'Thing maker', 1, $socket); } my $getline = readSocket($socket); if($getline eq 'ABORT') { print "MouseClicker wants to abort, exiting ScreenLooker\n"; $keepRunning = 0; } } exit(0); } my $input = ''; sub readSocket($socket) { my $retval = ''; while(1) { my $buf; my $readok = 0; eval { sysread($socket, $buf, 1); # Read one byte $readok = 1; }; if(!$readok) { croak("Socket error"); } last if(!defined($buf) || $buf eq ''); if($buf eq "\n") { $retval = '' . $input; $input = ''; last; } $input .= $buf; } return $retval; } sub findClickableStuff($img, $startx, $name, $clickonlyone, $socket) { my $listitemsfound = 0; for(my $iy = $listendy; $iy > $liststarty; $iy--) { my $found = 0; for(my $ix = 0; $ix < 90; $ix++) { my ($r, $g, $b) = $img->getpixel(x => $ix + $startx, y => $iy)->rgba(); if($r == 255 && $g == 255 && $b == 255) { # White text (or lines): List has at least one entry $listitemsfound = 1; } #next if($r > 180); #next if($g < 180); # Search for somewhat green text (means we can affort the item) if($r < 50 && $g > 220 && $b > 150) { $found = 1; last; } } if($found) { print $name, "item found!\n"; #for(1..3) { # click($startx + 45, $iy); #} syswrite($socket, $startx + 45 . '§' . $iy . "\n"); $iy -= 50; #$listitemsfound = 1; if($clickonlyone) { last; } } } return $listitemsfound; } sub click($x, $y) { if($x != $lastx || $y != $lasty) { MoveMouseAbs($x, $y, 0); #sleep(0.5); $lastx = $x; $lasty = $y; } else { my ($mx, $my) = GetMousePos(); my $dist = abs($mx - $x) + abs($my - $y); if($dist > 10) { print("USER ABORT!\n"); return 0; } } ClickMouseButton(M_LEFT); return 1; } exit(0);