Back to AutoHotkey Tools

RadMode Tools

Automation scripts and utilities for radiology workflows

How to Use

Complete AutoHotkey v2 script for radiology workflow automation

  1. 1Download all three files and place them in the same folder. Double-click the Runner (.exe) to launch — a setup GUI window will appear.
  2. 2Configure your hotkeys and settings in the setup GUI. The launcher minimizes to the system tray after setup — close it to stop the script.
  3. 3Press Caps + F to enter Rads Mode for dictation shortcuts, and Caps + D to exit.
  4. 4Hold Alt + Z to trigger Smart Break Glass using the hammer icon. All scripts intended for EPIC, PowerScribe, and Visage workflows.

Quick Reference

Key shortcuts — full list available in the script GUI

Global Shortcuts

Caps LockTrackball Scroll (Hold)
Alt + ZSmart Break Glass
Shift + Alt + ZForce Manual Entry
Alt + PLogin site
Caps + FEnable Rads Mode
Caps + DExit Rads Mode

Rads Mode — Dictation

DToggle dictation
HSave draft (F9)
FNext field
GPrevious field
SBackspace
ANew line
QClose window (Ctrl+W)

Mouse & Pick Lists

RLeft click
EMiddle click
WRight click
Alt + 1–5Pick list choice

Visage viewer:

Z, X, C, VSend to Visage

Download Files

All three files are required — place them in the same folder

AutoHotkey Script
.ahk
RadMode.ahk
Download Script
Executable Runner
.exe
RadMode.exe
Download Runner
Hammer Icon
.png
hammer.png

Required for the "Break the Glass" automation — must be in the same folder as the script.

