Jump to content


Photo

Strokeit-like AHK Mouse Gesture with colored mouse trail


  • Please log in to reply
6 replies to this topic

#1 jabobian

jabobian
  • Members
  • 29 posts

Posted 18 April 2010 - 04:50 AM

Hi, everyone

I have tried to combine several existing mouse-gesture scripts to get one more comfortable for my daily use.
Thanks to the following scripts:
1 AHK Mouse Gesture of Youfou (in Chinese). (the mostly credited)
2 Mouse Guestures Plus! v1.0 by Oasis (in Chinese).
3 deguix's MouseGestures
4 Shimanov and Metaxal's draw mouse movement on screen
5 many other MG scripts including my own original mousegesture.

I have reached a point with the following features,
1) draw mouse trace and erase ('gesture trail').
2) TrayTip prompts the identified motion seriers (e.g. DL, DR, RLR, etc) or identified code (e.g. character "C", "L", etc). So you can see what is going on. If the code is not what you want, simply move more than 3 directions, and the gesture is automatically canceled. The cancel info is also prompted by traytip.
3) Use a number of Hotkeys (e.g. ^+F12) to notify other AHK scripts, so we can implement AHK without touch the mouse gesture codes.
4) Without using 'Gui +AlwaysOnTop', so there are no mask over other windows. Better than programs like gMote where a mask is indeed visible.
5) Response speed is quite good, and CPU usage is low, even compared with other commerical couterparts.
6) Use Right Ctrl keystroke to activate on/off the mouse gesture.

Here are problems to be solved
1) EraseTrace subroutine may be better refined using CreatePolygonRgn. But I am not good at this, though I have developed program similar to Microsoft Visio before. Please provide help.

I wish experts in the forum can help to improve this, and finally get one that match Strokeit.

Best,
<!-- e --><a href="mailto:Jabobian@yahoo.com">Jabobian@yahoo.com</a><!-- e -->

#SingleInstance force
IfNotExist MGConfig.ini
{
  MsgBox cannot find MGConfig.ini
  ExitApp
}
#WinActivateForce

SetTitleMatchMode 2
#IfWinNotActive, (Maxthon)
loop
{
	IniRead ActName%A_Index%,MGConfig.ini,ActNames,%A_Index%
	IniRead act%A_Index%,MGConfig.ini,MouseGestures,%A_index%
	If (act%A_Index%="ERROR")
	{
		n:=A_Index-1
		Break
	}
}
Loop %n%
{
	LoopName:=ActName%A_Index%
	LoopAct:=act%A_Index%

	StringReplace LoopAct,LoopAct,R,→,A
	StringReplace LoopAct,LoopAct,L,←,A
	StringReplace LoopAct,LoopAct,U,↑,A
	StringReplace LoopAct,LoopAct,D,↓,A

	instructions=%instructions%%A_Index%.%A_Space%%LoopAct%%A_Space%%LoopName%`n`n
}

Menu,tray,add,Read Instruction,instructions
Menu,tray,add,View INI,runini
Menu,tray,add
Menu,tray,add,Quit,GuiClose

;--------- MouseTrace GDI Code ---------------------
color_names=0x000000,0xC0C0C0,0x808080,0xFFFFFF,0x800000,0xFF0000,0x800080,0xFF00FF,0x008000,0x00FF00,0x808000,0xFFFF00,0x000080,0x0000FF,0x008080,0x00FFFF
;Black,Silver,Gray,White,Maroon,Red,Purple,Fuchsia,Lime,Green,Olive,Yellow,Navy,Blue,Teal,Aqua

CoordMode, Mouse, Screen

Process, Exist
pid_this := ErrorLevel

hdc_screen := DllCall( "GetDC", "uint", 0 )
hdc_buffer := DllCall( "CreateCompatibleDC", "uint", hdc_screen )
hbm_buffer := DllCall( "CreateCompatibleBitmap", "uint", hdc_screen, "int", A_ScreenWidth, "int", A_ScreenHeight )
DllCall( "SelectObject", "uint", hdc_buffer, "uint", hbm_buffer )
WinGet, hw_canvas, ID, ahk_class AutoHotkeyGUI ahk_pid %pid_this%
hdc_canvas := DllCall( "GetDC", "uint", hw_canvas )

