Well, I tried the SpashImage on my slower computer, and it flopped. The SplashImage didn't enter smoothly, on the contrary it appeared slowly, painfully, and with some sort of a color inversion flash... In hindsight I think my higher than normal gesture parameters (m_Interval, m_LowThreshold, m_InitialTimeout) weren't conducive to revealing the canvas later on.
In any case, I'm quite sure now that the transcolor Gui is the better choice of canvas even on systems that work with both. I tried it in place of the splash image, and it works fine. In particular, it eliminates problems with stuck canvases on my slower computer, but other than that I think the original formulation (showing canvas earlier) may well run smoother.
In any case, I added rough implementations of the 2 suggestions I made to the original (current) gesture.ahk script to make the changes easy to spot with a comparison utility (in case anyone was interested). They are simply to demonstrate what I had in mind. I don't claim they actually improve the original script, but maybe they could if implemented more skillfully (note: I only tried this with the default values for m_Interval, m_LowThreshold, m_InitialTimeout, ... and with 8 initial zones combined with 4 permanent zones):
Code:
;
; AutoHotkey Version: 1.0.46.09 +
; Language: English
; Platform: Win XP, 2003, Vista, and probably 2000
; Author: Steve Gray, aka Lexikos*
; *NOTE: The undefiled version of Steve Gray's Gestures.ahk script is available at:
; http://www.autohotkey.com/forum/viewtopic.php?t=25892&start=0
; This version contains several changes made by someone else. The changes are probably
; suboptimal and are only meant to demonstrate a slightly different implemention
; of the mouse trails feature.
;
; Script Function:
; Mouse gestures.
; - Allows abitrary number of directions (zones).
; - Allows any number of movements/strokes in a sequence.
; - Custom script (label) execution OR variable-based key-stroke simulation.
; - Variable-based key-strokes and options can be set via Gestures.ini
; (setting a variable-based gesture overrides any associated labels/custom scripts)
;
gosub Gestures_Default_Init
; Read gesture definitions from Gestures.ini
G_LoadGestures( A_ScriptDir . "\Gestures.ini" )
if m_InitialZoneCount < 2
m_InitialZoneCount := m_ZoneCount
c_PI := 3.141592653589793
c_halfPI := 1.5707963267948965 ; Pi/2
c_Degrees := 57.29578 ; 180/Pi (degrees per radian)
#NoEnv
SendMode Input
#SingleInstance force
#KeyHistory 20
SetBatchLines -1
CoordMode, Mouse, Screen
SetTitleMatchMode, 2
; custom tray icon (also called by ToggleGestureSuspend)
G_SetTrayIcon(true)
; Hook "Suspend Hotkeys" messages to update the tray icon.
; Note: This has the odd side-effect of "disabling" the tray menu
; if the script is paused from the tray menu.
OnMessage(0x111, "WM_COMMAND")
OnExit, ExitRelease
Menu, TRAY, Tip, Mouse Gestures
Menu, TRAY, Add
Menu, TRAY, Add, Edit &Gestures, EditGestures
Menu, TRAY, Add, Edit &Default Gestures, EditGestures2
; RAlt or %m_GestureKey%, whichever was used last
m_LastGestureKey := m_GestureKey
; m_Stroke%i% will be set to the actual strokes (starting at m_Stroke1)
m_StrokeCount = 0
m_WaitForRelease := false
m_PassKeyUp := false
m_ClosingWindow := 0
; Set default or convert RRGGBB to 0xBBGGRR.
if m_PenColor =
m_PenColor := 0
else
m_PenColor := "0x" . SubStr(m_PenColor,5,2) . SubStr(m_PenColor,3,2) . SubStr(m_PenColor,1,2)
m_PenColor &= 0xffffff
; Use any other colour as the trail-Gui background.
m_TransColor := m_PenColor ? "000000" : "FFFFFF"
Gui, +LastFound ; Make the Gui background transparent.
Gui, Color, %m_TransColor%
WinSet, TransColor, %m_TransColor% ;Remove the caption and borders, and hide the Gui from the taskbar.
Gui, -Caption +ToolWindow +AlwaysOnTop
Gui, Show, X-32000 Y-32000 W%A_ScreenWidth% H%A_ScreenHeight% NA
hw_canvas := WinExist()
; Use code similar to this to disable gestures on a per-application basis:
; GroupAdd, Blacklist, Firefox ahk_class MozillaUIWindowClass
; Hotkey, IfWinNotActive, ahk_group Blacklist
; register the hotkey
Hotkey, %m_GestureKey%, GestureKey_Down
Hotkey, #%m_GestureKey%, ToggleGestureSuspend
; extra key
if ( m_GestureKey2 && m_GestureKey2 != m_GestureKey )
{
Hotkey, %m_GestureKey2%, GestureKey_Down
Hotkey, #%m_GestureKey2%, ToggleGestureSuspend
}
if !m_GestureKey2
m_GestureKey2 := m_GestureKey
; see also: GestureKey_Down for GetKeyState(...)
GroupAdd, WinCloseGroup, ahk_class ConsoleWindowClass
GroupAdd, WinCloseGroup, ahk_class AutoHotkey
return
Gestures_Default_Init:
#Include %A_ScriptDir%\Gestures Default.ahk
return
EditGestures:
Run, edit "%A_ScriptDir%\Gestures.ini",, UseErrorLevel
if ErrorLevel = ERROR
Run, notepad "%A_ScriptDir%\Gestures.ini"
return
EditGestures2:
Run, edit "%A_ScriptDir%\Gestures Default.ahk",, UseErrorLevel
if ErrorLevel = ERROR
Run, notepad "%A_ScriptDir%\Gestures Default.ahk"
return
; Wheel Gestures: Gesture key + Wheel sends keystrokes as defined by
; Gesture_WheelUp and Gesture_WheelDown in Gestures.ini.
WheelUp::
WheelDown::
gesture := c_GesturePrefix ? c_GesturePrefix : "Gesture"
if (m_WaitForRelease && %gesture%_%A_ThisHotkey%)
{ ; holding gesture button && this wheel gesture has a defined action
m_ScrolledWheel := true
m_ExitLoop := true
Send % %gesture%_%A_ThisHotkey%
} else
Send {%A_ThisHotkey%}
return
/********** XBUTTON HOTKEYS - included in the script for (my) convenience.
*/
XButton2 & LButton::MinimizeActiveWindow() ;WinMinimize, A
XButton2 & RButton::
ifWinActive, ahk_group WinCloseGroup
WinClose, A
else
Send !{F4}
return
; Task-switch with XButton1( + Wheel).
XButton1 & WheelUp::AltTab
XButton1 & WheelDown::ShiftAltTab
XButton1::Send !{Tab}
; Document/tab-switch with XButton2( + Wheel)
XButton2 & WheelUp::Send ^+{tab}
XButton2 & WheelDown::Send ^{tab}
XButton2::Send ^{Tab}
MinimizeActiveWindow()
{
global
lastMinTime := A_TickCount
lastMinID := WinExist("A")
; unlike WinMinimize, using WM_SYSCOMMAND, SC_MINIMIZE
; causes the system-wide "Minimize" sound to be played
PostMessage, 0x112, 0xF020
}
; Press Win + Gesture button to enable/disable gestures.
ToggleGestureSuspend:
Suspend, Toggle
G_SetTrayIcon(!A_IsSuspended)
; wurt from: http://addons.miranda-im.org/details.php?action=viewfile&id=1512
if A_IsSuspended
SoundPlay, %A_ScriptDir%\wurt_disabled.wav
else
SoundPlay, %A_ScriptDir%\wurt_enabled.wav
return
; Press Escape to cancel the current gesture (before releasing the gesture button.)
CancelGesture:
Hotkey, Escape, CancelGesture, Off
m_ExitLoop := true
return
GestureKey_Up:
if hdc_canvas
{ ;move window off-screen and resize to erase content
WinMove, ahk_id %hw_canvas%, , %A_ScreenWidth%, %A_ScreenHeight%, 100
DllCall( "SelectObject", "uint", hdc_canvas, "uint", old_pen ) ;Reselect "oroginal" pen per Microsoft reco.
DllCall( "ReleaseDC", "uint", hw_canvas, "uint", hdc_canvas ) ;Release device context returned by GetDC.
hdc_canvas := 0
}
m_WaitForRelease := false
Hotkey, IfWinActive
Hotkey, *%m_LastGestureKey% Up, GestureKey_Up, Off
Hotkey, Escape, CancelGesture, Off
MouseGetPos, m_EndX, m_EndY
if m_PassKeyUp
{
Send {%m_LastGestureKey% Up}
m_PassKeyUp := false
}
return
GestureKey_Down:
if ( m_WaitForRelease )
return
m_WaitForRelease := true
m_ExitLoop := false
Hotkey, IfWinActive
m_LastGestureKey := A_ThisHotkey
Hotkey, *%m_LastGestureKey% Up, GestureKey_Up, On
Hotkey, Escape, CancelGesture, On
waitCounter := 0
startX := -1
startY := -1
totalDistance := 0
lastZone := -1
m_StrokeCount := 0
; get starting mouse position
MouseGetPos, lastX, lastY
; record for later use
m_StartX := lastX
m_StartY := lastY
;set up new Gui canvas if old one has disappeared
;beeps to signal such event
;this should never happen and the condition is for testing
Gui +LastFoundExist
IfWinNotExist
{
Gui, +LastFound
Gui, Color, %m_TransColor%
WinSet, TransColor, %m_TransColor%
Gui, -Caption +ToolWindow +AlwaysOnTop
Gui, Show, X-32000 Y-32000 W%A_ScreenWidth% H%A_ScreenHeight% NA
hw_canvas := WinExist()
SoundBeep
}
;prepare clean canvas (right size, but still off screen)
WinMove, ahk_id %hw_canvas%,,%A_ScreenWidth%,%A_ScreenHeight%,%A_ScreenWidth%,%A_ScreenHeight%
Loop
{
; wait for mouse to move
Sleep, m_Interval
if ( m_ExitLoop )
{
if m_ScrolledWheel
KeyWait, %m_LastGestureKey%
return
}
; increment waitCounter by timer interval
; (may not be entirely accurate if the script is lagging...)
waitCounter += m_Interval
if ( !m_StrokeCount && m_InitialTimeout && waitCounter > m_InitialTimeout )
{
if m_WaitForRelease
{
if m_LastGestureKey in LButton,MButton,RButton
{
; convert key name to a "button" (silly how MouseClick won't accept "LButton")
StringLeft, btn, m_LastGestureKey, 1
; remember position
MouseGetPos, m_EndX, m_EndY
; move to point where gesture started, then click
MouseClick, %btn%, m_StartX, m_StartY, , 0, D
; move back into place
MouseMove, m_EndX, m_EndY, 0
btn =
}
else
{
Send {%m_LastGestureKey%}
}
; pass GestureKey Up on to active window
m_PassKeyUp := true
}
return
}
if !m_WaitForRelease
{ ; use location mouse was released at
x := m_EndX
y := m_EndY
}
else ; get current mouse position
MouseGetPos, x, y
offsetX := x - lastX
offsetY := y - lastY
; Check if mouse has moved.
if ( offsetX!=0 || offsetY!=0 )
{
if (totalDistance == 0)
{
WinMove, ahk_id %hw_canvas%, , 0, 0
hdc_canvas := DllCall( "GetDC", "uint", hw_canvas )
DllCall( "MoveToEx", "uint", hdc_canvas, "int", m_StartX, "int", m_StartY, "uint", 0 )
if !pen
pen := DllCall( "CreatePen", "int", 0, "int", m_PenWidth, "uint", m_PenColor )
old_pen := DllCall( "SelectObject", "uint", hdc_canvas, "uint", pen )
}
; Calculate distance and angle from origin.
; Note origin changes only when a new stroke is detected, so distance will continue
; to increase while the mouse contiues to move in the same approximate direction.
distance := G_GetLength(offsetX, offsetY)
if hdc_canvas
DllCall( "LineTo", "uint", hdc_canvas, "int", x, "int", y )
if ( distance > m_LowThreshold )
{
angle := G_GetAngle(offsetX, offsetY) * c_Degrees
lastX := x
lastY := y
; Allow the initial stroke to be more or less specific than subsequent strokes,
; ensuring the initial stroke can be extended according to m_InitialZoneCount.
if ( m_StrokeCount = 0
|| m_StrokeCount = 1 && G_GetZone(angle, m_InitialZoneCount) = lastZone )
zoneCount := m_InitialZoneCount
else zoneCount := m_ZoneCount
zone := G_GetZone(angle, zoneCount)
if zone =
{
; Error, or gesture stroke exceeded zone tolerance (m_Tolerance).
SoundPlay, *-1
return
}
if ( lastZone != zone )
{
; Record length of this stroke.
totalDistance := distance
; Remember zone index for subsequent iterations.
lastZone := zone
; Record this stroke.
m_StrokeCount += 1
m_Stroke%m_StrokeCount% := zone
; Reset timeout counter.
waitCounter := 0
}
else
{
; Extend length of this stroke.
totalDistance += distance
}
if ( m_HighThreshold > 0 && totalDistance > m_HighThreshold )
{
; Gesture stroke exceeded maximum stroke length (m_HighThreshold).
SoundPlay, *-1
Sleep, 150
SoundPlay, *-1
return
}
}
}
; end loop when gesture key is released
if !m_WaitForRelease
break
}
; Cancel gesture if the mouse was immobile for too long after the last stroke.
if ( m_Timeout > 0 && waitCounter > m_Timeout )
{
SoundPlay, *-1
; Gesture timed out (m_Timeout).
if ( m_DefaultOnTimeout )
{
gesture = Gesture_Default
if ( %gesture% )
SendEvent % %gesture%
else if ( IsLabel(gesture) )
gosub %gesture%
}
return
}
gesture := c_GesturePrefix ? c_GesturePrefix : "Gesture"
Loop %m_StrokeCount%
gesture .= "_" . m_Stroke%A_Index%
if ( m_StrokeCount == 0 )
gesture = Gesture_Default
; if gesture points to a variable, send its contents as keystrokes
if %gesture% !=
Send % %gesture%
; else if gesture has a label (e.g Gesture_D_R = down, then right), go to it
else if IsLabel(gesture)
gosub %gesture%
else
MsgBox, , , Unknown gesture with %m_StrokeCount% strokes:`n %gesture%, 2
return
; get angle of {x,y} from positive x-axis, relative to {0,0}
; return value is in RADIANS
G_GetAngle( x, y )
{
global c_PI, c_halfPI
; if {0,0}, angle can be any real value
if ( x==0 && y==0 )
return 0
if ( x > 0 )
{
if ( y >= 0 )
return ATan(y/x)
;y < 0
return ATan(y/x) + 2*c_PI
}
else if ( x < 0 )
{
return ATan(y/x) + c_PI
}
else ; x == 0
{
if ( y > 0 )
return c_halfPI
;y < 0
return -c_halfPI
}
}
; get distance of {x,y} from {0,0}
G_GetLength( x, y )
{
return Sqrt(x*x + y*y)
}
; get the zone of an angle
; angle: specified in degrees
; zoneCount: number of zones (zone centers are at (360/zoneCount) degree intervals)
G_GetZone( angle, zoneCount )
{
local degPerZone
local zone
local tolerance
if ( zoneCount < 2 )
{ ; show debug error message
MsgBox, 16, ERROR, Invalid zoneCount (%zoneCount%) passed to G_GetZone()., 5
return
}
if ( angle < 0 ) {
Loop {
angle += 360
if ( angle >= 0 )
break
}
}
; calculate zone size
degPerZone := 360 / zoneCount
; calculate zone - Round() finds the nearest zone (nearest integer)
zone := Mod( Round(angle/degPerZone), zoneCount )
; calculate maximum tolerance
tolerance := degPerZone/2.0
; calculate real tolerance from user-defined tolerance (percentage)
if ( m_Tolerance < 100 )
tolerance *= Abs(m_Tolerance)/100.0
if ( zone == 0 && angle > 180 )
angle -= 360
; return "" if angle is not within tolerance of the nearest zone
if ( Abs(angle-(zone*degPerZone)) >= tolerance )
return
; resolve to text form if available
if c_Zone%zoneCount%_%zone% !=
return c_Zone%zoneCount%_%zone%
return zone
}
G_LoadGestures( filename )
{
; declaring one or more local variables forces newly created variables to be global in scope
local line, varname
; declare a local array (0=array length, 1=first item)
local lineParts0
; these must be explicitly declared local
; (as of AHK 1.0.46.10, StringSplit creates them as local variables either way
; because lineParts0 is local, but references to 'lineParts1' in script refer
; to GLOBAL variables...)
local lineParts1, lineParts2
Loop, Read, %filename%
{
; ignore comments
StringLeft, line, A_LoopReadLine, 1
if ( line = ";" )
continue
; replace " = " with uncommon delimiter
StringReplace, line, A_LoopReadLine, %A_Space%=%A_Space%,
; split the string into (hopefully) two parts: variable, keys to send
StringSplit, lineParts, line, , %A_Space%%A_Tab%
if ( lineParts0 == 2 )
{
if ( StrLen(lineParts1) > 0 )
{
; allow underscores by removing them from comparison
StringReplace, varname, lineParts1, _, , All
; check if variable name is alphanumeric
if varname is alnum
{
; convert the key = value string to a variable
%lineParts1% := lineParts2
}
}
else
{
MsgBox empty variable name on line %A_Index%
}
}
}
}
WM_COMMAND(wParam, lParam)
{
static IsPaused, IsSuspended
Critical
id := wParam & 0xFFFF
if id in 65305,65404,65306,65403
{ ; "Suspend Hotkeys" or "Pause Script"
if id in 65306,65403 ; pause
IsPaused := ! IsPaused
else ; at this point, A_IsSuspended has not yet been toggled.
IsSuspended := ! A_IsSuspended
G_SetTrayIcon(!(IsPaused or IsSuspended))
}
}
G_SetTrayIcon(is_enabled)
{
icon := is_enabled ? "gestures.ico" : "nogestures.ico"
icon = %A_ScriptDir%\%icon%
; avoid an error message if the icon doesn't exist
IfExist, %icon%
Menu, TRAY, Icon, %icon%,, 1
}
ExitRelease:
WinClose, ahk_id %hw_canvas%
ExitApp
edit: just minor additions to code above loop in GestureKey_Down: an if-condition to recreate gui canvas if old is missing for any reason (this should never happen), and a WinMove command to restore canvas size off screen before entering loop (mainly intended to eliminate potential flicker and to ensure the gui is ready.)