.
--------------------------------------------------------------------------------
/MouseClickRipple.ahk:
--------------------------------------------------------------------------------
1 | #Include ./Utils.ahk
2 | #SingleInstance, Force
3 | #NoEnv
4 | #MaxThreadsPerHotkey 3
5 | #installmousehook
6 | #MaxHotkeysPerInterval 100
7 |
8 | SetBatchLines, -1
9 | SetWinDelay, -1
10 | CoordMode, mouse, screen
11 |
12 | ClickEvents := []
13 | IsStillDrawingRipples := False
14 | SetupMouseClickRipple()
15 | {
16 | global
17 | SETTINGS := ReadConfigFile("settings.ini")
18 | InitializeClickRippleGUI()
19 |
20 | local ProcessMouseClickFunc := Func("ProcessMouseClick").Bind()
21 | if (SETTINGS.cursorLeftClickRippleEffect.enabled = True) {
22 | Hotkey, ~*LButton, %ProcessMouseClickFunc%
23 | }
24 | if (SETTINGS.cursorRightClickRippleEffect.enabled = True) {
25 | Hotkey, ~*RButton, %ProcessMouseClickFunc%
26 | }
27 | if (SETTINGS.cursorMiddleClickRippleEffect.enabled = True) {
28 | Hotkey, ~*MButton, %ProcessMouseClickFunc%
29 | }
30 | }
31 |
32 | InitializeClickRippleGUI(){
33 | global
34 | ; Calculate the width/height of the bitmap we are going to create
35 | ClickRippleBitMapWidth := Max(SETTINGS.cursorLeftClickRippleEffect.rippleDiameterStart
36 | , SETTINGS.cursorLeftClickRippleEffect.rippleDiameterEnd
37 | , SETTINGS.cursorMiddleClickRippleEffect.rippleDiameterStart
38 | , SETTINGS.cursorMiddleClickRippleEffect.rippleDiameterEnd
39 | , SETTINGS.cursorRightClickRippleEffect.rippleDiameterStart
40 | , SETTINGS.cursorRightClickRippleEffect.rippleDiameterEnd ) + 2
41 |
42 | ; Start gdi+
43 | if (!Gdip_Startup())
44 | {
45 | MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system
46 | ExitApp
47 | }
48 |
49 | ; Create a layered window (+E0x80000), and it must be used with UpdateLayeredWindow() to trigger repaint.
50 | Gui, MouseClickRippleWindow: +AlwaysOnTop -Caption +ToolWindow +E0x80000 +hwndClickRippleWindowHwnd
51 | Gui, MouseClickRippleWindow: Show, NA
52 |
53 | ; Create a gdi bitmap that we are going to draw onto.
54 | ClickRippleHbm := CreateDIBSection(ClickRippleBitMapWidth, ClickRippleBitMapWidth)
55 |
56 | ; Get a device context compatible with the screen
57 | ClickRippleHdc := CreateCompatibleDC()
58 |
59 | ; Select the bitmap into the device context
60 | local obm := SelectObject(ClickRippleHdc, ClickRippleHbm)
61 |
62 | ; Get a pointer to the graphics of the bitmap
63 | ClickRippleGraphics := Gdip_GraphicsFromHDC(ClickRippleHdc)
64 |
65 | ; Set the smoothing mode to antialias = 4 to make shapes appear smother
66 | Gdip_SetSmoothingMode(ClickRippleGraphics, 4)
67 |
68 | Return
69 | }
70 |
71 | ProcessMouseClick() {
72 | global SETTINGS, ClickEvents
73 | if (InStr(A_ThisHotkey, "LButton"))
74 | {
75 | params := SETTINGS.cursorLeftClickRippleEffect
76 | }
77 | if (InStr(A_ThisHotkey, "MButton"))
78 | {
79 | params := SETTINGS.cursorMiddleClickRippleEffect
80 | }
81 | if (InStr(A_ThisHotkey, "RButton"))
82 | {
83 | params := SETTINGS.cursorRightClickRippleEffect
84 | }
85 |
86 | ; Add an event to the event array and call the CheckToDrawNextClickEvent function.
87 | MouseGetPos rippleMousePositionX, rippleMousePositionY
88 | params.rippleMousePositionX := rippleMousePositionX
89 | params.rippleMousePositionY := rippleMousePositionY
90 | ClickEvents.Push(params)
91 | CheckToDrawNextClickEvent()
92 | }
93 |
94 | CheckToDrawNextClickEvent()
95 | {
96 | global
97 | if (IsStillDrawingRipples || ClickEvents.Count() == 0)
98 | {
99 | Return
100 | }
101 |
102 | ; Get the first event from the ClickEvents array and then delete it
103 | RippleEventParams := ClickEvents[1]
104 | ClickEvents.RemoveAt(1)
105 |
106 | if RippleEventParams.playClickSound == True
107 | {
108 | SoundPlay, %A_ScriptDir%\MouseClickSound.wav
109 | }
110 |
111 | IsStillDrawingRipples := True
112 |
113 | CurrentRippleDiameter := RippleEventParams.rippleDiameterStart
114 | CurrentRippleAlpha := RippleEventParams.rippleAlphaStart
115 | TotalCountOfRipples := Abs(Round((RippleEventParams.rippleDiameterEnd - RippleEventParams.rippleDiameterStart) / RippleEventParams.rippleDiameterStep))
116 | RippleAlphaStep := Round((RippleEventParams.rippleAlphaEnd - RippleEventParams.rippleAlphaStart) / TotalCountOfRipples)
117 |
118 | RippleWindowPositionX := RippleEventParams.rippleMousePositionX - Round(ClickRippleBitMapWidth/2)
119 | RippleWindowPositionY := RippleEventParams.rippleMousePositionY - Round(ClickRippleBitMapWidth/2)
120 |
121 | AlreadyDrawnRipples := 0
122 | SetTimer, DRAW_RIPPLE, % RippleEventParams.rippleRefreshInterval
123 | DRAW_RIPPLE:
124 | ; Clear the previous drawing
125 | Gdip_GraphicsClear(ClickRippleGraphics, 0)
126 | ; Create a pen with ARGB (ARGB = Transparency, red, green, blue) to draw a circle
127 | local alphaRGB := CurrentRippleAlpha << 24 | RippleEventParams.rippleColor
128 | local pPen := Gdip_CreatePen(alphaRGB, RippleEventParams.rippleLineWidth)
129 |
130 | ; Draw a circle into the graphics of the bitmap using the pen created
131 | Gdip_DrawEllipse(ClickRippleGraphics
132 | , pPen
133 | , (ClickRippleBitMapWidth - CurrentRippleDiameter)/2
134 | , (ClickRippleBitMapWidth - CurrentRippleDiameter)/2
135 | , CurrentRippleDiameter
136 | , CurrentRippleDiameter)
137 | Gdip_DeletePen(pPen)
138 | UpdateLayeredWindow(ClickRippleWindowHwnd, ClickRippleHdc, RippleWindowPositionX, RippleWindowPositionY, ClickRippleBitMapWidth, ClickRippleBitMapWidth)
139 |
140 | ; Calculate necessary values to prepare for drawing the next circle
141 | CurrentRippleAlpha := CurrentRippleAlpha + RippleAlphaStep
142 | if (RippleEventParams.rippleDiameterEnd > RippleEventParams.rippleDiameterStart)
143 | {
144 | CurrentRippleDiameter := CurrentRippleDiameter + Abs(RippleEventParams.rippleDiameterStep)
145 | }
146 | else
147 | {
148 | CurrentRippleDiameter := CurrentRippleDiameter - Abs(RippleEventParams.rippleDiameterStep)
149 | }
150 | AlreadyDrawnRipples++
151 | if (AlreadyDrawnRipples >= TotalCountOfRipples)
152 | {
153 | ; All circles for one click event has been drawn
154 | IsStillDrawingRipples := False
155 | SetTimer, DRAW_RIPPLE, Off
156 | Gdip_GraphicsClear(ClickRippleGraphics, 0)
157 | UpdateLayeredWindow(ClickRippleWindowHwnd, ClickRippleHdc, RippleWindowPositionX, RippleWindowPositionY, ClickRippleBitMapWidth, ClickRippleBitMapWidth)
158 | ; Trigger the function again to check if there are other mouse click events waiting to be processed
159 | CheckToDrawNextClickEvent()
160 | }
161 | Return
162 | }
163 |
164 | SetupMouseClickRipple()
--------------------------------------------------------------------------------
/MouseClickRippleWithGui.ahk:
--------------------------------------------------------------------------------
1 | #Include ./Utils.ahk
2 | #SingleInstance, Force
3 | #NoEnv
4 | #MaxThreadsPerHotkey 3
5 | #installmousehook
6 | #MaxHotkeysPerInterval 100
7 |
8 | SetBatchLines, -1
9 | SetWinDelay, -1
10 | CoordMode, mouse, screen
11 |
12 | ClickEvents := []
13 | AlreadyCreatedRegionForRipples := Object()
14 | IsStillDrawingRipples := False
15 | SetupMouseClickRipple()
16 | {
17 | global
18 | SETTINGS := ReadConfigFile("settings.ini")
19 | InitializeClickRippleGUI()
20 |
21 | local ProcessMouseClickFunc := Func("ProcessMouseClick").Bind()
22 | if (SETTINGS.cursorLeftClickRippleEffect.enabled = True) {
23 | Hotkey, ~*LButton, %ProcessMouseClickFunc%
24 | }
25 | if (SETTINGS.cursorRightClickRippleEffect.enabled = True) {
26 | Hotkey, ~*RButton, %ProcessMouseClickFunc%
27 | }
28 | if (SETTINGS.cursorMiddleClickRippleEffect.enabled = True) {
29 | Hotkey, ~*MButton, %ProcessMouseClickFunc%
30 | }
31 | }
32 |
33 | InitializeClickRippleGUI(){
34 | global
35 | Gui, MouseClickRippleWindow: +hwndClickRippleWindowHwnd +AlwaysOnTop -Caption +ToolWindow +E0x20 ;+E0x20 click thru
36 | WinSet, Transparent, % transparency, % "ahk_id " ClickRippleWindowHwnd
37 | ClickRippleWindowWidth := Max(SETTINGS.cursorLeftClickRippleEffect.rippleDiameterStart
38 | , SETTINGS.cursorLeftClickRippleEffect.rippleDiameterEnd
39 | , SETTINGS.cursorMiddleClickRippleEffect.rippleDiameterStart
40 | , SETTINGS.cursorMiddleClickRippleEffect.rippleDiameterEnd
41 | , SETTINGS.cursorRightClickRippleEffect.rippleDiameterStart
42 | , SETTINGS.cursorRightClickRippleEffect.rippleDiameterEnd ) + 2
43 |
44 | Return
45 | }
46 |
47 | ProcessMouseClick() {
48 | global SETTINGS, ClickEvents
49 | if (InStr(A_ThisHotkey, "LButton"))
50 | {
51 | params := SETTINGS.cursorLeftClickRippleEffect
52 | }
53 | if (InStr(A_ThisHotkey, "MButton"))
54 | {
55 | params := SETTINGS.cursorMiddleClickRippleEffect
56 | }
57 | if (InStr(A_ThisHotkey, "RButton"))
58 | {
59 | params := SETTINGS.cursorRightClickRippleEffect
60 | }
61 | ; Add an event to the event array and call the DrawRipple function.
62 |
63 | MouseGetPos mousePositionX, mousePositionY
64 |
65 | params.mousePositionX := mousePositionX
66 | params.mousePositionY := mousePositionY
67 | ClickEvents.Push(params)
68 | CheckToDrawNextClickEvent()
69 | }
70 |
71 | CheckToDrawNextClickEvent()
72 | {
73 | global
74 | if (IsStillDrawingRipples || ClickEvents.Count() == 0)
75 | {
76 | Return
77 | }
78 |
79 | ; Get the first event from the ClickEvents array and then delete it
80 | RippleEventParams := ClickEvents[1]
81 | ClickEvents.RemoveAt(1)
82 |
83 | if (RippleEventParams.playClickSound == True)
84 | {
85 | SoundPlay, %A_ScriptDir%\MouseClickSound.wav
86 | }
87 |
88 | IsStillDrawingRipples := True
89 | CurrentRippleDiameter := RippleEventParams.rippleDiameterStart
90 | CurrentRippleAlpha := RippleEventParams.rippleAlphaStart
91 | TotalCountOfRipples := Abs(Round((RippleEventParams.rippleDiameterEnd - RippleEventParams.rippleDiameterStart) / RippleEventParams.rippleDiameterStep))
92 | RippleAlphaStep := Round((RippleEventParams.rippleAlphaEnd - RippleEventParams.rippleAlphaStart) / TotalCountOfRipples)
93 |
94 | RippleWindowPositionX := RippleEventParams.mousePositionX - Round(ClickRippleWindowWidth/2)
95 | RippleWindowPositionY := RippleEventParams.mousePositionY - Round(ClickRippleWindowWidth/2)
96 | Gui, MouseClickRippleWindow: Color, % RippleEventParams.rippleColor
97 | Gui, MouseClickRippleWindow: Show, x%RippleWindowPositionX% y%RippleWindowPositionY% w%ClickRippleWindowWidth% h%ClickRippleWindowWidth% NoActivate
98 | AlreadyDrawnRipples := 0
99 | SetTimer, DRAW_RIPPLE, % RippleEventParams.rippleRefreshInterval
100 |
101 | DRAW_RIPPLE:
102 | local regionKey := RippleEventParams.rippleColor "," CurrentRippleDiameter
103 | if (AlreadyCreatedRegionForRipples.HasKey(regionKey))
104 | {
105 | local finalRegion := AlreadyCreatedRegionForRipples[regionKey]
106 | }
107 | else
108 | {
109 | local outerRegionTopLeftX := Round((ClickRippleWindowWidth-CurrentRippleDiameter)/2)
110 | local outerRegionTopLeftY := Round((ClickRippleWindowWidth-CurrentRippleDiameter)/2)
111 | local outerRegionBottomRightX := outerRegionTopLeftX + CurrentRippleDiameter
112 | local outerRegionBottomRightY := outerRegionTopLeftY + CurrentRippleDiameter
113 | local innerRegionTopLeftX := outerRegionTopLeftX + RippleEventParams.rippleLineWidth
114 | local innerRegionTopLeftY := outerRegionTopLeftY + RippleEventParams.rippleLineWidth
115 | local innerRegionBottomRightX := outerRegionBottomRightX - RippleEventParams.rippleLineWidth
116 | local innerRegionBottomRightY := outerRegionBottomRightY - RippleEventParams.rippleLineWidth
117 | local finalRegion := DllCall("CreateEllipticRgn", "Int", outerRegionTopLeftX, "Int", outerRegionTopLeftY, "Int", outerRegionBottomRightX, "Int", outerRegionBottomRightY)
118 | local inner := DllCall("CreateEllipticRgn", "Int", innerRegionTopLeftX, "Int", innerRegionTopLeftY, "Int", innerRegionBottomRightX, "Int", innerRegionBottomRightY)
119 | DllCall("CombineRgn", "UInt", finalRegion, "UInt", finalRegion, "UInt", inner, "Int", 3) ; RGN_XOR = 3
120 | DeleteObject(inner)
121 | }
122 |
123 | DllCall("SetWindowRgn", "UInt", ClickRippleWindowHwnd, "UInt", finalRegion, "UInt", true)
124 | WinSet,Transparent , %CurrentRippleAlpha%, % "ahk_id " ClickRippleWindowHwnd
125 | DeleteObject(finalRegion)
126 | ; Clone the current region and save it for the next usage
127 | local clonedRegion := DllCall("CreateRectRgn", "int", 0, "int", 0, "int", 0, "int", 0)
128 | local RegionType := DllCall("GetWindowRgn", "uint", ClickRippleWindowHwnd, "uint", clonedRegion)
129 | AlreadyCreatedRegionForRipples[regionKey] := clonedRegion
130 | CurrentRippleAlpha := CurrentRippleAlpha + RippleAlphaStep
131 | if (RippleEventParams.rippleDiameterEnd > RippleEventParams.rippleDiameterStart)
132 | {
133 | CurrentRippleDiameter := CurrentRippleDiameter + Abs(RippleEventParams.rippleDiameterStep)
134 | }
135 | else
136 | {
137 | CurrentRippleDiameter := CurrentRippleDiameter - Abs(RippleEventParams.rippleDiameterStep)
138 | }
139 | AlreadyDrawnRipples++
140 | if (AlreadyDrawnRipples >= TotalCountOfRipples)
141 | {
142 | IsStillDrawingRipples := False
143 | SetTimer, DRAW_RIPPLE, Off
144 | Gui, MouseClickRippleWindow: Hide
145 | ; Trigger the function again to check if there are other mouse click events waiting to be drawn
146 | CheckToDrawNextClickEvent()
147 | }
148 | Return
149 | }
150 |
151 | SetupMouseClickRipple()
152 |
--------------------------------------------------------------------------------
/MouseClickSound.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yunyi-the-coder/mouse-cursor-highlight-windows/35926785875d56bb142eea24838780c599c15ccb/MouseClickSound.wav
--------------------------------------------------------------------------------
/MouseSpotlight.ahk:
--------------------------------------------------------------------------------
1 | #Include ./Utils.ahk
2 | #SingleInstance, Force
3 | #NoEnv
4 | #MaxThreadsPerHotkey 3
5 | #installmousehook
6 | #MaxHotkeysPerInterval 100
7 |
8 | SetBatchLines, -1
9 | SetWinDelay, -1
10 | CoordMode, mouse, screen
11 | SetWorkingDir, %A_ScriptDir%
12 |
13 | ClickEvents := []
14 |
15 | SetupMouseSpotlight()
16 | {
17 | global
18 | SETTINGS := ReadConfigFile("settings.ini")
19 | InitializeSpotlightGUI()
20 | }
21 |
22 | InitializeSpotlightGUI(){
23 | global CursorSpotlightHwnd, SETTINGS
24 | if (SETTINGS.cursorSpotlight.enabled == True)
25 | {
26 | global CursorSpotlightDiameter := SETTINGS.cursorSpotlight.spotlightDiameter
27 | spotlightOuterRingWidth := SETTINGS.cursorSpotlight.spotlightOuterRingWidth
28 | Gui, CursorSpotlightWindow: +HwndCursorSpotlightHwnd +AlwaysOnTop -Caption +ToolWindow +E0x20 ;+E0x20 click thru
29 | Gui, CursorSpotlightWindow: Color, % SETTINGS.cursorSpotlight.spotlightColor
30 | Gui, CursorSpotlightWindow: Show, x0 y0 w%CursorSpotlightDiameter% h%CursorSpotlightDiameter% NA
31 | WinSet, Transparent, % SETTINGS.CursorSpotlight.spotlightOpacity, ahk_id %CursorSpotlightHwnd%
32 | ; Create a ring region to highlight the cursor
33 | finalRegion := DllCall("CreateEllipticRgn", "Int", 0, "Int", 0, "Int", CursorSpotlightDiameter, "Int", CursorSpotlightDiameter)
34 | if (spotlightOuterRingWidth < CursorSpotlightDiameter/2)
35 | {
36 | inner := DllCall("CreateEllipticRgn", "Int", spotlightOuterRingWidth, "Int", spotlightOuterRingWidth, "Int", CursorSpotlightDiameter-spotlightOuterRingWidth, "Int", CursorSpotlightDiameter-spotlightOuterRingWidth)
37 | DllCall("CombineRgn", "UInt", finalRegion, "UInt", finalRegion, "UInt", inner, "Int", 3) ; RGN_XOR = 3
38 | DllCall("DeleteObject", UInt, inner)
39 | }
40 | DllCall("SetWindowRgn", "UInt", CursorSpotlightHwnd, "UInt", finalRegion, "UInt", true)
41 | SetTimer, DrawSpotlight, 10
42 | Return
43 |
44 | DrawSpotlight:
45 | ; SETTINGS.cursorSpotlight.enabled can be changed by other script such as Annotation.ahk
46 | if (SETTINGS.cursorSpotlight.enabled == True)
47 | {
48 | MouseGetPos, X, Y
49 | X -= CursorSpotlightDiameter / 2
50 | Y -= CursorSpotlightDiameter / 2
51 | WinMove, ahk_id %CursorSpotlightHwnd%, , %X%, %Y%
52 | WinSet, AlwaysOnTop, On, ahk_id %CursorSpotlightHwnd%
53 | }
54 | else
55 | {
56 | WinMove, ahk_id %CursorSpotlightHwnd%, , -999999999, -999999999
57 | }
58 |
59 | Return
60 | }
61 | }
62 |
63 | SetupMouseSpotlight()
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Check the following video for tutorial on how to use this script
2 | [](https://youtu.be/lpEFMcIbyjg "Tutorial")
3 |
4 |
5 | This open source code is a handy AutoHotkey script designed for presenters and teachers who need to highlight their mouse pointer. The script has these features: ***highlighting your mouse cursor***, ***keystroke visualization*** and ***on-screen annotation***:
6 |
7 | ### Mouse Highlight
8 | Highlight mouse pointer with a circle or spotlight, and when you click the mouse button it will display a ring (ripple) animation, so that your audience can follow your mouse easily.
9 |
10 | ### Keystroke OSD (Keystroke Visualization)
11 | It has a Keystroke OSD that can display the shortcut keys that you have pressed, which can make your audience easier to understand your presentation.
12 |
13 | ### On-Screen Annotation
14 | You can use your mouse pointer to annotate any part of the screen. The color and pen of the annotation is customizable through the settings file.
15 |
16 | ### Show KeyStroke ODS GUI on multiple monitors at same time
17 |
18 | To add KeyStroke ODS to a second monitor, you can launch two processes of this program - one process will render the KeyStroke OSD on the main monitor, and also render the click ripples, spotlight and annotation across all monitors. The other process will render the KeyStroke OSD on the second monitor. Here are the steps:
19 |
20 | * Step 1:
21 |
22 | Copy all files of this program into a new folder.
23 |
24 |
25 | * Step 2:
26 |
27 | Go to the new folder and open "settings.ini", then find these lines:
28 |
29 | [cursorSpotlight]
30 | enabled=True
31 | .. ...
32 | [cursorLeftClickRippleEffect]
33 | enabled=True
34 | ... ...
35 | [cursorMiddleClickRippleEffect]
36 | enabled=True
37 | ... ...
38 | [cursorRightClickRippleEffect]
39 | enabled=True
40 | ... ...
41 | [keyStrokeOSD]
42 | enabled=True
43 | ... ...
44 | [annotation]
45 | enabled=True
46 |
47 | Change all enabled=True to enabled=False except the one in [keyStrokeOSD] section:
48 |
49 | [cursorSpotlight]
50 | enabled=False
51 | .. ...
52 | [cursorLeftClickRippleEffect]
53 | enabled=False
54 | ... ...
55 | [cursorMiddleClickRippleEffect]
56 | enabled=False
57 | ... ...
58 | [cursorRightClickRippleEffect]
59 | enabled=False
60 | ... ...
61 | [keyStrokeOSD]
62 | enabled=True
63 | ... ...
64 | [annotation]
65 | enabled=False
66 |
67 |
68 | * Step 3:
69 |
70 | In the "setting.ini" file, find these two lines in the [keyStrokeOSD] section:
71 |
72 | osdWindowPositionX=760
73 | osdWindowPositionY=540
74 |
75 | And then change them to the values that fit into your second monitor (sometimes you may have to try negative values to move the OSD onto your second monitor).
76 |
77 |
78 | * Step 4:
79 |
80 | Save the "setting.ini" file, and then double click the "Start.ahk" to check if the OSD is displayed at the correct position. If not, go back to step 3 to adjust the values -- try increasing or decreasing the values bit by bit to check which direction the OSD window is moving toward and then try guessing the proper values for your second monitor.
81 |
82 |
83 | * Step 5:
84 |
85 | Go back to the original folder of this program and double click "Start.ahk" to launch the second process for your main monitor.
86 |
87 |
88 |
89 |
90 |
91 |
92 | # Buy Me a Coffee?
93 | ### Donate via [PayPal](https://www.paypal.com/donate/?business=JY46S54HME9LQ&no_recurring=0¤cy_code=USD)
--------------------------------------------------------------------------------
/Start.ahk:
--------------------------------------------------------------------------------
1 | #Include ./MouseSpotlight.ahk
2 | #Include ./KeyStrokeOSD.ahk
3 | #Include ./Annotation.ahk
4 | ; There are two ahk files which can display a ripple effect for mouse clicks: MouseClickRippleWithGui.ahk and MouseClickRipple.ahk.
5 | ; The MouseClickRippleWithGui.ahk uses window regions to simulate the ripples and MouseClickRipple.ahk uses LayeredWindow and gdi to draw ripples.
6 | ; The performance of MouseClickRipple.ahk is a little better than MouseClickRippleWithGui.ahk, and because gdi has an anti-aliasing mode, the circles
7 | ; drawn by MouseClickRipple.ahk are rounder than those drawn by MouseClickRippleWithGui.ahk.
8 | ; However, in my testing MouseClickRipple.ahk may cause problems to some programs -- they cannot respond to mouse clicking events correctly (e.g. DaVinci Resolve 17).
9 | ; But I haven't run into issues with MouseClickRippleWithGui.ahk, so MouseClickRippleWithGui.ahk seems to have better compatibility than MouseClickRipple.ahk.
10 | ; #Include ./MouseClickRippleWithGui.ahk
11 | #Include ./MouseClickRipple.ahk
12 |
--------------------------------------------------------------------------------
/Utils.ahk:
--------------------------------------------------------------------------------
1 | ReadConfigFile(configFileName)
2 | {
3 | result := {}
4 | IniRead, allSections, %configFileName%
5 | allSections := StrSplit(allSections, "`n", "`r")
6 | for index, oneSection in allSections {
7 | if (oneSection = "comment")
8 | {
9 | continue
10 | }
11 | IniRead, textInOneSection, %configFileName%, %oneSection%
12 | items := []
13 | lines := StrSplit(textInOneSection, ["`n"], "`r")
14 | for notInUse, oneLine in lines
15 | {
16 | if (SubStr(oneLine, 1, 1) == "#")
17 | {
18 | ;Ignore comments in the ini file
19 | continue
20 | }
21 | keyAndValue := StrSplit(oneLine, ["="], "`r")
22 | items.Push(keyAndValue[1])
23 | items.Push(keyAndValue[2])
24 | }
25 |
26 | for index2, oneItem in items
27 | {
28 | if(Mod(index2, 2) == 0 )
29 | {
30 | if (oneItem = "True")
31 | {
32 | items[index2] := True
33 | }
34 | else if (oneItem = "False")
35 | {
36 | items[index2] := False
37 | }
38 | }
39 | }
40 | result[oneSection] := Object(items*)
41 | }
42 | Return result
43 | }
44 |
45 | Max(num*){
46 | max := -9223372036854775807
47 | Loop % num.MaxIndex()
48 | max := (num[A_Index] > max) ? num[A_Index] : max
49 | Return max
50 | }
51 |
52 | Min(num*){
53 | min := 9223372036854775807
54 | Loop % num.MaxIndex()
55 | min := (num[A_Index] < min) ? num[A_Index] : min
56 | Return min
57 | }
58 |
59 | HasVal(haystack, needle) {
60 | if !(IsObject(haystack)) || (haystack.Length() = 0)
61 | return 0
62 | for index, value in haystack
63 | if (value = needle)
64 | return index
65 | return 0
66 | }
67 |
68 |
69 | ; *************** The following are functions related gdi plus ***************
70 | ReleaseDC(hdc, hwnd=0)
71 | {
72 | Ptr := A_PtrSize ? "UPtr" : "UInt"
73 |
74 | return DllCall("ReleaseDC", Ptr, hwnd, Ptr, hdc)
75 | }
76 |
77 | GetDC(hwnd=0)
78 | {
79 | return DllCall("GetDC", A_PtrSize ? "UPtr" : "UInt", hwnd)
80 | }
81 |
82 | Gdip_Startup()
83 | {
84 | Ptr := A_PtrSize ? "UPtr" : "UInt"
85 |
86 | if !DllCall("GetModuleHandle", "str", "gdiplus", Ptr)
87 | DllCall("LoadLibrary", "str", "gdiplus")
88 | VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0), si := Chr(1)
89 | DllCall("gdiplus\GdiplusStartup", A_PtrSize ? "UPtr*" : "uint*", pToken, Ptr, &si, Ptr, 0)
90 | return pToken
91 | }
92 |
93 | CreateDIBSection(w, h, hdc="", bpp=32, ByRef ppvBits=0)
94 | {
95 | Ptr := A_PtrSize ? "UPtr" : "UInt"
96 |
97 | hdc2 := hdc ? hdc : GetDC()
98 | VarSetCapacity(bi, 40, 0)
99 |
100 | NumPut(w, bi, 4, "uint")
101 | , NumPut(h, bi, 8, "uint")
102 | , NumPut(40, bi, 0, "uint")
103 | , NumPut(1, bi, 12, "ushort")
104 | , NumPut(0, bi, 16, "uInt")
105 | , NumPut(bpp, bi, 14, "ushort")
106 |
107 | hbm := DllCall("CreateDIBSection"
108 | , Ptr, hdc2
109 | , Ptr, &bi
110 | , "uint", 0
111 | , A_PtrSize ? "UPtr*" : "uint*", ppvBits
112 | , Ptr, 0
113 | , "uint", 0, Ptr)
114 |
115 | if !hdc
116 | ReleaseDC(hdc2)
117 | return hbm
118 | }
119 |
120 | CreateCompatibleDC(hdc=0)
121 | {
122 | return DllCall("CreateCompatibleDC", A_PtrSize ? "UPtr" : "UInt", hdc)
123 | }
124 |
125 | SelectObject(hdc, hgdiobj)
126 | {
127 | Ptr := A_PtrSize ? "UPtr" : "UInt"
128 |
129 | return DllCall("SelectObject", Ptr, hdc, Ptr, hgdiobj)
130 | }
131 |
132 | Gdip_GraphicsFromHDC(hdc)
133 | {
134 | DllCall("gdiplus\GdipCreateFromHDC", A_PtrSize ? "UPtr" : "UInt", hdc, A_PtrSize ? "UPtr*" : "UInt*", pGraphics)
135 | return pGraphics
136 | }
137 |
138 | Gdip_SetSmoothingMode(pGraphics, SmoothingMode)
139 | {
140 | return DllCall("gdiplus\GdipSetSmoothingMode", A_PtrSize ? "UPtr" : "UInt", pGraphics, "int", SmoothingMode)
141 | }
142 |
143 | Gdip_GraphicsClear(pGraphics, ARGB=0x00ffffff)
144 | {
145 | return DllCall("gdiplus\GdipGraphicsClear", A_PtrSize ? "UPtr" : "UInt", pGraphics, "int", ARGB)
146 | }
147 |
148 | Gdip_CreatePen(ARGB, w)
149 | {
150 | DllCall("gdiplus\GdipCreatePen1", "UInt", ARGB, "float", w, "int", 2, A_PtrSize ? "UPtr*" : "UInt*", pPen)
151 | return pPen
152 | }
153 |
154 | Gdip_DrawEllipse(pGraphics, pPen, x, y, w, h)
155 | {
156 | Ptr := A_PtrSize ? "UPtr" : "UInt"
157 |
158 | return DllCall("gdiplus\GdipDrawEllipse", Ptr, pGraphics, Ptr, pPen, "float", x, "float", y, "float", w, "float", h)
159 | }
160 |
161 | Gdip_DrawLine(pGraphics, pPen, x1, y1, x2, y2)
162 | {
163 | Ptr := A_PtrSize ? "UPtr" : "UInt"
164 |
165 | return DllCall("gdiplus\GdipDrawLine"
166 | , Ptr, pGraphics
167 | , Ptr, pPen
168 | , "float", x1
169 | , "float", y1
170 | , "float", x2
171 | , "float", y2)
172 | }
173 |
174 |
175 | Gdip_DrawLines(pGraphics, pPen, Points)
176 | {
177 | Ptr := A_PtrSize ? "UPtr" : "UInt"
178 | StringSplit, Points, Points, |
179 | VarSetCapacity(PointF, 8*Points0)
180 | Loop, %Points0%
181 | {
182 | StringSplit, Coord, Points%A_Index%, `,
183 | NumPut(Coord1, PointF, 8*(A_Index-1), "float"), NumPut(Coord2, PointF, (8*(A_Index-1))+4, "float")
184 | }
185 | return DllCall("gdiplus\GdipDrawLines", Ptr, pGraphics, Ptr, pPen, Ptr, &PointF, "int", Points0)
186 | }
187 |
188 | Gdip_DeletePen(pPen)
189 | {
190 | return DllCall("gdiplus\GdipDeletePen", A_PtrSize ? "UPtr" : "UInt", pPen)
191 | }
192 |
193 | UpdateLayeredWindow(hwnd, hdc, x="", y="", w="", h="", Alpha=255)
194 | {
195 | Ptr := A_PtrSize ? "UPtr" : "UInt"
196 |
197 | if ((x != "") && (y != ""))
198 | VarSetCapacity(pt, 8), NumPut(x, pt, 0, "UInt"), NumPut(y, pt, 4, "UInt")
199 |
200 | if (w = "") ||(h = "")
201 | WinGetPos,,, w, h, ahk_id %hwnd%
202 |
203 | return DllCall("UpdateLayeredWindow"
204 | , Ptr, hwnd
205 | , Ptr, 0
206 | , Ptr, ((x = "") && (y = "")) ? 0 : &pt
207 | , "int64*", w|h<<32
208 | , Ptr, hdc
209 | , "int64*", 0
210 | , "uint", 0
211 | , "UInt*", Alpha<<16|1<<24
212 | , "uint", 2)
213 | }
214 |
215 | BitBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, Raster="")
216 | {
217 | Ptr := A_PtrSize ? "UPtr" : "UInt"
218 |
219 | return DllCall("gdi32\BitBlt"
220 | , Ptr, dDC
221 | , "int", dx
222 | , "int", dy
223 | , "int", dw
224 | , "int", dh
225 | , Ptr, sDC
226 | , "int", sx
227 | , "int", sy
228 | , "uint", Raster ? Raster : 0x00CC0020)
229 | }
230 |
231 | CreateCompatibleBitmap(hdc, w, h)
232 | {
233 | return DllCall("gdi32\CreateCompatibleBitmap", A_PtrSize ? "UPtr" : "UInt", hdc, "int", w, "int", h)
234 | }
235 |
236 | DeleteDC(hdc)
237 | {
238 | return DllCall("DeleteDC", A_PtrSize ? "UPtr" : "UInt", hdc)
239 | }
240 |
241 | DeleteObject(hObject)
242 | {
243 | return DllCall("DeleteObject", A_PtrSize ? "UPtr" : "UInt", hObject)
244 | }
245 |
246 | Gdip_DrawRectangle(pGraphics, pPen, x, y, w, h)
247 | {
248 | Ptr := A_PtrSize ? "UPtr" : "UInt"
249 |
250 | return DllCall("gdiplus\GdipDrawRectangle", Ptr, pGraphics, Ptr, pPen, "float", x, "float", y, "float", w, "float", h)
251 | }
--------------------------------------------------------------------------------
/settings.ini:
--------------------------------------------------------------------------------
1 | [comment]
2 | # If you enjoy this script, maybe you can consider making a donnation to this Hard Working Developer. Just a cup of coffee or tea should be good enough :).
3 | # Donation link: https://www.paypal.com/donate/?business=JY46S54HME9LQ&no_recurring=0&item_name=Buy+the+Hard+Working+Developer+a+cup+of+coffee+or+tea+%3A%29¤cy_code=USD
4 | [cursorSpotlight]
5 | enabled=True
6 | spotlightColor=0xF6D167
7 | spotlightDiameter=50
8 | spotlightOuterRingWidth=25
9 | spotlightOpacity=100
10 | [cursorLeftClickRippleEffect]
11 | enabled=True
12 | rippleColor=0xFF0000
13 | playClickSound=True
14 | rippleDiameterStart=20
15 | rippleDiameterEnd=120
16 | rippleDiameterStep=10
17 | rippleLineWidth=4
18 | rippleAlphaStart=255
19 | rippleAlphaEnd=128
20 | rippleRefreshInterval=25
21 | [cursorMiddleClickRippleEffect]
22 | enabled=True
23 | rippleColor=0xFFB344
24 | playClickSound=True
25 | rippleDiameterStart=20
26 | rippleDiameterEnd=120
27 | rippleDiameterStep=10
28 | rippleLineWidth=4
29 | rippleAlphaStart=255
30 | rippleAlphaEnd=128
31 | rippleRefreshInterval=25
32 | [cursorRightClickRippleEffect]
33 | enabled=True
34 | rippleColor=0x66DE93
35 | playClickSound=True
36 | rippleDiameterStart=20
37 | rippleDiameterEnd=120
38 | rippleDiameterStep=10
39 | rippleLineWidth=4
40 | rippleAlphaStart=255
41 | rippleAlphaEnd=128
42 | rippleRefreshInterval=25
43 | [keyStrokeOSD]
44 | enabled=True
45 | osdWindowHeight=50
46 | osdWindowWidth=400
47 | osdWindowPositionX=760
48 | osdWindowPositionY=540
49 | osdWindowBackgroundColor=0x000000
50 | osdWindowOpacity=120
51 | osdFontFamily=Verdana
52 | osdFontSize=32
53 | osdFontColor=0xFFFFFF
54 | osdDuration=2000
55 | osdHotkeyRegex=^(Ctrl|Alt|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|Shift\+Left|Shift\+Right|Shift\+Up|Shift\+Down|Enter|Shift\+F.)
56 | osdKeyChordsRegex=^Ctrl\+. (.|Ctrl\+.)$
57 | [annotation]
58 | enabled=True
59 | annotationLineDrawingToggleHotkey=^1
60 | annotationRectangleDrawingToggleHotkey=^2
61 | annotationClearDrawingHotkey=^3
62 | annotationLineWidth=10
63 | annotationLineColor=0xFF0000
64 | annotationLineAlpha=128
65 | annotationRectangleBorderWidth=10
66 | annotationRectangleBorderAlpha=128
67 | annotationRectangleBorderColor=0xFF0000
68 | annotationShowSpotlightWhenDrawing=True
69 |
70 |
--------------------------------------------------------------------------------