StringSplit colors, color_names, `,
color_index := 10  
color := colors%color_index%

width := 2
Return

instructions:
TrayTip,MouseGesture4U,%instructions%
Return

RunIni:
Run MGConfig.ini
Return

GuiClose:
ExitApp
Return

~RCtrl::
  If Disabled = 0
  {
    Menu, TRAY, Icon, Mouse Gestures4.ico
    Menu, TRAY, Tip, Mouse Gestures (disabled)
    Hotkey, RButton, Off
    Disabled = 1
  }
  Else
  {
    Menu, TRAY, Icon, Mouse Gestures.ico
    Menu, TRAY, Tip, Mouse Gestures
    Hotkey, RButton, On
    Disabled = 0
  }
Return

RButton::
  SetTimer TimerRButton, -1
Return

TimerRButton:
SetBatchLines -1  ;any suggestions?
MouseGetPos TX,TY,UMWID,UMC
Loop
{
	MouseGetPos TXX,TYY
	TR:=GetKeyState("RButton","P")
	XX:=TXX-TX
	YY:=TYY-TY
	DS:=XX*XX+YY*YY
	If ((TR=0) And (DS<=100))
	{
		SendPlay {RButton}
		Break
	}
	If ((DS>100) And (TR=1))
	{
		Gosub Do
		Break
	}
}
If (GestureList=act1)  
    Send +{F7}
If (GestureList=act2)   
    Send +{F8}
If (GestureList=act3)  
    Send +{F9}
If (GestureList=act4)  
    Send +{F10}
If (GestureList=act5)   
    Send +{F11}
If (GestureList=act6)   
    Send +{F6}
If (GestureList=act7)   
    Send ^+{F7}
If (GestureList=act8)   
    Send ^+{F8}
If (GestureList=act9)   
    Send ^+{F9}
If (GestureList=act10)  
    Send ^+{F10}
If (GestureList=act11)  
    Send ^+{F11}
If (GestureList=act12) 
  {
    TrayTip Instructions,%instructions%,20
    SetTimer, TrayTipCheck, 200
  }
GestureList=
Return

Do:
DllCall( "BitBlt", "uint", hdc_buffer, "int", 0, "int", 0, "int", A_ScreenWidth, "int", A_ScreenHeight, "uint", hdc_screen, "int", 0, "int", 0, "uint", 0x00CC0020 )
Num:=1
x_last:=TX
y_last:=TY
Rx%Num% := x_last
Ry%Num% := y_last

Loop %Count%
	Gesture%A_Index%=
GestureList=

Count:=1
lx:=TX
ly:=TY
act=
Loop
{
	lastGesture:=Gesture%lastcount%
	TR:=GetKeyState("RButton","P")
	If TR=0
		Break
	MouseGetPos nx,ny
	xx:=nx-lx
	yy:=ny-ly
	;--------------------------------------------------
	MouseGetPos x_new, y_new
	drawLine(x_last, y_last, x_new, y_new, color, width)	
	x_last := x_new 
	y_last := y_new
	Num += 1
	Rx%Num% := x_new
	Ry%Num% := y_new
	;--------------------------------------------------
	DS:=XX*XX+YY*YY
	If (DS<225)
		Continue
	identify()
	lx:=nx
	ly:=ny
	lastcount:=count-1
	If (Gesture%count%=Gesture%lastcount%)
	{
		Gesture%count%=
		Continue
	}
	If Gesture%count%=
		Continue
	NewMove:=Gesture%Count%
	GestureList=%GestureList%%NewMove%
	StringReplace rsuser,GestureList,R,→%A_Space%,A
	StringReplace rsuser,rsuser,L,←%A_Space%,A
	StringReplace rsuser,rsuser,U,↑%A_Space%,A
	StringReplace rsuser,rsuser,D,↓%A_Space%,A
	Loop %n%
	{
		If (GestureList=Act%A_Index%)
		{
			act:=ActName%A_Index%
			Break
		}
	}
		If (count > 3)  ;change as you like, but 3 is enough for most people
	{	
		TrayTip ,,%A_Space%Gesture Canceled
    break
  }
  Else
	{
    TrayTip ,,%rsuser%%A_Space%%act%
		SetTimer, TrayTipOff, -500
	}	
	count++
	act=
}
GoSub EraseTrace
return 

TrayTipCheck:
	TR:=GetKeyState("RButton","P")
	If TR=0
	  {
  	  SetTimer, TrayTipCheck, Off	
      SetTimer, TrayTipOff, -4000
	  }
Return

TrayTipOff:
    SetTimer, TrayTipOff, Off
    TrayTip
Return

EraseTrace:
Critical
If Num <> 0
{
	DoingErase := 1
	xmin := Rx1, 	ymin := Ry1, 	xmax := Rx1, 	ymax := Ry1
	Loop %Num%
	{
		xmin := Rx%A_Index%>xmin? xmin:Rx%A_Index%
		xmax := Rx%A_Index%<xmax? xmax:Rx%A_Index%
		ymin := Ry%A_Index%>ymin? ymin:Ry%A_Index%
		ymax := Ry%A_Index%<ymax? ymax:Ry%A_Index%
		cwidth := xmax - xmin
		cheight := ymax - ymin
	}	
  DllCall( "BitBlt", "uint", hdc_canvas, "int", xmin-2, "int", ymin-2, "int", cwidth+4, "int", cheight+4, "uint", hdc_buffer, "int", xmin-2, "int", ymin-2, "uint", 0x00CC0020 )
}
Return

identify()
{
	global
	If ((XX>0) And (XX>Abs(YY)*2))
	Gesture%Count%:="R"
	If ((XX<0) And (Abs(XX)>Abs(YY)*2))
	Gesture%Count%:="L"
	If ((YY>0) And (YY>Abs(XX)*2))
	Gesture%Count%:="D"
	If ((YY<0) And (Abs(YY)>Abs(XX)*2))
	Gesture%Count%:="U"
}

drawLine(x0, y0, x1, y1, color_ini=0, width=1)
{
   dx := x1 - x0
   dy := y1 - y0
   
   dxy := (Abs(dx) > Abs(dy) ? Abs(dx) : Abs(dy) )
   
   dx := dx / dxy
   dy := dy / dxy
   
   Loop %dxy%
   {
      x0 += dx
      y0 += dy
   
     drawCircle(Round(x0),round(y0), color_ini)
   }
}

drawCircle(x,y, color_ini) {
    global hdc_canvas, width, hdc_screen

    cRegion := DllCall( "gdi32.dll\CreateRoundRectRgn", "int", x-width , "int", y-width , "int", x+width , "int", y+width, "int", width*2, "int", width*2 )
    cBrush := DllCall("gdi32.dll\CreateSolidBrush", "uint", color_ini )
		
		DllCall( "gdi32.dll\FillRgn" , "uint", hdc_canvas , "uint", cRegion , "uint", cBrush )
    DllCall("gdi32.dll\DeleteObject", "uint", cRegion)
    DllCall("gdi32.dll\DeleteObject", "uint", cBrush)
}

~^s::  ;For convenience during debug in SciTE
  ExitApp
Return

And the following are the MGConfig.ini file.

[MouseGestures]
### 1=0 hr	2=3 hr	3=6 hr
### 4=9 hr	5="L"	6="C"	
1=U
2=R
3=D
4=L
5=DR
6=LDR


[ActNames]	
###here is the information to show in traytip for each hotkey!###
1=0 hr
2=3 hr
3=6 hr
4=9 hr
5="L
6="C"
12=Instruction Help


#2 jabobian

jabobian
  • Members
  • 29 posts

Posted 18 April 2010 - 06:25 AM

It definitely worth a try. Please send feedback and suggestions to my email.

One can also modify the ini file to give application-specific prompts (in future version), for example
[Acrobat_ActNames]	
1=Restore
2=Comment
3=Add Bookmark

[MSWord_ActNames]	
1=Ribbon Edit
2=Ribbon Layout
3=Ribbon Review

BTW, the reason to choose +F6 to +F11, and ^+F6 - ^+F11 (One may also use !F6 to !F11, and ^!F6 - ^!F11) as shortcuts is that:
1. Such shortcuts are rarely used by other applications.
2. ^+F4 may be mis-interpretated as ^F4 by some applications, which may close some child windows.
3. F3, +F3, F4, +F4 may be already used by some applications.
4. Send +F12, ^F12 or ^+F12 doesn't work, anybody knows why?

But I am not sure if there is a better approach.

#3 Lexikos

Lexikos
  • Administrators
  • 8832 posts

Posted 19 April 2010 - 03:32 PM

I tried it briefly (a few times in different configurations), and have some observations and suggestions:


Only beginning to type up this post I finally realised "n hr" meant "the nth hour of the clock". It would seem more logical to me if the INI file mapped gestures (like L, R, DR, etc.) to actions (or keystrokes to send) rather than mapping arbitrary numbers to gestures, names to numbers and not mapping actions at all. Using Send to pass "messages" between scripts will only work if the other script can (and does) register those hotkeys with the default "reg" (RegisterHotkey) method. When RegisterHotkey fails, AutoHotkey falls back to the keyboard hook, which ignores simulated events (i.e. Send). This is why F12 doesn't work:

The F12 key is reserved for use by the debugger at all times,
Source: RegisterHotKey Function

I find the hook method to be more reliable and consistent, so I use #UseHook in my scripts. As a side-effect, Send (i.e. by your script) will never trigger any of my hotkeys.

Using my own gesture script, I found that gesture=keystrokes mappings were the most useful. Most of my gesture=subroutine mappings are basically equivalent, just adding minor tweaks. I think that supporting direct gesture=keystroke mappings in your script would help usability a fair bit.


When I first tried on my Windows 7 system, trails wouldn't work. With Aero/desktop composition enabled, I saw only a small circle extremely slowly tracing part of the gesture. However, I realise now that WoW running in the background may have been interfering. ;) After closing WoW, the circle becomes a trail, but it is still extremely slow.

This is due to the way desktop composition works - basically, each BitBlt to the screen has to "synchronize" with the video adapter, which makes it much slower than if desktop composition was disabled. More specifically - If I recall correctly what I've read, BitBlt actually causes the entire screen to be copied from video memory into system memory and back each call. I think this is partly because what is shown on the screen is the result of blending done by the video adapter, and ordinarily only exists in video memory. OTOH, a typical window has both a surface in system memory and a surface in video memory, so GDI calls such as BitBlt operate normally.

After I disabled Aero (and closed WoW), trails still didn't work. I would see a fraction of the first trail, then no sign of any trails until the script is reloaded. This only happened on Windows 7 (see below).

Speed issues with Aero aside, BitBlting to the screen is generally error-prone, as you cannot predict when some other window will update itself and overwrite what you've drawn. For example, in my Vista VM (which doesn't support Aero), the trails work well, except that the hover effects in Explorer tend to erase parts of the trail.

I can't think of a slower way to draw a line than a series of FillRgn calls - I suggest looking into MoveToEx and LineTo.

Rather than drawing to the screen, you could create an empty window with no caption or border, expand it to fill the screen, and draw onto it. The WM_ERASEBKGND message can be overridden via OnMessage to prevent the background colour from filling the window/screen.


The default configuration seems to disrupt right-click - i.e. I see the menu pop up, then immediately disappear - the "instructions" traytip appearing in its place. Otherwise gesture recognition seems reasonably good.

#4 jabobian

jabobian
  • Members
  • 29 posts

Posted 20 April 2010 - 03:16 AM

Hi, Lex, many thanks for your suggestions, especially those on GDI. It seems my gesture is only suitable for XP. I will try to improve it.

BTW The time to dismiss the traytip of instructions is increased to 4 seconds after releasing the right button.

#5 jabobian

jabobian
  • Members
  • 29 posts

Posted 20 April 2010 - 03:32 AM

Rather than drawing to the screen, you could create an empty window with no caption or border, expand it to fill the screen, and draw onto it. The WM_ERASEBKGND message can be overridden via OnMessage to prevent the background colour from filling the window/screen.

I have thought of creating an empty window, but clicking on it will make the last found window deactivated, and the titlebar of last found window is changed from deep blue to light blue.

I will give it another try.

#6 jabobian

jabobian
  • Members
  • 29 posts

Posted 20 April 2010 - 07:54 AM

Please use Lex's gestures.

I had briefly tried Lex's gestures a few days ago, and thought there is a bug for redraw mouse trail (which is fixed now). So I developed myself this gesture script. Indeed, Lex's one is already near perfect. My work is not necessary at all.
:cry:

#7 ayuanx

ayuanx
  • Members
  • 8 posts

Posted 26 October 2011 - 04:07 PM

Try this!

[MouseGesture.ahk] Mouse Gesture Script English Version
<!-- m -->http://www.autohotke...pic.php?t=77271<!-- m -->