Download Icon
Full RadMode Automation Script
View the complete source code or copy it to your clipboard
1#Requires Autohotkey v2
2#SingleInstance Force
3CoordMode "ToolTip", "Screen"
4CoordMode "Mouse", "Screen" 
5CoordMode "Pixel", "Screen" 
6SetTitleMatchMode 2
7
8; ==============================================================================
9; Version & Configuration Defaults
10; ==============================================================================
11global VERSION := "v22.19" ; Incremented version to reflect global clipboard injection
12global INI_FILE := "rad_helper_settings.ini"
13
14; --- User Variables (Runtime) ---
15global USER := ""
16global PASS := ""
17global KEEPAWAKE := 0
18global TRACKBALL_SCROLL := 1
19global DEBUG_MODE := 0
20
21; --- Advanced Settings (Loaded from INI) ---
22global BTG_TAB_DELAY := 80        
23global BTG_POPUP_DELAY := 500     
24global SCROLL_THRESHOLD := 15     
25global SCROLL_LOCK_RATIO := 2.0   
26
27; --- Marker Customization ---
28global MARKER_SIZE := 5           
29global MARKER_COLOR := "FF0000"   
30global MARKER_ALPHA := 180        
31
32; --- Reliability Settings ---
33SetKeyDelay 10, 10
34global LOGIN_STEP_DELAY := 150    
35
36; --- Window Titles ---
37global EPIC_TITLE := "Prod"
38global PS := "PowerScribe"
39global VISAGE := "Visage"
40global VISAGE_LOGIN := "Client Login ahk_class Qt51510QWindowIcon"
41
42global RADSMODE := false
43global MarkerGui := unset
44
45; ==============================================================================
46; Initialization
47; ==============================================================================
48LoadSettings() 
49CreateCursorMarker()
50
51; ==============================================================================
52; Main GUI
53; ==============================================================================
54myGui := Gui()
55myGui.Title := "Radiology Helper " . VERSION
56myGui.BackColor := "FBFBFB"
57myGui.SetFont("s10", "Segoe UI")
58
59; --- Login Section ---
60myGui.Add("GroupBox", "x20 y10 w460 h80", "Login Credentials")
61myGui.Add("Text", "x40 y40 w70 h25 +0x200", "Username:")
62Edit1 := myGui.Add("Edit", "x115 y40 w130 h25", USER)
63
64myGui.Add("Text", "x255 y40 w70 h25 +0x200", "Password:")
65Edit2 := myGui.Add("Edit", "x330 y40 w110 h25 +Password", PASS)
66
67; --- Action Button ---
68LaunchButton := myGui.Add("Button", "x20 y100 w260 h50 +Default", "Launch Visage System")
69LaunchButton.OnEvent("Click", LaunchButtonHandler)
70LaunchButton.SetFont("s11 bold")
71
72; --- Config Button ---
73ConfigButton := myGui.Add("Button", "x290 y100 w190 h50", "Advanced Config")
74ConfigButton.OnEvent("Click", OpenConfigMenu)
75
76; --- Options Section ---
77CheckBox1 := myGui.Add("CheckBox", "x40 y160 w150 h25 Checked" . KEEPAWAKE, "Prevent Sleep")
78CheckBox2 := myGui.Add("CheckBox", "x220 y160 w240 h25 Checked" . TRACKBALL_SCROLL, "Hold CapsLock to scroll")
79CheckBox3 := myGui.Add("CheckBox", "x40 y185 w150 h25 Checked" . DEBUG_MODE, "Debug Mode")
80
81; --- Divider ---
82myGui.Add("Text", "x20 y215 w460 h2 0x10")
83
84; --- Reference Guide (Reformatted) ---
85myGui.SetFont("s9", "Segoe UI")
86RefGroup := myGui.Add("GroupBox", "x20 y220 w460 h185", "Quick Reference Guide")
87
88; Column 1: Global
89myGui.SetFont("s9 w700")
90myGui.Add("Text", "x40 y245 w180", "Global Shortcuts")
91myGui.SetFont("s9 w400")
92myGui.Add("Text", "x40 y265 w200", "Caps + f : Rads Mode ON")
93myGui.Add("Text", "x40 y285 w200", "Caps + d : Rads Mode OFF")
94myGui.Add("Text", "x40 y305 w200", "Alt + p : Auto-Login Site")
95myGui.Add("Text", "x40 y325 w200", "Alt + z : Fast Break Glass")
96
97; Column 2: Rads Mode
98myGui.SetFont("s9 w700")
99myGui.Add("Text", "x260 y245 w180", "Rads Mode (Vim-Style)")
100myGui.SetFont("s9 w400")
101myGui.Add("Text", "x260 y265 w200", "d : Toggle Dictation")
102myGui.Add("Text", "x260 y285 w200", "h : Save Draft (F9)")
103myGui.Add("Text", "x260 y305 w200", "f / g : Next / Prev Field")
104myGui.Add("Text", "x260 y325 w200", "s / a : Backspace / Enter")
105myGui.Add("Text", "x260 y345 w200", "Alt + 1-5 : Pick List Choice")
106
107; Logic Note
108myGui.SetFont("s8 italic c555555")
109myGui.Add("Text", "x40 y370 w420", "Logic Note: Logins utilize a clipboard-injection method to instantly paste credentials, bypassing standard keystroke lag.")
110
111; Footer
112myGui.SetFont("s8 c777777")
113myGui.Add("Text", "x20 y415 w460 Center", "Note: Red dot indicator appears when Rads Mode is active.")
114
115myGui.Show("w500 h445")
116
117; ==============================================================================
118; Cursor Marker Logic
119; ==============================================================================
120CreateCursorMarker() {
121    global MarkerGui, MARKER_SIZE, MARKER_COLOR, MARKER_ALPHA
122    if IsSet(MarkerGui)
123        MarkerGui.Destroy()
124    MarkerGui := Gui("+AlwaysOnTop -Caption +ToolWindow +E0x20") 
125    MarkerGui.BackColor := MARKER_COLOR
126    D := MARKER_SIZE
127    WinSetRegion("0-0 W" D " H" D " R" D "-" D, MarkerGui.Hwnd)
128    MarkerGui.Opt("+LastFound")
129    WinSetTransparent(MARKER_ALPHA, MarkerGui.Hwnd)
130}
131
132UpdateMarkerPos() {
133    global RADSMODE, MarkerGui, MARKER_SIZE
134    if (RADSMODE) {
135        MouseGetPos(&mX, &mY)
136        MarkerGui.Show("x" (mX + 12) " y" (mY + 12) " w" MARKER_SIZE " h" MARKER_SIZE " NoActivate")
137    } else {
138        MarkerGui.Hide()
139    }
140}
141
142; ==============================================================================
143; Advanced Config Menu
144; ==============================================================================
145OpenConfigMenu(*) {
146    advGui := Gui()
147    advGui.Title := "Advanced Configuration"
148    advGui.SetFont("s9", "Segoe UI")
149
150    advGui.Add("GroupBox", "x10 y10 w380 h100", "Rads Mode Indicator (Dot)")
151    advGui.Add("Text", "x30 y35 w100 h20", "Dot Size (px):")
152    Ctrl_Size := advGui.Add("Edit", "x130 y32 w60 h20 Number", MARKER_SIZE)
153    advGui.Add("Text", "x210 y35 w80 h20", "Hex Color:")
154    Ctrl_Color := advGui.Add("Edit", "x290 y32 w80 h20", MARKER_COLOR)
155    advGui.Add("Text", "x30 y65 w100 h20", "Opacity (0-255):")
156    Ctrl_Alpha := advGui.Add("Edit", "x130 y62 w60 h20 Number", MARKER_ALPHA)
157
158    advGui.Add("GroupBox", "x10 y120 w380 h130", "Workflow Settings")
159    advGui.Add("Text", "x30 y145 w150 h20", "Tab Delay (ms):")
160    Ctrl_TabDelay := advGui.Add("Edit", "x240 y142 w80 h20 Number", BTG_TAB_DELAY)
161    advGui.Add("Text", "x30 y175 w150 h20", "Scroll Sensitivity:")
162    Ctrl_Sens := advGui.Add("Edit", "x240 y172 w80 h20 Number", SCROLL_THRESHOLD)
163    advGui.Add("Text", "x30 y205 w150 h20", "Axis Lock Ratio:")
164    Ctrl_Lock := advGui.Add("Edit", "x240 y202 w80 h20", SCROLL_LOCK_RATIO)
165
166    SaveBtn := advGui.Add("Button", "x90 y270 w100 h30", "Save")
167    CancelBtn := advGui.Add("Button", "x200 y270 w100 h30", "Cancel")
168    SaveBtn.OnEvent("Click", SaveHandler)
169    CancelBtn.OnEvent("Click", (*) => advGui.Destroy())
170    advGui.Show()
171
172    SaveHandler(*) {
173        global BTG_TAB_DELAY, SCROLL_THRESHOLD, SCROLL_LOCK_RATIO
174        global MARKER_SIZE, MARKER_COLOR, MARKER_ALPHA
175        MARKER_SIZE := Ctrl_Size.Value
176        MARKER_COLOR := Ctrl_Color.Value
177        MARKER_ALPHA := Ctrl_Alpha.Value
178        BTG_TAB_DELAY := Ctrl_TabDelay.Value
179        SCROLL_THRESHOLD := Ctrl_Sens.Value
180        SCROLL_LOCK_RATIO := Number(Ctrl_Lock.Value) 
181        SaveSettings() 
182        CreateCursorMarker()
183        advGui.Destroy()
184        MsgBox("Settings Saved!", "Configuration", "Iconi T1")
185    }
186}
187
188; ==============================================================================
189; INI Persistence Functions
190; ==============================================================================
191LoadSettings() {
192    global BTG_TAB_DELAY, SCROLL_THRESHOLD, SCROLL_LOCK_RATIO
193    global MARKER_SIZE, MARKER_COLOR, MARKER_ALPHA
194    if FileExist(INI_FILE) {
195        try {
196            BTG_TAB_DELAY := IniRead(INI_FILE, "Citrix", "TabDelay", 80)
197            SCROLL_THRESHOLD := IniRead(INI_FILE, "Physics", "Sensitivity", 15)
198            SCROLL_LOCK_RATIO := IniRead(INI_FILE, "Physics", "AxisLock", 2.0)
199            MARKER_SIZE := IniRead(INI_FILE, "Marker", "Size", 5)
200            MARKER_COLOR := IniRead(INI_FILE, "Marker", "Color", "FF0000")
201            MARKER_ALPHA := IniRead(INI_FILE, "Marker", "Alpha", 180)
202        }
203    }
204}
205
206SaveSettings() {
207    IniWrite(BTG_TAB_DELAY, INI_FILE, "Citrix", "TabDelay")
208    IniWrite(SCROLL_THRESHOLD, INI_FILE, "Physics", "Sensitivity")
209    IniWrite(SCROLL_LOCK_RATIO, INI_FILE, "Physics", "AxisLock")
210    IniWrite(MARKER_SIZE, INI_FILE, "Marker", "Size")
211    IniWrite(MARKER_COLOR, INI_FILE, "Marker", "Color")
212    IniWrite(MARKER_ALPHA, INI_FILE, "Marker", "Alpha")
213}
214
215; ==============================================================================
216; GUI Handlers
217; ==============================================================================
218UpdateGlobals(*) {
219    global USER := Edit1.Value, PASS := Edit2.Value, KEEPAWAKE := CheckBox1.Value
220    global TRACKBALL_SCROLL := CheckBox2.Value, DEBUG_MODE := CheckBox3.Value
221}
222LaunchButtonHandler(*) {
223    LaunchVisage()
224    myGui.Minimize()
225}
226CloseHandler(*) => ExitApp()
227
228; ==============================================================================
229; Core Logic
230; ==============================================================================
231SetTimer StopSleep, 100000
232SetTimer UpdateMarkerPos, 15 
233
234LaunchVisage() {
235    if WinExist(VISAGE) {
236        WinActivate(VISAGE)
237        return
238    }
239    if WinExist(VISAGE_LOGIN) {
240        VisageLoginSequence()
241        return
242    }
243    targetLnk := ""
244    loop files A_Desktop "\*Visage*.lnk" {
245        targetLnk := A_LoopFileFullPath
246        break
247    }
248    if (targetLnk == "") {
249        loop files A_DesktopCommon "\*Visage*.lnk" {
250            targetLnk := A_LoopFileFullPath
251            break
252        }
253    }
254    if (targetLnk) {
255        Run(targetLnk)
256    } else {
257        fallbackPath := "C:\Program Files\Visage Imaging\Visage 7.1\bin\arch-Win\vsclient.exe"
258        if FileExist(fallbackPath) {
259            Run(fallbackPath)
260        } else {
261            MsgBox("Could not find Visage executable.", "Error", "Icon!")
262            return
263        }
264    }
265    if WinWait(VISAGE_LOGIN,, 20) {
266        VisageLoginSequence()
267    }
268}
269
270VisageLoginSequence() {
271    global VISAGE_LOGIN, LOGIN_STEP_DELAY
272    global Edit1, Edit2 
273    
274    if !WinExist(VISAGE_LOGIN)
275        return
276    WinActivate(VISAGE_LOGIN)
277    if !WinWaitActive(VISAGE_LOGIN,, 3)
278        return
279    
280    local safeUser := Edit1.Value
281    local safePass := Edit2.Value
282    
283    SetKeyDelay 10, 10
284    
285    WinGetPos(,, &winW, &winH, VISAGE_LOGIN)
286    oldMode := A_CoordModeMouse
287    CoordMode "Mouse", "Window"
288    
289    MouseMove(winW * 0.60, winH * 0.40) 
290    Sleep(LOGIN_STEP_DELAY)
291    Click()
292    Sleep(LOGIN_STEP_DELAY)
293
294    ; Save the current clipboard contents to memory
295    ClipSaved := ClipboardAll()
296    A_Clipboard := "" 
297
298    SendEvent("^a{Delete}")
299    Sleep(LOGIN_STEP_DELAY)
300    A_Clipboard := "VISAGE.NYUMC.ORG"
301    Sleep(50)
302    Send("^v")
303    Sleep(LOGIN_STEP_DELAY)
304    SendEvent("{Tab}")
305    Sleep(LOGIN_STEP_DELAY)
306
307    SendEvent("^a{Delete}")
308    Sleep(LOGIN_STEP_DELAY)
309    A_Clipboard := safeUser
310    Sleep(50)
311    Send("^v")
312    Sleep(LOGIN_STEP_DELAY)
313    SendEvent("{Tab}")
314    Sleep(LOGIN_STEP_DELAY)
315
316    A_Clipboard := safePass
317    Sleep(50)
318    Send("^v")
319    Sleep(LOGIN_STEP_DELAY + 200) 
320
321    SendEvent("{Enter}")
322    
323    ; Wait slightly before restoring so the final paste completes
324    Sleep(150)
325    A_Clipboard := ClipSaved
326    
327    CoordMode "Mouse", oldMode
328}
329
330; --- Fast Break Glass (Clipboard Injection) ---
331fastBreakGlass(*) {
332    global BTG_TAB_DELAY, Edit2
333
334    ; Save the current clipboard contents to memory
335    ClipSaved := ClipboardAll()
336    A_Clipboard := "" 
337
338    ; Inject the Indication
339    A_Clipboard := "Provision"
340    Sleep(50) ; Brief pause allows Windows clipboard to update
341    Send("^v")
342    Sleep(BTG_TAB_DELAY)
343    
344    Send("{Tab}")
345    Sleep(BTG_TAB_DELAY)
346    Send("{Tab}")
347    Sleep(BTG_TAB_DELAY)
348    
349    ; Inject the Password
350    A_Clipboard := Edit2.Value
351    Sleep(50) 
352    Send("^v")
353    Sleep(BTG_TAB_DELAY)
354    
355    ; Accept
356    Send("!a")
357    
358    ; Wait slightly before restoring so the final paste completes
359    Sleep(150)
360    A_Clipboard := ClipSaved
361}
362
363loginSite() {
364    global Edit1, Edit2
365    
366    ClipSaved := ClipboardAll()
367    A_Clipboard := ""
368
369    A_Clipboard := Edit1.Value
370    Sleep(50)
371    Send("^v")
372    Sleep(200)
373    Send("{Tab}")
374    Sleep(200)
375
376    A_Clipboard := Edit2.Value
377    Sleep(50)
378    Send("^v")
379    Sleep(200)
380    Send("{Enter}")
381
382    Sleep(150)
383    A_Clipboard := ClipSaved
384}
385
386; ==============================================================================
387; CAPSLOCK SCROLL
388; ==============================================================================
389TrackballScroll(*) {
390    global SCROLL_THRESHOLD, SCROLL_LOCK_RATIO
391    oldMode := A_CoordModeMouse
392    CoordMode "Mouse", "Screen"
393    MouseGetPos(&startX, &startY)
394    bankX := 0, bankY := 0
395    Loop {
396        if !GetKeyState("CapsLock", "P")
397            break
398        MouseGetPos(&currentX, &currentY)
399        moveX := currentX - startX, moveY := currentY - startY
400        if (moveX != 0 || moveY != 0) {
401            DllCall("SetCursorPos", "int", startX, "int", startY)
402        }
403        bankX += moveX, bankY += moveY
404        absX := Abs(bankX), absY := Abs(bankY)
405        if (absX > (absY * SCROLL_LOCK_RATIO)) {
406            if (absX >= SCROLL_THRESHOLD) {
407                ticks := Integer(absX / SCROLL_THRESHOLD)
408                direction := (bankX > 0) ? "{WheelRight}" : "{WheelLeft}"
409                Loop ticks
410                    Send(direction)
411                bankX := Mod(bankX, SCROLL_THRESHOLD), bankY := 0
412            }
413        } else if (absY > absX) {
414            if (absY >= SCROLL_THRESHOLD) {
415                ticks := Integer(absY / SCROLL_THRESHOLD)
416                direction := (bankY > 0) ? "{WheelDown}" : "{WheelUp}"
417                Loop ticks
418                    Send(direction)
419                bankY := Mod(bankY, SCROLL_THRESHOLD), bankX := 0
420            }
421        }
422        Sleep(5)
423    }
424    CoordMode "Mouse", oldMode
425}
426
427; ==============================================================================
428; UTILS & HOTKEYS
429; ==============================================================================
430GetTargetControl() {
431    global PS
432    largestArea := 0, bestControl := ""
433    
434    ; STRATEGY: Find the largest visible RichEdit control first.
435    try {
436        ctrlList := WinGetControls(PS)
437        for ctrl in ctrlList {
438            if InStr(ctrl, "RICHEDIT") {
439                try {
440                    if ControlGetVisible(ctrl, PS) {
441                        ControlGetPos(,, &w, &h, ctrl, PS)
442                        if (w * h > largestArea) {
443                            largestArea := w * h, bestControl := ctrl
444                        }
445                    }
446                }
447            }
448        }
449    }
450    
451    if (bestControl != "")
452        return bestControl
453        
454    try {
455        focusedCtrl := ControlGetFocus(PS)
456        if InStr(focusedCtrl, "RICHEDIT")
457            return focusedCtrl
458    }
459    
460    return ""
461}
462
463ActivateAndSend(key, targetSpecificControl := true) {
464    global PS
465    if WinExist(PS) {
466        if !WinActive(PS) {
467            WinActivate(PS), WinWaitActive(PS,, 1), Sleep(50)
468        }
469        target := targetSpecificControl ? GetTargetControl() : ""
470        if (target)
471            ControlSend(key, target, PS)
472        else
473            SendEvent(key)
474    }
475}
476
477StopSleep(*) {
478    if (KEEPAWAKE) {
479        oldMode := A_CoordModeMouse
480        CoordMode("Mouse", "Screen")
481        MouseGetPos(&cX, &cY)
482        MouseMove(cX + 1, cY, 0), MouseMove(cX, cY, 0)
483        CoordMode("Mouse", oldMode)
484    }
485}
486
487pick(num) {
488    global PS
489    MouseGetPos &startX, &startY
490    CoordMode "Mouse", "Client" 
491    heightRow := 17
492    if WinExist(PS) {
493        try {
494            for ctrl in WinGetControls(PS) {
495                try {
496                    txt := ControlGetText(ctrl, PS)
497                } catch {
498                    continue
499                }
500                if (txt == "Pick List Choices") {
501                    ControlGetPos(&x, &y, &w, &h, ctrl, PS)
502                    targetY := y + Integer(heightRow/2) + (heightRow * num)
503                    targetX := x + 20
504                    if !WinActive(PS) {
505                        WinActivate(PS)
506                        WinWaitActive(PS,, 1)
507                    }
508                    Click(targetX, targetY)
509                    Sleep(50)
510                    Click(targetX, targetY)
511                    CoordMode "Mouse", "Screen"
512                    MouseMove(startX, startY)
513                    Sleep(50)
514                    ActivateAndSend("{Tab}", true)
515                    return
516                }
517            }
518        }
519    }
520    CoordMode "Mouse", "Screen"
521}
522
523; -- Common Rads Logic --
524toggleDictation(*) => ActivateAndSend("{F4}", false)
525saveDraft(*)       => ActivateAndSend("{F9}", false)
526nextField(*)       => ActivateAndSend("{Tab}", true)
527prevField(*)       => ActivateAndSend("+{Tab}", true)
528backspace(*)       => ActivateAndSend("{Backspace}", true)
529newLine(*)         => ActivateAndSend("{Enter}", true)
530
531enableRadsMode(*) {
532    global RADSMODE := true
533}
534exitRadsMode(*) {
535    global RADSMODE := false
536}
537
538#HotIf TRACKBALL_SCROLL
539    $CapsLock::TrackballScroll()
540#HotIf
541
542#HotIf GetKeyState("CapsLock", "P")
543    f:: enableRadsMode()
544    d:: exitRadsMode()
545#HotIf
546
547; -- Global Hotkeys --
548!z:: fastBreakGlass()
549!p:: loginSite()
550
551#HotIf RADSMODE
552    r::LButton
553    e::MButton
554    w::RButton
555    $d:: toggleDictation()
556    $h:: saveDraft()
557    $f:: nextField()
558    $g:: prevField()
559    $s:: backspace()
560    $a:: newLine()
561    $q:: Send("^w")
562    
563    ; -- Pick List Hotkeys --
564    $!1:: pick(1)
565    $!2:: pick(2)
566    $!3:: pick(3)
567    $!4:: pick(4)
568    $!5:: pick(5)
569
570    $z:: (WinActive(PS) ? "" : Send("z"))
571    $x:: (WinActive(PS) ? "" : Send("x"))
572    $c:: (WinActive(PS) ? "" : Send("c"))
573    $v:: (WinActive(PS) ? "" : Send("v"))
574#HotIf

This is the full source code of the RadMode.ahk file. You can copy this into a new .ahk file or download the file directly using the button above.