Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Unable to capture mouse events in Win32::Console

by fireblood (Scribe)
on Apr 25, 2022 at 19:48 UTC ( #11143285=perlquestion: print w/replies, xml ) Need Help??

fireblood has asked for the wisdom of the Perl Monks concerning the following question:

Dear wise ones,

I've been trying to capture mouse events using Win32::Console and after having done an advanced Google search on perlmonks, have not been able to find an answer. My code is the following:

# https://docs.microsoft.com/en-us/windows/console/reading-input-buf +fer-events use strict; use warnings; use Win32; use Win32::Console; my $console; my @console_event; unless ($console = new Win32::Console STD_INPUT_HANDLE) { print STDERR "\nSomething has gone wrong with the Win32::Conso +le constructor: $!\n\n"; die; } $console -> Alloc (); $console -> Mode (ENABLE_MOUSE_INPUT); print "Perl version $^V running on ", join (" ", Win32::GetOSName), ". +\n\n"; print "Your mouse has ", $console->MouseButtons(), " buttons.\n"; my $counter = 0; while ($counter++ < 15) { if ($console -> GetEvents ()) { @console_event = $console -> Input (); print "A console event has been detected. Its attribu +tes are the following:\n\n"; print "$_\n" for @console_event; print "\nGood job.\n"; exit; # exit the program, do not fall through to t +hat final print instruction } else { sleep 1; } } print "\nThe counter has reached $counter. Your input was not detected +.\n"; print "Better luck after seeking the legendary wisdom of the Perl monk +s.\n";

The initial output from the above code is

Perl version v5.32.0 running on Win10 Build 18363 (64-bit). Your mouse has 16 buttons.
While this code is running, if I press on any key, I get the following output:
A console event has been detected. Its attributes are the following: 1 1 1 75 37 107 32 Good job.

The value of "1" in the first array element indicates that the event type is keyboard input. When mouse input is captured, the first array element should have a value of "2". However, while this program is running, I can click on the console window endlessly and the mouse click is not detected. I end up falling through to the final consolation message after the end of looping.

What am I missing in how I'm setting this up to enable capturing of mouse events?

Thank you.

Replies are listed 'Best First'.
Re: Unable to capture mouse events in Win32::Console
by Anonymous Monk on Apr 25, 2022 at 21:49 UTC

      Hi Anonymous, thanks for replying so quickly. Actually, I had seen that sample code previously, and had downloaded and run it, and found that although it displayed amazing graphics, it too did not respond to my mouse clicks. I also noticed that it did not have a $console -> Alloc () instruction anywhere, which I initially thought might be why neither it nor my program responded to mouse events, because I did not have a $console -> Alloc either. However, when I added a $console -> Alloc () instruction to my code, the window in which my program was running would instantly vanish as soon as the $console -> Alloc instruction was reached. So I fiddled with $console -> Free () with it for a while, to no avail, and finally removed the $console -> Alloc instruction again.

      I have now revised my code to eliminate the 1-second sleep. By commenting one instruction or not, I can make it sleep only a hundredth of a second or not sleep at all, with the results being the same, viz. no mouse events are detected. This is the revised code:

      use strict; use warnings; use Time::HiRes qw (usleep); use Win32; use Win32::Console; my $console; my @console_event; undef $/; unless ($console = new Win32::Console STD_INPUT_HANDLE) { print STDERR "\nSomething has gone wrong with the Win32::Conso +le constructor: $!\n\n"; die; } $console -> Mode (ENABLE_MOUSE_INPUT); print "Perl version $^V running on ", join (" ", Win32::GetOSName), ". +\n\n"; print "Your mouse has ", $console->MouseButtons(), " buttons.\n\n"; print "Go ahead, make my day ...\n\n"; my $start_time = time (); my $when_to_stop_waiting = $start_time + 15; while (time () < $when_to_stop_waiting) { if ($console -> GetEvents ()) { @console_event = $console -> Input (); print "A console event has been detected. Its attribu +tes are the following:\n\n"; print "$_\n" for @console_event; print "\nGood job.\n"; exit; # exit the program, do not fall through to t +hat final print instruction } else { usleep 100; # sleep for 10 milliseconds, i.e. a +hundredth of a second, to prevent # this program from getting into a r +eally tight loop and crowding out # everyone else, but even without th +is usleep instruction the mouse # clicks are still not detected } } print "The time to stop waiting has been reached. Your input was not d +etected.\n"; print "Better luck after seeking the wisdom of the monks.\n";

      When I run this revised version and press any key, the following is generated:

      Go ahead, make my day ... A console event has been detected. Its attributes are the following: 1 1 1 70 33 102 32 Good job.

      But as before, I can click on the mouse all night, and this is what is generated:

      H:\sandbox>perl capture_mouse_events.pl Perl version v5.32.0 running on Win10 Build 18363 (64-bit). Your mouse has 16 buttons. Go ahead, make my day ... The time to stop waiting has been reached. Your input was not detected +. Better luck after seeking the wisdom of the monks. H:\sandbox>

      I've thought about re-writing my application in Win32::GUI but it would involve writing a lot more code creating and managing the window entirely within the program, not just hooking into a pre-existing console as with Win32::Console.

        replacing your Mode call with the following works for me.

        my $save = $console-> Mode; END { $console-> Mode( $save )}; $console-> Mode(( $save | 0x0010 ) & ~0x0040 ); # +MOUSE, -QUICK_EDIT

        Edit. Looks like either ENABLE_INSERT_MODE or ENABLE_EXTENDED_FLAGS are necessary in the mask (i.e. passing 0x30 or 0x90 as argument would work). Without them console continues to behave as if ENABLE_QUICK_EDIT_MODE is still on i.e. mouse selects text.

      Interestingly enough, this sample.pl does not work as advertised on my Windows 10 machine.
      When it starts, "TEST SUITE" is flashing. It says, "Press any key or mouse button to proceed". However, no mouse click will work, although pressing any other key will work.

      I believe that the 1 second sleep idea is a "red herring" -> doesn't matter. All that means is that it will take at most one second for the keyboard event to be seen. A normal assumption would be that if you don't poll fast enough, you miss a character because apparently (not sure) that the event stack is only one character deep?

      I did play around with the Properties of my standard command window. I turned off any of the normal fancy CTL-V or CTL-C off. But I haven't found the "magic formula" yet. In my testing with short programs, I am seeing that a right click causes the following program to "hang". When the program starts, hitting a normal key produces expected results, but a mouse click gets you nowhere!

      I have seen other test programs fail to see the Right-Click. When the test program exits, I get the normal CTL-V behavior. Somehow the Mouse Click is not getting to the running test program and is buffered and presented to the command shell when the test program exits. The correct voo-doo for Console should be able to get this character (mouse click).

      I am curious as to what others find. The sample code is complex and I am just trying to find a simple subset that works. This does not.

      # https://docs.microsoft.com/en-us/windows/console/reading-input-buffe +r-events\ # reference Perl Monks: https://www.perlmonks.org/?node_id=1212227 use strict; use warnings; use Win32::Console; my $OUT = new Win32::Console(STD_OUTPUT_HANDLE); my $IN = new Win32::Console(STD_INPUT_HANDLE); $OUT->Write("Perl version $^V \n"); $OUT->Write("more text could go here\n\n"); my $n_buttons = $IN->MouseButtons(); $OUT->Write("Your mouse has $n_buttons buttons\n"); $IN->Mode(ENABLE_MOUSE_INPUT); my $counter = 0; while ($counter++ < 5) #5x time out for testing... { if (my @console_events = $IN -> Input ()) { $OUT->Write ("calling input method!\n"); # my @console_events = $IN -> Input (); ##not needed goof (COR +RECTION) $OUT->Write ("An Event was detected! number= ".@console_events. +"\n"); foreach my $event (@console_events) { $OUT->Write("event: $event\n"); } exit; # exit the program, do not fall through to that final + print instruction } else { sleep 1; #should be fine to poll 1x per second for testing... } } $OUT->Write("Timed out! Bummer!\n");
        I played briefly with Marshall's adaptation of the script (though I upped to 60 instead of 5 loops, and before the exit I added a sleep(10);).

        If I run cmd.exe, and from there run perl pm.pl, when I try to do any mouse, it is consumed by my cmd.exe > properties > options, so the script doesn't show any events. If I go to cmd.exe > properties > options, and turn off all the edit and text selection options, then run the script, it still grabs my mouse clicks -- and when I look, I see that it turned back on Quick Edit Mode and Insert Mode! If I do the sequence of run the script, then turn off all the edit options, if I click or wiggle, I do see a mouse event (#2):

        C:\Users\peter.jones\Downloads\TempData\perl>perl pm.pl Perl version v5.30.0 more text could go here Your mouse has 5 buttons calling input method! An Event was detected! number= 6 event: 2 event: 42 event: 48 event: 1 event: 32 event: 0

        Similarly, if I run from Explorer using my double-click association, so that it opens a new cmd.exe window (which doesn't inherit my default options with edit options enabled), I get events. (This was the reason I added the sleep(10): so that the created cmd.exe window wouldn't go away immediately after processing the mouse event(s).

        update: As an aside, if I add the mode setting from vr's post (which I hadn't read yet when I created this post), modified to use Marshall's $IN console, then I find I don't need to futz with the cmd.exe properties manually. Thanks!
        my $save = $IN-> Mode; END { $IN-> Mode( $save )}; $IN-> Mode(( $save | 0x0010 ) & ~0x0040 ); # +MOUSE, -QUICK_EDIT

        When looking at the adapted code, I have a nitpick and a query.

        Nitpick: (this originated in fireblood's original): per Indirect Object Syntax, one should avoid the indirect-object style new Win32::Console(...) syntax, and instead use Win32::Console->new(...) syntax (or even the Win32::Console::->new(...) syntax; there are some circumstances where perl can interpret it ambiguously without both the :: and ->). It's not critical in this example, and I know that Win32::Console still recommends the old notation, but one can learn and use best practices even when the documentation/examples don't use it.

        Query to Marshall: why are you throwing away the first @console_events ? You are actually getting two events, and only reporting the first. I can see this if I change the "calling input method!" print to $OUT->Write ("calling input method!" . @console_events . "(@console_events)\n");, at which point the above in-cmd.exe example becomes

        C:\Users\peter.jones\Downloads\TempData\perl>perl pm.pl Perl version v5.30.0 more text could go here Your mouse has 5 buttons calling input method!6(2 42 47 0 32 1) An Event was detected! number= 6 event: 2 event: 42 event: 48 event: 1 event: 32 event: 0

        For the if condition, did you really want to Win32::Console#PeekInput instead, or stick with Win32::Console#GetEvents as shown in the original, so that it polls whether there was an event, but doesn't remove the event from the queue? Or was there a specific reason for throwing away the first event?

        Regarding the two mys, I am actually surprised there isn't a warning, because the my @console_events in the if condition is then masked by the my @console_events inside the block, but the use warnings isn't saying anything about it. (which it normally does, like in perl -le "use strict; use warnings; my $x; my $x" (warning: windows quote syntax, given the windows context of Win32::Console))

        TL;DR

        Back to the main point: if your cmd.exe window is not trapping the mouse inputs with its edit options, Win32::Console can see mouse events. But sometimes, when you think it won't trap, cmd.exe re-enables at least a couple of the traps, so you have to re-disable them after the program has started (edit: or by using the Mode command).

        Hi Marshall, thanks for modifying it and testing it with the two different consoles. That looked promising. Also, after writing what I wrote about the sample code and my own code both not working, my next thought was that if the problem appears in two different programs written by two different people, then maybe the problem was in my mouse, and that I should try re-running it using a better/newer mouse. But since you reproduced the same problem on your academic computer that I was having, I'm thinking now that the mouse is not the problem.

        But both of us are running Windows 10, and the Win32::Console module has been around for a lot longer than Windows 10 has. I wonder if it's a Windows 10 problem? An Internet search of Win32::Console reveals that other languages have versions of it as well. I wonder if they are encountering problems when running programs written in those other languages on Windows 10 machines? No way to pin that down since this is a Perl forum. I did notice that one discussion about this module on Perl in particular -- http://computer-programming-forum.com/53-perl/1fb16f663d489dbe.htm -- goes back to June of 1902, so maybe it is only a Windows 10 problem. But that date makes me even more impressed with the magnitude of what Larry has accomplished beyond just the brilliant design of Perl.

Re: Unable to capture mouse events in Win32::Console
by fireblood (Scribe) on Apr 28, 2022 at 13:10 UTC

    Hi all, the following is what I put together based on all of the feedback that you've provided:

    # This program displays details about console events that occur in +the window in # wnich this program is running. This program creates a file calle +d "event_log.txt" # in the current directory. # # References: # # https://docs.microsoft.com/en-us/windows/console/reading-i +nput-buffer-events # # Definitions and values of constants such as ENABLE_* are avail +able at the # following URL: # # https://docs.microsoft.com/en-us/windows/console/setconsol +emode # BEGIN { select STDERR; $|++; # do not buffer STDERR select STDOUT; $|++; # do not buffer STDOUT system ("cls"); print "\n\n\tInitializing the environment ...\n\n\t"; }; use strict; use warnings; # The following package provides access to basic information about t +he runtime environment # such as the release of Windows under which this program is running use Win32; # The following package provides access to console events such as ke +y presses and mouse clicks use Win32::Console; my $console; my @console_event; my @info; my $log_file = "event_log.txt"; my $fh_log_file; unless (open ($fh_log_file, ">", $log_file)) { print STDERR "\nCould not open $log_file: $!\n\n"; die; } unless ($console = Win32::Console -> new (STD_INPUT_HANDLE)) { print STDERR "\nSomething has gone wrong with the Win32::Conso +le constructor: $!\n\n"; die; } $console -> Flush (); my $starting_console_mode_setting = $console -> Mode; END {$console -> Mode ($starting_console_mode_setting)}; # The following instruction is based on expertise provided by vr # at https://www.perlmonks.org/?node_id=11143316 $console -> Mode ((($starting_console_mode_setting | 0x0010 ) & ~0x004 +0 ) & ~ENABLE_PROCESSED_INPUT); print "Perl version $^V running on ", join (" ", Win32::GetOSName), ". +\n\n"; print $fh_log_file "Perl version $^V running on ", join (" ", Win32::G +etOSName), ".\n\n"; # The following is to demonstrate that the $console environment has +been set up print "Your mouse has ", $console->MouseButtons(), " buttons.\n\n"; print $fh_log_file "Your mouse has ", $console->MouseButtons(), " butt +ons.\n\n"; print "Enter keyboard activity or mouse events ...\n\n"; print $fh_log_file "Enter keyboard activity or mouse events ...\n\n"; my $start_time = time (); my $when_to_stop_listening = $start_time + 15; while (time () < $when_to_stop_listening) { if ($console -> GetEvents ()) # This may be unnecessary, c +an simply invoke Input () # and check if it has return +ed anything { @console_event = $console -> Input (); print "A console event has been detected. Its attribu +tes are the following:\n\n"; print $fh_log_file "A console event has been detected. + Its attributes are the following:\n\n"; print "Time of event: ", time (), "\n"; print $fh_log_file "Time of event: ", time (), "\ +n"; print "Type of event: "; print $fh_log_file "Type of event: "; if (defined ($console_event [0])) { if ($console_event [0] == 1) { print "Keyboard event.\n"; print $fh_log_file "Keyboard event.\n" +; print "Key down: ", $console +_event[1], "\n"; print $fh_log_file "Key down: + ", $console_event[1], "\n"; print "Repeat count: ", $console +_event[2], "\n"; print $fh_log_file "Repeat count: + ", $console_event[2], "\n"; print "Virtual key code: ", $console +_event[3], "\n"; print $fh_log_file "Virtual key code: + ", $console_event[3], "\n"; print "Virtual scan code: ", $console +_event[4], "\n"; print $fh_log_file "Virtual scan code: + ", $console_event[4], "\n"; print "ASCII code: ", $console +_event[5], "\n"; print $fh_log_file "ASCII code: + ", $console_event[5], "\n"; print "ASCII code letter: ", chr $co +nsole_event[5], "\n"; print $fh_log_file "ASCII code letter: + ", chr $console_event[5], "\n"; print "Control key state: ", $console +_event[6], "\n"; print $fh_log_file "Control key state: + ", $console_event[6], "\n"; } elsif ($console_event [0] == 2) { print "Mouse event.\n"; print $fh_log_file "Mouse event.\n"; print "Mouse X coord: ", $console +_event[1], "\n"; print $fh_log_file "Mouse X coord: + ", $console_event[1], "\n"; print "Mouse Y coord: ", $console +_event[2], "\n"; print $fh_log_file "Mouse Y coord: + ", $console_event[2], "\n"; print "Mouse button state: ", $console +_event[3], "\n"; print $fh_log_file "Mouse button state +: ", $console_event[3], "\n"; print "Control key state: ", $console +_event[4], "\n"; print $fh_log_file "Control key state: + ", $console_event[4], "\n"; print "Event flags: ", $console +_event[5], "\n"; print $fh_log_file "Event flags: + ", $console_event[5], "\n"; } else { print "Unknown type \"$console_event[0 +]\".\n"; print $fh_log_file "Unknown type \"$co +nsole_event[0]\".\n"; } print "\nAdditional information:\n"; print $fh_log_file "A\ndditional information:\ +n"; @info = $console -> Info (); # @info is supposed to contain the following + elements: # # $info[0]: columns (X size) of the consol +e buffer. # $info[1]: rows (Y size) of the console b +uffer. # $info[2]: current column (X position) of + the cursor. # $info[3]: current row (Y position) of th +e cursor. # $info[4]: current attribute used for Wri +te. # $info[5]: left column (X of the starting + point) of the current console window. # $info[6]: top row (Y of the starting poi +nt) of the current console window. # $info[7]: right column (X of the final p +oint) of the current console window. # $info[8]: bottom row (Y of the final poi +nt) of the current console window. # $info[9]: maximum number of columns for +the console window, given the current buffer size, font and the scree +n size. # $info[10]: maximum number of rows for the + console window, given the current buffer size, font and the screen s +ize. # # but look at what is actually happening: print "The size of \@info is ", scalar @info, +".\n"; print $fh_log_file "The size of \@info is ", s +calar @info, ".\n"; print "The value of \$info[0] is \"", $info[0] +, "\".\n"; print $fh_log_file "The value of \$info[0] is +\"", $info[0], "\".\n"; print "\n"; print $fh_log_file "\n"; } else { print "undefined event type.\n"; print $fh_log_file "undefined event type.\n"; } print "\n"; print $fh_log_file "\n"; } } print "The time to stop listening has been reached. Program is ending +.\n\n"; print $fh_log_file "The time to stop listening has been reached. Prog +ram is ending.\n\n";

    When I run this in my environment, I find that $console -> Info () returns only a one-element array the single member of which is an empty string. Because some of the Info () elements are critical to my real application, I looked again at Win32::GUI and found that it can be used with non-owned windows, i.e., windows that it did not create. A quick test program revealed that it provided all of the functionality that I need for my real project. Given that Win32::Console was written to be used with what Microsoft is now calling legacy consoles, whereas Win32::GUI was written in 2017 and seems to work exactly as its documentation describes, it seems that Win32::GUI is the better way to go, at least for now. One comment that was provided was that using the TCL module is actually the best way to go in the long run.

    Thanks again for all of the very constructive feedback.

      Your while loop loops ~700k times in 15s for me, and somewhat fully loads 2 cores even without me touching anything. It's NOT the result supposed to arise from advice given.

      + Not an expert on these, Info seems to produce valid list if STD_OUTPUT_HANDLE was used to create an object.

      + Win32::GUI was not "written in 2017", is built on same-time as Win32::Console i.e. dated and legacy some-middle-90s MS API.

        somewhat fully loads 2 cores even without me touching anything

        If I do a loop (similar to what fireblood used), then it does seem to hog at least one CPU if it can:

        use strict; use warnings; use Win32::Console; use Time::HiRes qw(sleep); my $OUT = new Win32::Console(STD_OUTPUT_HANDLE); my $IN = new Win32::Console(STD_INPUT_HANDLE); my $save = $IN-> Mode; END { $IN-> Mode( $save )}; $IN-> Mode(( $save | 0x0010 ) & ~0x0040 ); # +MOUSE, -QUICK_EDIT $OUT->Write("Perl version $^V \n"); $OUT->Write("more text could go here\n\n"); my $n_buttons = $IN->MouseButtons(); $OUT->Write("Your mouse has $n_buttons buttons\n"); $IN->Mode(ENABLE_MOUSE_INPUT); my @info = $IN->Info(); $OUT->Write(sprintf "IN info:(%s)\n", "@info"); @info = $OUT->Info(); $OUT->Write(sprintf "OUT info:(%s)\n", "@info"); my $counter = 0; my %MouseFlags = ( FIRST_CLICK => 0, MOUSE_MOVED => 1, DOUBLE_CLICK => 2, MOUSE_WHEELED => 4, MOUSE_HWHEELED => 8, ); @MouseFlags{values %MouseFlags} = keys %MouseFlags; # reverse mapping while(1) { if($IN->GetEvents) { @console_events = $IN->Input(); local $" = ","; if( 2 == ($console_events[0]//0) ) { $OUT->Write(sprintf "Mouse (@console_events): (%d,%d) Btn= +%d Flag=%d:%s\r", @console_events[1..3], $console_events[5], $MouseFl +ags{$console_events[5]}); # I switched to \r so that the mouse events + wouldn't flood the screen, which was causing the mouse y coordinate +reported to be confusing, since it was scrolling the window, and it's + mouse-coordinate relative to the TOP of the console history } elsif ( 1 == ($console_events[0]//0) ) { $OUT->Write("Keyboard (@console_events)\n", ); exit if 27 == ($console_events[3]//0); # ESC = 27 } else { $OUT->Write("Unknown (@console_events)\n"); } } }

        However, if I add a 10ms sleep in the else condition, then the CPU usage drops to virtually nothing, at least for me:

        ... # the code before the while loop is identical to the program listi +ng above use Time::HiRes qw/sleep/ while(1) { if($IN->GetEvents) { @console_events = $IN->Input(); ... } else { sleep 0.01; } }

        Alternately, getting rid of the GetEvents also seems to not peg the CPU, at least for me:

        ... while(1) { @console_events = $IN->Input(); ... }

        Info seems to produce valid list if STD_OUTPUT_HANDLE was used

        I agree with the Anonymous Monk's assessment that STD_OUTPUT_HANDLE will give valid ->Info().

        use strict; use warnings; use Win32::Console; my $OUT = new Win32::Console(STD_OUTPUT_HANDLE); my $IN = new Win32::Console(STD_INPUT_HANDLE); my @info = $IN->Info(); $OUT->Write(sprintf "IN info:(%s)\n", "@info"); @info = $OUT->Info(); $OUT->Write(sprintf "OUT info:(%s)\n", "@info"); __END__ IN info:() OUT info:(150 9001 0 15 7 0 0 149 29 150 71)

        is built on same-time as Win32::Console i.e. dated and legacy some-middle-90s MS API.

        I partially agree. Win32::GUI is built on the Win32 API, which has its roots in the 90s MS API -- but so are any Win32 API application. But MS specifically documents the Console+Mouse related calls as not recommended (see, for example, Mouse Event Record Structure, which is where the ->Input() results come from for Mouse events), whereas MS doesn't mark most of the Win32 API (like the Win32 controls, which is where Win32::GUI gets all of its pushbuttons, windows, etc) with similar verbiage

        And regarding 2013 vs 2017: both are still too long since they were maintained and had reported bugs fixed: Win32::Console hasn't been updated since 2013; Win32::GUI hasn't been updated since 2017; both could stand some improvements, even in their handling of the underlying Win32 API, and especially in their documentation.

        Yes, you are correct, I should have said that the last update to Win32::GUI was in 2017, not that it was written in 2017.
Re: Unable to capture mouse events in Win32::Console
by fireblood (Scribe) on Apr 29, 2022 at 12:41 UTC

    Thanks again for the feedback. My latest attempt is the following. I've added the sleep function call back in again to see if it will solve the excessive CPU time problem.

    # This program displays details about console events that occur in +the window in # wnich this program is running. This program creates a file calle +d "event_log.txt" # in the current directory. # # References: # # https://docs.microsoft.com/en-us/windows/console/reading-i +nput-buffer-events # # Definitions and values of constants such as ENABLE_* are avail +able at the # following URL: # # https://docs.microsoft.com/en-us/windows/console/setconsol +emode # BEGIN { select STDERR; $|++; # do not buffer STDERR select STDOUT; $|++; # do not buffer STDOUT system ("cls"); print "\n\n\tInitializing the environment ...\n\n\t"; }; use strict; use warnings; use Time::HiRes qw (sleep); # The following package provides access to basic information about t +he runtime environment # such as the release of Windows under which this program is running use Win32; # The following package provides access to console events such as ke +y presses and mouse clicks use Win32::Console; my ($IN, $OUT); my @console_event; my @info; my $log_file = "event_log.txt"; my $fh_log_file; unless (open ($fh_log_file, ">", $log_file)) { print STDERR "\nCould not open $log_file: $!\n\n"; die; } unless ($IN = Win32::Console -> new (STD_INPUT_HANDLE)) { print STDERR "\nSomething has gone wrong with the Win32::Conso +le constructor: $!\n\n"; die; } unless ($OUT = Win32::Console -> new (STD_OUTPUT_HANDLE)) { print STDERR "\nSomething has gone wrong with the Win32::Conso +le constructor: $!\n\n"; die; } $IN -> Flush (); my $starting_console_mode_setting = $IN -> Mode; END {$IN -> Mode ($starting_console_mode_setting)}; $IN -> Mode ((($starting_console_mode_setting | 0x0010 ) & ~0x0040 ) & + ~ENABLE_PROCESSED_INPUT); print "Perl version $^V running on ", join (" ", Win32::GetOSName), ". +\n\n"; print $fh_log_file "Perl version $^V running on ", join (" ", Win32::G +etOSName), ".\n\n"; # The following is to demonstrate that the $console environment has +been set up print "Your mouse has ", $IN->MouseButtons(), " buttons.\n\n"; print $fh_log_file "Your mouse has ", $IN->MouseButtons(), " buttons.\ +n\n"; print "Enter keyboard activity or mouse events ...\n\n"; print $fh_log_file "Enter keyboard activity or mouse events ...\n\n"; my $start_time = time (); my $when_to_stop_listening = $start_time + 15; while (time () < $when_to_stop_listening) { if ($IN -> GetEvents ()) # This may be unnecessary, can si +mply invoke Input () # and check if it has return +ed anything { @console_event = $IN -> Input (); print "A console event has been detected. Its attribu +tes are the following:\n\n"; print $fh_log_file "A console event has been detected. + Its attributes are the following:\n\n"; print "Time of event: ", time (), "\n"; print $fh_log_file "Time of event: ", time (), "\ +n"; print "Type of event: "; print $fh_log_file "Type of event: "; if (defined ($console_event [0])) { if ($console_event [0] == 1) { print "Keyboard event.\n"; print $fh_log_file "Keyboard event.\n" +; print "Key down: ", $console +_event[1], "\n"; print $fh_log_file "Key down: + ", $console_event[1], "\n"; print "Repeat count: ", $console +_event[2], "\n"; print $fh_log_file "Repeat count: + ", $console_event[2], "\n"; print "Virtual key code: ", $console +_event[3], "\n"; print $fh_log_file "Virtual key code: + ", $console_event[3], "\n"; print "Virtual scan code: ", $console +_event[4], "\n"; print $fh_log_file "Virtual scan code: + ", $console_event[4], "\n"; print "ASCII code: ", $console +_event[5], "\n"; print $fh_log_file "ASCII code: + ", $console_event[5], "\n"; print "ASCII code letter: ", chr $con +sole_event[5], "\n"; print $fh_log_file "ASCII code letter: + ", chr $console_event[5], "\n"; print "Control key state: ", $console +_event[6], "\n"; print $fh_log_file "Control key state: + ", $console_event[6], "\n"; } elsif ($console_event [0] == 2) { print "Mouse event.\n"; print $fh_log_file "Mouse event.\n"; print "Mouse X coord: ", $console +_event[1], "\n"; print $fh_log_file "Mouse X coord: + ", $console_event[1], "\n"; print "Mouse Y coord: ", $console +_event[2], "\n"; print $fh_log_file "Mouse Y coord: + ", $console_event[2], "\n"; print "Mouse button state: ", $console +_event[3], "\n"; print $fh_log_file "Mouse button state +: ", $console_event[3], "\n"; print "Control key state: ", $console +_event[4], "\n"; print $fh_log_file "Control key state: + ", $console_event[4], "\n"; print "Event flags: ", $console +_event[5], "\n"; print $fh_log_file "Event flags: + ", $console_event[5], "\n"; } else { print "Unknown type \"$console_event[0 +]\".\n"; print $fh_log_file "Unknown type \"$co +nsole_event[0]\".\n"; } print "\nAdditional information as of the time + of the event:\n\n"; print $fh_log_file "\nAdditional information a +s of the time of the event:\n\n"; @info = $OUT -> Info(); # @info contains the following elements: # # $info[0]: columns (X size) of the consol +e buffer. # $info[1]: rows (Y size) of the console b +uffer. # $info[2]: current column (X position) of + the cursor. # $info[3]: current row (Y position) of th +e cursor. # $info[4]: current attribute used for Wri +te. # $info[5]: left column (X of the starting + point) of the current console window. # $info[6]: top row (Y of the starting poi +nt) of the current console window. # $info[7]: right column (X of the final p +oint) of the current console window. # $info[8]: bottom row (Y of the final poi +nt) of the current console window. # $info[9]: maximum number of columns for +the console window, given the current buffer size, font and the scree +n size. # $info[10]: maximum number of rows for the + console window, given the current buffer size, font and the screen s +ize. print "\$info[0] = ", sprintf ("%6d", $info[0 +]), "\tcolumns (X size) of the console buffer\n"; print $fh_log_file "\$info[0] = ", sprintf (" +%6d", $info[0]), "\tcolumns (X size) of the console buffer\n"; print "\$info[1] = ", sprintf ("%6d", $info[1 +]), "\trows (Y size) of the console buffer\n"; print $fh_log_file "\$info[1] = ", sprintf (" +%6d", $info[1]), "\trows (Y size) of the console buffer\n"; print "\$info[2] = ", sprintf ("%6d", $info[2 +]), "\tcurrent column (X position) of the cursor\n"; print $fh_log_file "\$info[2] = ", sprintf (" +%6d", $info[2]), "\tcurrent column (X position) of the cursor\n"; print "\$info[3] = ", sprintf ("%6d", $info[3 +]), "\tcurrent row (Y position) of the cursor\n"; print $fh_log_file "\$info[3] = ", sprintf (" +%6d", $info[3]), "\tcurrent row (Y position) of the cursor\n"; print "\$info[4] = ", sprintf ("%6d", $info[4 +]), "\tcurrent attribute used for Write\n"; print $fh_log_file "\$info[4] = ", sprintf (" +%6d", $info[4]), "\tcurrent attribute used for Write\n"; print "\$info[6] = ", sprintf ("%6d", $info[5 +]), "\tX of the starting point of the current console window\n"; print $fh_log_file "\$info[6] = ", sprintf (" +%6d", $info[5]), "\tX of the starting point of the current console wi +ndow\n"; print "\$info[6] = ", sprintf ("%6d", $info[6 +]), "\tY of the starting point of the current console window\n"; print $fh_log_file "\$info[6] = ", sprintf (" +%6d", $info[6]), "\tY of the starting point of the current console wi +ndow\n"; print "\$info[7] = ", sprintf ("%6d", $info[7 +]), "\tX of the final point of the current console window\n"; print $fh_log_file "\$info[7] = ", sprintf (" +%6d", $info[7]), "\tX of the final point of the current console windo +w\n"; print "\$info[8] = ", sprintf ("%6d", $info[8 +]), "\tY of the final point of the current console window\n"; print $fh_log_file "\$info[8] = ", sprintf (" +%6d", $info[8]), "\tY of the final point of the current console windo +w\n"; print "\$info[9] = ", sprintf ("%6d", $info[9 +]), "\tmaximum number of columns for the console window\n"; print $fh_log_file "\$info[9] = ", sprintf (" +%6d", $info[9]), "\tmaximum number of columns for the console window\ +n"; print "\$info[10] = ", sprintf ("%6d", $info[1 +0]), "\tmaximum number of rows for the console window\n"; print $fh_log_file "\$info[10] = ", sprintf (" +%6d", $info[10]), "\tmaximum number of rows for the console window\n" +; print "\n"; print $fh_log_file "\n"; } else { print "undefined event type.\n"; print $fh_log_file "undefined event type.\n"; } print "\n"; print $fh_log_file "\n"; } sleep 0.01; } print "The time to stop listening has been reached. Program is ending +.\n\n"; print $fh_log_file "The time to stop listening has been reached. Prog +ram is ending.\n\n";

      I feel partly responsible for confusion and code re-writes caused by my words that "GetEvents and sleep are not required". I don't know if you indeed tried to exclude both to find timeout never occurs if user is inactive; then restored just "peeking" into the queue (GetEvents) to detrimental effect, and finally restored the original. Maybe this timeout is crucial to your application. Maybe the above never happened, it's just the (wrong) idea of

      GetEvents returned just a single scalar ... would be more efficient

      Maybe you tried to timeout through alarm (I think it is somewhere in FAQ) to find it doesn't work with blocking I/O on Windows. Maybe I'm partially or totally wrong, you are not interested in "things async", peeking/blocking, etc. Code finally working as desired is important (which is absolutely right!), goal achieved. Well, so it's to my amusement and sleep-not-really-OK bias I found that timed-out Win32::Console::Input can be done like this:

      use strict; use warnings; use feature 'say'; use Win32::Console; use Win32::IPC 'wait_any'; use Time::HiRes 'time'; my $IN = Win32::Console-> new( STD_INPUT_HANDLE ); my $max = 15; my $count = 0; my $t = time + $max; while ( wait_any @{[ $IN ]}, 1000 * ( $t - time )) { my @ev = $IN-> Input; say "Ah ah ah, you didn't press the magic key <--- @ev"; ++ $count } say $count ? "Enough, I'm tired! $count events in $max seconds!" : "You failed to press ANY key in $max seconds! Read my FAX!!!";

      The one aspect of this that I still find puzzling is that in the @info array, all of the Y values are usually greater than the height of my console window. Do they represent Y values within the console buffer rather than within the console window? In my case the console buffer is much taller than the console window. I was not able to find an answer at https://docs.microsoft.com/en-us/windows/console/reading-input-buffer-events

        It took me a while to figure that one out as well. It's the character-based y coordinate relative to the first (top) line of text in the console, assuming your scroll bar is at the very top. So if your window is 25 lines tall but your history is 50 characters tall total, then if you scroll to the top , it will say the y is line 0, and if you scroll to the bottom, it will say the line is 49.

        When I originally had a \n in my ->Write() string, I was confused why the y coordinate would start increasing and "never" stop, even as I moved the cursor back up the screen. So I changed it to a \r instead, so the text wouldn't scroll the screen, and it suddenly made perfect sense, as I manually scrolled or intentionally moved down a line for printing (typing an ENTER, because the keyboard events still kept \n). (I also didn't realize until I did that -- though it should have been obvious, even from the x coordinate -- that the coordinates were in characters, not in pixels.)


        update: My original reply was assuming you were talking about the mouse coordinates from @console_events. Sorry I misread. But yes, the current x and current y, in the info array as well, are still relative to the top-left of the whole buffer, so the whole size and current location can be outside the window dimensions.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11143285]
Approved by johngg
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (1)
As of 2022-10-02 06:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My preferred way to holiday/vacation is:











    Results (7 votes). Check out past polls.

    Notices?