I have a procedure in Ada which reads from a touch screen input. The code is very old and I do not have the touch screen anymore. I would like to replace the touch screen code with reading from a mouse input. Would it be simpler to write the function in C and Import it into the Ada code? The code below is the touch screen code.
HIL_NAME : STRING (1.. 10) := "/dev/touch";
procedure READ (X, Y : out INTEGER) is
type BYTE is new INTEGER range 0 .. 255;
for BYTE'SIZE use 8;
package IN_IO is new SEQUENTIAL_IO (BYTE);
use IN_IO;
type DATA_TYPE is array (2 .. 9) of BYTE;
HIL_FILE : IN_IO.FILE_TYPE;
COUNT : BYTE;
DATA : DATA_TYPE;
begin
IN_IO.OPEN (HIL_FILE, IN_FILE, HIL_NAME); -- open the touchscreen
loop
IN_IO.READ (HIL_FILE, COUNT); -- read the incoming record size
-- read the incoming record
for I in INTEGER range 2 .. BYTE'POS (COUNT) loop
IN_IO.READ (HIL_FILE, DATA (I));
end loop;
-- is this a fingerdown? overkill test.
if ((COUNT = 9) and (DATA (6) = 2#01000010#) and (DATA (9) = 142)) then
X := BYTE'POS (DATA (7)); -- pick out coordinates
Y := BYTE'POS (DATA (8));
IN_IO.CLOSE (HIL_FILE); -- close touchscreen to flush buffer
return; -- return to caller
end if;
end loop;
end READ;
It would be useful to know OS, version, compiler, window manager toolkit and version. For example I'm running Debian 10, and with Gnome 3 as my WM I can most easily access the mouse using the GTKAda toolkit. Last time I wrote code directly accessing a mouse was on DOS, in Modula-2.
However, GTKAda is not particularly easy to learn...
If you're willing to use a web browser as the GUI to your app (which also helps portability across systems ... you might even run the app on a PC but access it via a tablet or phone, giving you a touchscreen!) I recommend looking at Gnoga available from www.gnoga.com. Take a look at some of its tutorials, they should be easy to build and get you started accessing mouse and simple drawing.
EDIT
Having found the magic words (Centos, ncurses) in various comments (which you could usefully add to the question, in case there are better answers) what you are looking for is an Ada binding to ncurses such as this one. This binding is part of the official ncurses source since version 5.8 so should already be available on Centos.
It should then be a simple matter of writing a Read procedure which calls the ncurses mouse handling package, returning mouse position (scaled to an 8-bit Integer or Natural, and probably offset from the console window origin) whenever the LH button is pressed, otherwise presumably returning ... whatever an OUT parameter is initialised to, (presumably BYTE'FIRST)
Job done.
Now we can see the touch screen filename si part of the /dev/ hierarchy it may be even simpler to see if there is any mileage in finding documentation on /dev/mouse as #zerte suggests (or /dev/input/mouse[0|1] on my laptop) ... but I think ncurses will be less machine-dependent.
I have solved the problem using Ncurses. I downloaded the terminal-interface-curses and used the files to create the following procedure.
with Terminal_Interface.Curses;
use Terminal_Interface.Curses;
tmp2 : Event_Mask;
c : Key_Code;
firsttime : Bollean;
procedure READ (X1 : out Column_Position;
Y1 : Line_Position) is
begin
tmp2 := Start_Mouse (All_Events);
c:= Character'Pos ('?');
Set_Raw_Mode (SwitchOn => True);
Set_KeyPad_Mode (SwitchOn => True);
firsttime := true;
loop
if not firsttime then
if c = KeyMouse then
declare
event : Mouse_Event;
Y : Line_Position;
X : Column_Position;
Button : Mouse_Button;
State : Mouse_State;
begin
event := Get_Mouse;
Get_Event (event, Y, X, Button, State);
X1 := X;
Y1 := Y;
exit;
end;
end if;
end if;
firsttime := False;
loop
c := Get_Keystroke;
exit when c /= Key_None;
end loop;
end loop;
End_Mouse (tmp2);
end READ;
You can read the mouse by using the Linux input subsystem (as was suggested by #Zerte). See also this question on SO and some kernel documentation here and here. Reading the mouse' input doesn't seem hard (at least not on a Raspberry Pi 3 running Raspbian GNU/Linux 10). Of course, you still need to apply proper scaling and you need to figure out the device that exposes the mouse events (in my case: /dev/input/event0)
NOTE: You can find the number by inspecting the output of sudo dmesg | grep "input:". If a mouse (or other pointing device) is connected to inputX, then the events of this device will be exposed on eventX.
main.adb
with Ada.Text_IO;
with Ada.Sequential_IO;
with Interfaces.C;
procedure Main is
package C renames Interfaces.C;
use type C.unsigned_short;
use type C.int;
-- Input event codes (linux/input-event-codes.h)
EV_SYN : constant := 16#00#;
EV_KEY : constant := 16#01#;
EV_REL : constant := 16#02#;
EV_ABS : constant := 16#03#;
EV_MSC : constant := 16#04#;
BTN_MOUSE : constant := 16#110#;
BTN_LEFT : constant := 16#110#;
BTN_RIGHT : constant := 16#111#;
BTN_MIDDLE : constant := 16#112#;
REL_X : constant := 16#00#;
REL_Y : constant := 16#01#;
REL_WHEEL : constant := 16#08#;
-- Time value (sys/time.h)
subtype suseconds_t is C.long;
subtype time_t is C.long;
type timeval is record
tv_sec : time_t;
tv_usec : suseconds_t;
end record;
pragma Convention (C, timeval);
-- Input event struct (linux/input.h)
type input_event is record
time : timeval;
typ : C.unsigned_short;
code : C.unsigned_short;
value : C.int;
end record;
pragma Convention (C, input_event);
-- ... and a package instantiation for sequential IO.
package Input_Event_IO is new Ada.Sequential_IO (input_event);
use Input_Event_IO;
File : File_Type;
Event : input_event;
-- Position of the mouse and wheel.
X, Y, W : C.int := 0;
begin
Open (File, In_File, "/dev/input/event0");
-- Infinite loop, use Ctrl-C to exit.
loop
-- Wait for a new event.
Read (File, Event);
-- Process the event.
case Event.typ is
when EV_SYN =>
Ada.Text_IO.Put_Line
(X'Image & "," & Y'Image & " [" & W'Image & "]");
when EV_KEY =>
case Event.code is
when BTN_LEFT =>
Ada.Text_IO.Put_Line ("Left button.");
when BTN_MIDDLE =>
Ada.Text_IO.Put_Line ("Middle button.");
when BTN_RIGHT =>
Ada.Text_IO.Put_Line ("Right button.");
when others =>
null;
end case;
when EV_REL =>
case Event.code is
when REL_X =>
X := X + Event.value;
when REL_Y =>
Y := Y + Event.value;
when REL_WHEEL =>
W := W + Event.value;
when others =>
null;
end case;
when EV_ABS =>
case Event.code is
when REL_X =>
X := Event.value;
when REL_Y =>
Y := Event.value;
when REL_WHEEL =>
W := Event.value;
when others =>
null;
end case;
when others =>
null;
end case;
end loop;
end Main;
output (running on a headless RPi 3)
pi#raspberrypi:~/mouse $ sudo obj/main
[...]
-85, 9 [-5]
-84, 9 [-5]
-83, 9 [-5]
Left button.
-83, 9 [-5]
Left button.
-83, 9 [-5]
Left button.
-83, 9 [-5]
Left button.
-83, 9 [-5]
Right button.
-83, 9 [-5]
Right button.
-83, 9 [-5]
Middle button.
-83, 9 [-5]
Middle button.
-83, 9 [-5]
-84, 9 [-5]
^C
pi#raspberrypi:~/mouse $
Related
I am currently writing a program that, when a variable reaches a certain point, a connected light will flash on and off every second. I know the light is properly hooked up, and I know that the program to alternate between on and off works, because it did it multiple times a second. I tried adding a wait timer to slow the flashing down.
Here is the chunk of code I am trying to add:
VAR
delay : TON;
Count : INT := 0;
END_VAR
delay(IN := TRUE, PT:= T#5S);
IF NOT (delay.Q) THEN
RETURN;
END_IF;
delay(IN := FALSE);
When I add it to my code, I get the error invalid time constant.
I'm not sure if it matters too much, but I am using Schneider Electric's EcoStruxure Machine Expert to write and execute my code.
For those that wish to see the entire program, if it would help, here it is:
IF (change < 70) THEN
Light13 := FALSE;
END_IF;
IF (change >= 70) AND (change <= 90) THEN
Light13 := TRUE;
END_IF;
IF (change > 90) THEN
WHILE change > 90 DO
IF (index MOD 2 = 0) THEN
Light13 := TRUE;
END_IF;
IF (index MOD 2 <> 0) THEN
Light13 := FALSE;
END_IF;
delay(IN := TRUE, PT:= T#5s);
IF NOT (delay.Q) THEN
RETURN;
END_IF;
delay(IN := FALSE);
index := index + 1;
END_WHILE;
END_IF;
To avoid getting a repeat question to this question, Timers in PLC - Structured Text, I will again reiterate that I am getting an error using this method. Just wanted to clarify beforehand.
I am not at all set on using this way if there is a better option. Thanks for the help!
Schneider Electric's EcoStruxure Machine Expert is CoDeSys based. So you have a few options.
Use BLINK in Util library
Open library manager, search for BLINK and double click it. Now you have blink block available. Use it like this.
VAR
fbBlink: BLINK;
END_VAR
fbBlink(ENABLE := TRUE, TIMELOW := T#1s, TIMEHIGH := T#300ms, OUT => bSignal);
The advantage of this method that you can set a different times for LOW and HIGH states of your lite and use different signals. For instance, short blink once a 2 seconds error 1 and short blink every half second error 2.
Create your own BLINK function as it is suggested by #Filippo.
If you want to flash your light on and off each second you can use this code:
Declaration part:
FUNCTION_BLOCK FB_Flash
VAR_INPUT
tFlashTime : TIME;
END_VAR
VAR_OUTPUT
bSignal : BOOL;
END_VAR
VAR
fbTonDelay : TON;
END_VAR
Implementation part:
fbTonDelay(IN := NOT fbTonDelay.q, PT:= tFlashTime);
IF fbTonDelay.Q
THEN
bSignal := NOT bSignal;
END_IF
You can call it like this:
fbFlash(tFlashTime := T#1S, bSignal => bFlashLight);
Where bFlashLight is your hardware output.
Now if you want the light to flash when a special condition is fullfilled, you can do like this:
IF bSpecialCondition
THEN
fbFlash(tFlashTime := T#1S, bSignal => bFlashLight);
ELSE
bFlashLight := FALSE;
END_IF
Try to reach your goals with maximum simplicity and clarity.
I am using (learning) Tensorflow through the eager C API, or to be even more precise through a FreePascal wrapper around it.
When I want to do e.g. a matrix multiplication, I call
TFE_Execute(Op, #OutTensH, #NumOutVals, Status);
where Op.Op_Name is 'MatMul'. I have a couple of other instructions figured out, e.g. 'Transpose', 'Softmax', 'Inv', etc., but I do not have a complete list. In particular I want to get the determinant of a matrix, but cannot find it (assume it exists). I tried to find it on the web, as well as in the source on GitHub, but no success.
In Python there is tf.linalg.det, but already in C++ API I do not find it.
Could someone direct me to a place where I can find a complete list of supported operations?
Can someone tell me how to calculate the determinant with Tensorflow?
Edit: On Gaurav's request I attach a small program. As said above, it is in Pascal, and calls the C API through a wrapper. I therefore copied also the relevant part of the wrapper here (full version: https://macpgmr.github.io/). The set-up works, the "only" question is that I do not find a list of supported operations.
// A minimal program to transpose a matrix
program test;
uses
SysUtils,
TF;
var
Tensor:TTensor;
begin
Tensor:=TTensor.CreateSingle([2,1],[1.0,2.0]);
writeln('Before transpose ',Tensor.Dim[0],' x ',Tensor.Dim[1]); // 2 x 1
Tensor:=Tensor.Temp.ExecOp('Transpose',TTensor.CreateInt32([1,0]).Temp);
writeln('After transpose ',Tensor.Dim[0],' x ',Tensor.Dim[1]); // 1 x 2
FreeAndNil(Tensor);
end.
// extract from TF.pas ( (C) Phil Hess ). It basically re-packages the operation
// and calls the relevant C TFE_Execute, with the same operation name passed on:
// in our case 'Transpose'.
// I am looking for a complete list of supported operations.
function TTensor.ExecOp(const OpName : string;
Tensor2 : TTensor = nil;
Tensor3 : TTensor = nil;
Tensor4 : TTensor = nil) : TTensor;
var
Status : TF_StatusPtr;
Op : TFE_OpPtr;
NumOutVals : cint;
OutTensH : TFE_TensorHandlePtr;
begin
Result := nil;
Status := TF_NewStatus();
Op := TFE_NewOp(Context, PAnsiChar(OpName), Status);
try
if not CheckStatus(Status) then
Exit;
{Add operation input tensors}
TFE_OpAddInput(Op, TensorH, Status);
if not CheckStatus(Status) then
Exit;
if Assigned(Tensor2) then {Operation has 2nd tensor input?}
begin
TFE_OpAddInput(Op, Tensor2.TensorH, Status);
if not CheckStatus(Status) then
Exit;
end;
if Assigned(Tensor3) then {Operation has 3rd tensor input?}
begin
TFE_OpAddInput(Op, Tensor3.TensorH, Status);
if not CheckStatus(Status) then
Exit;
end;
if Assigned(Tensor4) then {Operation has 4th tensor input?}
begin
TFE_OpAddInput(Op, Tensor4.TensorH, Status);
if not CheckStatus(Status) then
Exit;
end;
{Set operation attributes}
TFE_OpSetAttrType(Op, 'T', DataType); //typically result type same as input's
if OpName = 'MatMul' then
begin
TFE_OpSetAttrBool(Op, 'transpose_a', #0); //default (False)
TFE_OpSetAttrBool(Op, 'transpose_b', #0); //default (False)
end
else if OpName = 'Transpose' then
TFE_OpSetAttrType(Op, 'Tperm', Tensor2.DataType) //permutations type
else if OpName = 'Sum' then
begin
TFE_OpSetAttrType(Op, 'Tidx', Tensor2.DataType); //reduction_indices type
TFE_OpSetAttrBool(Op, 'keep_dims', #0); //default (False)
end
else if (OpName = 'RandomUniform') or (OpName = 'RandomStandardNormal') then
begin
TFE_OpSetAttrInt(Op, 'seed', 0); //default
TFE_OpSetAttrInt(Op, 'seed2', 0); //default
TFE_OpSetAttrType(Op, 'dtype', TF_FLOAT); //for now, use this as result type
end
else if OpName = 'OneHot' then
begin
TFE_OpSetAttrType(Op, 'T', Tensor3.DataType); //result type must be same as on/off
TFE_OpSetAttrInt(Op, 'axis', -1); //default
TFE_OpSetAttrType(Op, 'TI', DataType); //indices type
end;
NumOutVals := 1;
try
// **** THIS IS THE ACTUAL CALL TO THE C API, WHERE Op HAS THE OPNAME
TFE_Execute(Op, #OutTensH, #NumOutVals, Status);
// ***********************************************************************
except on e:Exception do
raise Exception.Create('TensorFlow unable to execute ' + OpName +
' operation: ' + e.Message);
end;
if not CheckStatus(Status) then
Exit;
Result := TTensor.CreateWithHandle(OutTensH);
finally
if Assigned(Op) then
TFE_DeleteOp(Op);
TF_DeleteStatus(Status);
{Even if exception occurred, don't want to leave any temps dangling}
if Assigned(Tensor2) and Tensor2.IsTemp then
Tensor2.Free;
if Assigned(Tensor3) and Tensor3.IsTemp then
Tensor3.Free;
if Assigned(Tensor4) and Tensor4.IsTemp then
Tensor4.Free;
if IsTemp then
Free;
end;
end;
In the meantime, I found the description file of TensorFlow at: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/ops/ops.pbtxt. This includes all the operations and their detailed specification.
If someone is interested in a pascal interface to TF, I created one at https://github.com/zsoltszakaly/tensorflowforpascal.
Here is how you can obtain the list of valid operation names from Python:
from tensorflow.python.framework.ops import op_def_registry
registered_ops = op_def_registry.get_registered_ops()
valid_op_names = sorted(registered_ops.keys())
print(len(valid_op_names)) # Number of operation names in TensorFlow 2.0
1223
print(*valid_op_names, sep='\n')
# Abort
# Abs
# AccumulateNV2
# AccumulatorApplyGradient
# AccumulatorNumAccumulated
# AccumulatorSetGlobalStep
# AccumulatorTakeGradient
# Acos
# Acosh
# Add
# ...
This might be a nobrainer but as a novice i can not get my head around it.
I have a function returning a fixed size array. I am trying to convert this array into a record of the same size
The function signature is like this:
type Control is new UInt8_Array (1 .. 2);
function readControl (Command : Control) return Control;
I am trying to get the two bytes (UInt8) into the record Contro_Status_Bytes with the following definition:
type Control_Status_MSB is
record
RES_UP : Boolean;
QMAX_UP : Boolean;
BCA : Boolean;
CCA : Boolean;
CALMODE : Boolean;
SS : Boolean;
WDRESET : Boolean;
SHUTDOWNEN : Boolean;
end record;
for Control_Status_MSB use
record
RES_UP at 0 range 0 .. 0;
QMAX_UP at 0 range 1 .. 1;
BCA at 0 range 2 .. 2;
CCA at 0 range 3 .. 3;
CALMODE at 0 range 4 .. 4;
SS at 0 range 5 .. 5;
WDRESET at 0 range 6 .. 6;
SHUTDOWNEN at 0 range 7 .. 7;
end record;
type Control_Status_LSB is
record
VOK : Boolean;
RUP_DIS : Boolean;
LDMD : Boolean;
SLEEP : Boolean;
HIBERNATE : Boolean;
INITCOMP : Boolean;
end record;
for Control_Status_LSB use
record
VOK at 0 range 1 .. 1;
end record;
type Control_Status_Bytes is
record
HighByte : Control_Status_MSB;
LowByte : Control_Status_LSB;
end record;
I think it must be possible to convert the array to the record and vice versa without an unchecked conversion. But currently i am missing something.
Update: This might be an valid answer/way to do that i came up after reading #Simons answer.
function readControl (Command : Control) return Control_Status_Bytes is
CSB : Control_Status_Bytes;
begin
-- do stuff return UInt8_Array of size 2 as response
CSB.HighByte := response'First;
CSB.LowByte := response'Last;
return CSB;
end readControl;
Unchecked conversion is the usual way.
But for I/O ports and peripheral registers in MCUs (Atmel AVR, MSP430 etc) which can be addressed either as numbers, or arrays of booleans (or potentially, records) there's a hack ...
p1in : constant unsigned_8; -- Port 1 Input
Pragma Volatile(p1in);
Pragma Import(Ada, p1in); -- see ARM C.6 (13)
For p1in'Address use 16#20#;
p1in_bits : constant Byte; -- Port 1 Input Bits
Pragma Volatile(p1in_bits);
Pragma Import(Ada, p1in_bits);
For p1in_bits'Address use 16#20#;
This maps the inputs from I/O port 1 to the same address, viewed either as an 8 bit Unsigned or as a Byte (an array of 8 booleans).
The equivalent in your case would be something like
For Control_Status_Record'Address use Control_Status_Array`Address;
Note you probably need to attach "pragma volatile" to both views, as here, so that changes to one view aren't lost because the other view is cached in a register.
All in all, I recommend Unchecked_Conversion over this approach. It's designed for the job and avoids messing with Volatile.
It has to depend on what happens inside readControl, but couldn't you make it return the type you want directly?
function readControl (Command : Control) return Control_Status_Bytes;
(I expect that Command actually has some structure too?).
By the way, you only define the position of one component (VOK) in Control_Status_LSB, which leaves the rest up to the compiler.
The hint from #Simon Wright pointed me in the right direction.
This is what is use now and it works:
function convert (ResponseArray : Control) return Control_Status_Bytes is
Result : Control_Status_Bytes with
Import, Convention => Ada, Address => ResponseArray'Address;
begin
return Result;
end convert;
Iv been recently interested in developing a way to measure the times between my mouse clicks for research however im unsure what functions autohotkey has available to help with this. I firstly tried to get a measure of the exact time using :
FormatTime, ssnow, %A_Now%, ss
The problem with this was that subtracting one time from another is apparently impossible in autohotkey according to some forums i have searched and the result when testing also produced an empty value.
Is there a way to initiate a counter when the left button is down and then stop the timer when the button is released?
Here is the code I have been working on:
clickTime := 0
lastClick := 0
~LButton::
FormatTime, ssnow, %A_THEN%, ss
lastClick=%A_THEN%
~LButton Up::
FormatTime, ssnow, %A_Now%, ss
clickTime=%A_Now%
MsgBox (%clickTime% - %lastClick% )
Try:
~LButton::
StartTime := A_TickCount
While(GetKeyState("LButton", "P"))
continue
ToolTip % A_TickCount - StartTime
return
or:
~LButton::
StartTime := A_TickCount
keywait, LButton, L
ToolTip % A_TickCount - StartTime
return
I bought a new mouse which has a wheel on it and I've made it so a variable (Quote_Selector) increases or decreases from which way the secondary mouse wheel turns. The integer from this variable is also the key in which defines which message my button sendsfrom the array. The problem is trying to link the Quote_Selector as a key to pull which message is in the array shown and send it. My goal is to try and make this as clean as possible as well. And I've even tried using
For key [,value] in expression but I can't seem to come up with anything. i am using the AutoHotKey language and software.
; Declare Variables
Quote_Selector = 0
Min_Selector_Range = 0
Max_Selector_Range = 3
; Declare Message Choices
MessageArray := []
MessageArray[0] := "Darude - Sandstorm"
MessageArray[1] := "Rekt"
MessageArray[2] := "I cry all the time"
MessageArray[3] := "My anaconda don't"
return
; Forward Key Command
$=::
{
If Quote_Selector < %Max_Selector_Range%
Quote_Selector ++
Send, %Quote_Selector%
}
return
; Backward Key Command
$-::
{
If Quote_Selector > %Min_Selector_Range%
Quote_Selector --
Send, %Quote_Selector%
}
return
; Enter Chat Command
$0::
{
Send, {Enter}
Send, /all{space} %value%
Send, {enter}
}
return
; Declare Variables
Quote_Selector := 0
Min_Selector_Range := 0
Max_Selector_Range := 3
; Declare Message Choices
MessageArray := []
MessageArray[0] = "Darude - Sandstorm"
MessageArray[1] = "Rekt"
MessageArray[2] = "Ready to meme"
MessageArray[3] = "My anaconda don't"
return
; Forward Key Command
$=::
If (Quote_Selector < Max_Selector_Range)
{
Quote_Selector := Quote_Selector + 1
}
return
; Backward Key Command
$-::
If (Quote_Selector > Min_Selector_Range)
{
Quote_Selector := Quote_Selector - 1
}
return
; Enter Chat Command
$0::
Send, {Enter}
CurrentMessage := MessageArray[%Quote_Selector%]
Send, /all{Space} %CurrentMessage%
Send, {Enter}
CurrentMessage := ""
return
The code shown uses two keys to change the message to previous or next, and pressing the send button sends that the text provided in the array.
Try this:
; Declare Variables
Quote_Selector := 0
Min_Selector_Range := 0
Max_Selector_Range := 3
; Declare Message Choices
MessageArray := []
MessageArray[0] := "Darude - Sandstorm"
MessageArray[1] := "Rekt"
MessageArray[2] := "I cry all the time"
MessageArray[3] := "My anaconda don't"
return
; Forward Key Command
$=::
If (Quote_Selector < Max_Selector_Range)
{
Quote_Selector := Quote_Selector + 1
CurrentMessage := MessageArray[Quote_Selector]
Send, %CurrentMessage%
CurrentMessage := ""
}
return
; Backward Key Command
$-::
If (Quote_Selector > Min_Selector_Range)
{
Quote_Selector := Quote_Selector - 1
CurrentMessage := MessageArray[Quote_Selector]
Send, %CurrentMessage%
CurrentMessage := ""
}
return
; Enter Chat Command
$0::
Send, {Enter}
Send, /all{space} %value%
Send, {enter}
return
Is this what you want script to do?
Your mistakes:
Always place { after if statement condition in a new line, not before if statement.
It is not an actual error but always try to use := instead of = for assigning numeric values.
Always enclose if statement condition in ().
As far as I know you can't directly use array item with Send command. Put array item to another variable and after use that variable with Send command.
Hotkey does not need to be enclosed in {}.
Also, always use AutoHotkey and its documenatation from http://ahkscript.org/ (current uptodate version, new official website)! AutoHotkey and its documentation from autohotkey.com is outdated and you may have some problems using them!