AutoHotkey Community

It is currently May 26th, 2012, 8:49 pm

All times are UTC [ DST ]




Post new topic Reply to topic  [ 107 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6, 7, 8  Next
Author Message
 Post subject:
PostPosted: June 21st, 2009, 2:02 am 
Offline

Joined: February 7th, 2009, 11:28 pm
Posts: 384
Lexikos wrote:
SplashImage always produces an opaque window; I guess you see an effect similar to transparency because the transparent GIF prevents the window from painting its background over the top of whatever was on-screen before the window appeared. This "technique" won't work with desktop composition (Aero) enabled on Windows 7 (or probably Vista). There are two alternatives that I know of:
  1. Use Gui, Color and WinSet, TransColor to make any region you have not painted over transparent.
  2. Copy the screen into a bitmap via GDI before showing the Gui. This method would be faster (allowing gestures to be more responsive) but probably more complicated, and would hide any changes which are occurring to windows in the background.


Hello Lexikos, the changes you made to convert zones into letters earlier in the script are working well for me, plus I scaled down my previous m_LowThreshold setting (to 40). I dropped the relative stroke distance conditions I was testing previously because everything was running smoothly so I didn't see a need for them anymore - I suppose my skill at 'drawing' mouse gestures has improved too.

But I decided to revisit the SplashImage method of drawing mouse trails (or of providing a canvas for them). I decided to use the SplashImage instead of a Gui mainly because I'm more familiar with the former. I added black borders to my splashimage gif so that I would see when it's on for purposes of testing the script. Here's a PrtSc pic of the setup:

Image

I wanted to run this by you to share (I think this method may have some advantages), and also to ask if you see any problems with it and/or how I could compare its performance to the transcolor gui method.

Goals: (1) I wanted create the canvas window only during the initialization, and subsequently just toggle it with WinShow/WinHide. (2) I wanted to get the canvas out only if an actual stroke was begun (as opposed to regular right-clicks). The DllCalls for drawing on the canvas are from your (Lexikos') code i.e. CreatePen, LineTo... :

1. Here's the code I added to the initialization (probably includes redundant windowing commands):

Code:
SetWinDelay, -1
DetectHiddenWindows, On
SplashImage, %A_ScriptDir%\canvas.gif, A M b X-32000 Y-32000,,, mPathCanvas
hw_canvas:= WinExist("mPathCanvas")

WinShow, ahk_id %hw_canvas%
WinMove, ahk_id %hw_canvas%,, 0, 0
WinSet, AlwaysOnTop, On, ahk_id %hw_canvas%
WinActivate, ahk_id %hw_canvas%
WinWaitActive, ahk_id %hw_canvas%
WinHide, ahk_id %hw_canvas%
DetectHiddenWindows, Off

OnExit, ExitRelease ;for clean up


2. Under GestureKey_Up I have:

Code:
    if hdc_canvas
    {
        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.
        WinHide, ahk_id %hw_canvas%
;;maybe should add a 'winwaithide'-type command...
        hdc_canvas:= 0
    }

[+ the regular code of course]

3. I moved the GestureKey_Down canvas/drawing code below the codition "if ( offsetX!=0 || offsetY!=0 ) ;check if mouse has moved" so that the canvas isn't called unnecessarily (on non-gesture right-clicks). Since the WinShow and 'get-pen-ready' commands are now inside the gesture detection loop, I put if (totalDistance == 0) there to limit them to only run on the first loop iteration satisfying if ( offsetX!=0 || offsetY!=0 ):

Code:
            if (totalDistance == 0) && m_PenWidth
            {
                WinShow, ahk_id %hw_canvas%
                WinActivate, ahk_id %hw_canvas%
                WinWaitActive, ahk_id %hw_canvas%
                hdc_canvas := DllCall( "GetDC", "uint", hw_canvas )

                ; Set the initial position, where the first line will begin.
                DllCall( "MoveToEx", "uint", hdc_canvas, "int", m_StartX, "int", m_StartY, "uint", 0 )
                ; Create the pen, if not already created.
                if !pen
                    pen := DllCall( "CreatePen", "int", 0, "int", m_PenWidth, "uint", m_PenColor )
                ; Select the pen and store a handle to the previously selected pen (common GDI practice).
                old_pen := DllCall( "SelectObject", "uint", hdc_canvas, "uint", pen )
                ;sleep, 10
            }
                   
            ; 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 ;Draw line to current position from starting position or end of the previous line.
                DllCall( "LineTo", "uint", hdc_canvas, "int", x, "int", y )


4. The canvas is closed on exit. Most of the code below is probably redundant, but I haven't had time to streamline it:

Code:
~^Esc:: ;in case of a problem (e.g. stuck canvas)
ExitRelease:
if hdc_canvas
{
    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
}

DetectHiddenWindows, On
IfWinExist, ahk_id %hw_canvas%
    WinKill, ahk_id %hw_canvas% ;winclose might be enough
ExitApp

_________________
Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 21st, 2009, 5:59 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7502
Location: Australia
That's all well and good, but I won't be using the transparent GIF method on my system as it produces a completely opaque window with no transparent effect whatsoever. If it did, I would not go to much effort to compare performance - just run the script with one method after the other and see if you notice a difference.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 21st, 2009, 9:44 am 
Offline

Joined: February 7th, 2009, 11:28 pm
Posts: 384
Lexikos wrote:
That's all well and good, but I won't be using the transparent GIF method on my system as it produces a completely opaque window with no transparent effect whatsoever. If it did, I would not go to much effort to compare performance - just run the script with one method after the other and see if you notice a difference.


that's strange. fwiw, i used IrfanView (great free image viewer and basic editor) to create a black-white image the size of my screen (without any black). Upon saving the image, Irfanview let's me select a color to make transparent (for gif, png, etc formats), so I selected white. The result is a ~2KB gif-image (larger if black borders are included). When I load it with AHK's splashimage command, it's transparent:

Image

Image

Image

Image

_________________
Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 21st, 2009, 10:09 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7502
Location: Australia
I already explained it. SplashImage does not create a transparent window. It only looks transparent because the transparent pixels in the image are not painted and what was previously on the screen is not erased.

Try this: create a "transparent" SplashImage, then move it. (Do not hide or recreate the SplashImage.)

(Edit: I'd appreciate if you shrank or removed those images; at least one of them is too big.)


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 21st, 2009, 10:48 am 
Offline

Joined: February 7th, 2009, 11:28 pm
Posts: 384
Lexikos wrote:
I already explained it. SplashImage does not create a transparent window. It only looks transparent because the transparent pixels in the image are not painted and what was previously on the screen is not erased.

Try this: create a "transparent" SplashImage, then move it. (Do not hide or recreate the SplashImage.)

(Edit: I'd appreciate if you shrank or removed those images; at least one of them is too big.)


I see what you mean, but not why it matters, except for users with "desktop composition (Aero) enabled on Windows 7 (or probably Vista)."

And the SplashImage wasn't the main point, but rather that (1) it might be more efficient to only create the canvas (even if gui) once during the initialization of the script, and henceforth just toggle it back and forth on right-clicks? And (2) perhaps moving the code used to call or create the canvas further down in the script would make sense so that it's not activated on regular (non-gesture) right-clicks?

I phrased the above points as questions because I thought maybe you considered doing it that way, but decided against it for some reason.

_________________
Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 21st, 2009, 2:52 pm 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7502
Location: Australia
pajenn wrote:
I see what you mean, but not why it matters, except for users with "desktop composition (Aero) enabled on Windows 7 (or probably Vista)."
Obviously since I'm one of those users, that was my main concern. Another minor issue is that any windows which are constantly changing will appear to freeze while you draw a gesture.
Quote:
(1) it might be more efficient to only create the canvas (even if gui) once during the initialization of the script,
On my system the Gui method takes around 50ms to create and destroy the canvas. With the default m_Interval of 50ms, that means the first stroke can be recognized less than 100ms after pressing the button - seems perfectly reasonable to me. However, I think 50ms is worth making such a trivial change. It might also help combat the flicker which occurs occasionally as the Gui is created and shown.
Quote:
And (2) perhaps moving the code used to call or create the canvas further down in the script would make sense so that it's not activated on regular (non-gesture) right-clicks?
Since the trails indicate the path of the mouse and not the path of recognized gestures, I think it is more appropriate to draw the trails as soon as the mouse moves. That said, I think it would be better to draw the lines in a way that more accurately reflects how the gestures are recognized, but I'm not sure since I personally don't want or need trails.

Note that nothing is drawn if the mouse does not move, so your point only makes sense if "regular (non-gesture) right-clicks" includes right-click & drag via m_InitialTimeout or m_Timeout && m_DefaultOnTimeout.


I have been revising the script over the last couple weeks. I had made several changes and one or two improvements to my own copy of the script since my initial post, but hadn't updated this thread as some of the additions were specific to my setup. After the next update, the structure of the script will be as follows:
  • Gestures_Default.ahk will contain only the default gestures, and will be entirely optional.
  • Gestures_User.ahk may contain user-defined (scripted) gestures, hotkeys and anything else the user wants.
  • Default settings and everything else will be in Gestures.ahk.
Gestures_User.ahk won't be included - currently it contains all of the changes I didn't want to distribute. That includes my over-complicated XButton hotkeys; since the ones originally included in Gestures.ahk are fairly simple, I may post them separately.

I've also added an "active timeout" option to cause one of three things to happen if the mouse remains still for too long:
  • Gesture-recognition is cancelled.
  • Gesture-recognition is cancelled and a default action is performed (like m_InitialTimeout).
  • Gesture-recognition ends as if the gesture key has been released.
I plan to use this to implement button-less gestures. Basically I just need to add the options and a timer to relaunch GestureKey_Down, and conditionally avoid registering hotkeys or using A_ThisHotkey/m_LastGestureKey.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 21st, 2009, 3:33 pm 
Offline

Joined: February 7th, 2009, 11:28 pm
Posts: 384
Lexikos wrote:
Since the trails indicate the path of the mouse and not the path of recognized gestures, I think it is more appropriate to draw the trails as soon as the mouse moves. That said, I think it would be better to draw the lines in a way that more accurately reflects how the gestures are recognized, but I'm not sure since I personally don't want or need trails.

Note that nothing is drawn if the mouse does not move, so your point only makes sense if "regular (non-gesture) right-clicks" includes right-click & drag via m_InitialTimeout or m_Timeout && m_DefaultOnTimeout.


I'm not suggesting to start the trails earlier or later, but I would delay calling the canvas and setting up the pen until a stroke (and trail drawing) begins. i.e. executing this code slightly later:

Code:
    if m_PenWidth
    {
        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
        Gui, Show, X0 Y0 NA
        hw_canvas := WinExist()
        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 )
    }


edit:

Actually, I personally have a proper motivation for moving that down or putting some condition on it to not start until a stroke starts: I tried the gesture script on an older computer, a desktop that I don't use actively anymore (1.3 GHz CPU and 500 RAM). I guess it was too slow for the script because on regular (read quick and stationary) right-clicks the gui would often pop up without transparency (opaque gray) and fail to unload until I right-clicked the mouse again, longer, to cause a time-out. The gestures and trails work fine on it though. (i.e. the gui canvas problem only occurs on non-gesture clicks).

_________________
Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler.


Last edited by pajenn on June 21st, 2009, 3:46 pm, edited 1 time in total.

Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 21st, 2009, 3:45 pm 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7502
Location: Australia
In that case, your two suggestions seem to be contradictory. If the canvas is created during initialization of the script, only two things need to occur prior to drawing the first stroke: show the canvas, set the initial drawing position.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 21st, 2009, 3:56 pm 
Offline

Joined: February 7th, 2009, 11:28 pm
Posts: 384
Lexikos wrote:
In that case, your two suggestions seem to be contradictory. If the canvas is created during initialization of the script, only two things need to occur prior to drawing the first stroke: show the canvas, set the initial drawing position.


(please see edit on my previous post - I think I'm failing to explain myself properly)

Suggestion 1 is only meant as a potential way to speed up showing and hiding, or gui equivalents, of the canvas window. Suggestion 2 is meant to reduce potential problems of stuck canvases on slower computers, and to eliminate resource use on setting up or showing a splash image or gui and getting the pen ready on every single right-click even when not meant to initiate a gesture.

_________________
Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 21st, 2009, 10:51 pm 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7502
Location: Australia
pajenn wrote:
Suggestion 2 is meant to reduce potential problems of stuck canvases on slower computers,
OK.
Quote:
and to eliminate resource use on setting up or showing a splash image or gui and getting the pen ready on every single right-click even when not meant to initiate a gesture.
If the initialization happens only once, what is it eliminating? Gui, Show + DllCall(MoveToEx) are unlikely to be much of an issue, resource usage-wise.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 22nd, 2009, 6:02 pm 
Offline

Joined: February 7th, 2009, 11:28 pm
Posts: 384
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.)

_________________
Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 23rd, 2009, 5:16 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7502
Location: Australia
Among the changes I've made so far, the canvas (including hdc_canvas) and pen are initialized when the script loads. I plan to make several more changes to the script before uploading it, though.

pajenn wrote:
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.)
On my system, I found that it can take a moment for the window to update after it is shown. I believe this is due to the way SetLayeredWindowAttributes (WinSet, Transparent) works. If the system was under load, the first gesture reliably flashed the default window background colour on-screen. Subsequent gestures often flashed the previous gesture on-screen - even while the system was mostly idle.

Showing the Gui off-screen then clearing it via FillRect before moving it on-screen seems to be a reliable solution.

I've also added support for trails on multiple monitors and an option do draw a small "notch" at the point where one stroke ends and the next begins.

(To be perfectly clear, I have not yet uploaded these new features.)


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 25th, 2009, 12:55 pm 
Offline

Joined: February 7th, 2009, 11:28 pm
Posts: 384
Lexikos wrote:
On my system, I found that it can take a moment for the window to update after it is shown. I believe this is due to the way SetLayeredWindowAttributes (WinSet, Transparent) works. If the system was under load, the first gesture reliably flashed the default window background colour on-screen. Subsequent gestures often flashed the previous gesture on-screen - even while the system was mostly idle.

Showing the Gui off-screen then clearing it via FillRect before moving it on-screen seems to be a reliable solution.


Resizing the canvas between gestures cleared it of previous gesture flashes on my sytem.

At the moment I resize the canvas on Gesture_Up to a small black spot on the lower right of my screen:
Code:
WinMove, ahk_id %hw_canvas%,,60,740,10,10
WinSet, TransColor, Off, ahk_id %hw_canvas%


and right before the starting of a gesture loop (or when I right-click for any reason), transcolor is turned on again:
Code:
WinSet, AlwaysOnTop, On, ahk_id %hw_canvas%
WinSet, TransColor, %m_TransColor%, ahk_id %hw_canvas%


And then I just resize the canvas to cover the whole screen before the trails start. Initially I turned off transcolor on Gesture_Up and left the window on screen (in tiny size) just to have a visual cue that the code was working, but it doesn't bother me so I've kept the script that way for now.

Quote:
I've also added support for trails on multiple monitors and an option do draw a small "notch" at the point where one stroke ends and the next begins.

(To be perfectly clear, I have not yet uploaded these new features.)


Sounds promising. I look forward to it.

_________________
Hardware: 1.8 GHz laptop with 4 GB ram, Windows XP/SP3
Software: Prevx, Privatefirewall, KeyScrambler.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: July 19th, 2009, 2:57 am 
Offline

Joined: June 8th, 2009, 8:09 pm
Posts: 84
This script makes the Windows 7 SuperBar prehistoric.
thanks for your hard work :)

how to re-assign Gesture_R and Gesture_L to MasterVolume up/down until gesture button is released?


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: July 19th, 2009, 6:23 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7502
Location: Australia
Bebert wrote:
how to re-assign Gesture_R and Gesture_L to MasterVolume up/down until gesture button is released?
I suppose you want to hold the button and drag left or right to adjust volume. It could work as follows:
  • Once the mouse is dragged left or right more than %m_LowThreshold% pixels, exit gesture recognition and enter "volume-adjust" mode.
  • For consistency, immediately adjust the volume based on how far the mouse has moved from the origin (m_StartX, m_StartY).
  • While in "volume-adjust" mode, ignore vertical movement (possibly lock the cursor on the Y-axis) and adjust the volume based on how far and in which direction the mouse moves. Volume-per-pixel would be a user-defined variable.
  • When the button is released, exit "volume-adjust" mode.
I'm not likely to implement features like this anytime soon as the current design doesn't really allow it, but I'll keep it in mind for a future version. I planned to implement something like this to allow, for instance, a "window-move" gesture as a substitute for grabbing the window's title bar. (Currently one can use L-R-L-R or R-L-R-L, but it moves the mouse and requires a left click or Enter keypress to stop moving.)


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 107 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6, 7, 8  Next

All times are UTC [ DST ]


Who is online

Users browsing this forum: No registered users and 11 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group