Categories


Authors

Windows apps for an efficient workflow

Intro

For some reason, I have an absolute drive for efficiency in almost everything that I do so, over the years, I’ve honed my Windows workflow to be insanely efficient and I thought I’d share some of the main ways that I’ve managed to do so.

A lot of this software I’ve mentioned previously in my blog posts “Great software” and “Great software 2” but I’ve never really posted specific details about my configuration until now.



AutoHotkey

AutoHotkey is a general Windows automation tool that I’ve only recently come to appreciate just how powerful it is, largely because it exposes some pretty low-level Win32 / Windows API functions in a relatively user-friendly way.

A lot of the things I wanted to accomplish I couldn’t find a solution for online so, as usual, I created almost all of these solutions from scratch myself.

Below are some excerpts from my main script that you can simply copy and paste into a .AHK file and run yourself. Each is quite long because all pre-requisites (modes, functions, etc) are present so that they’re completely self-contained / standalone.

Note: I’ve noticed that, when editing in Notepad++, the encoding needs to be UTF-8 with BOM in Notepad and UCS-2 LE BOM in Notepad++ for certain things like regexes and bullet point characters to work properly for some reason.

Notepad(++): Reload edited AHK script

While not the most objectively useful, this is a small and very useful addition when making frequent changes to a single, main script (which you may very well be doing when you start reading the below) because you don’t have to right-click on the System Tray icon and click on Reload This Script every time.

#If (WinActive("ahk_class Notepad++") Or WinActive("ahk_class Notepad"))
	; Ctrl and S → Save changes and, if an AutoHotkey script, reload it
	$^s::
		SendInput, ^s
		
		WinGetTitle, WindowTitle
		If (InStr(WindowTitle, ".ahk")){
			Reload
		}
	Return
#If 

File Explorer: Automatically work around Windows' restricted characters

This is an enhanced, automated and contextual version of what I posted at https://mythofechelon.co.uk/blog/2020/3/6/how-to-work-around-windows-restricted-characters#the-workaround.

GroupAdd, FileExplorer, ahk_class CabinetWClass ; Normal window
GroupAdd, FileExplorer, ahk_class #32770, ShellView ; Save as / open window
GroupAdd, FileExplorer, ahk_class WorkerW ; Desktop view

Replace_InvalidCharacters(OriginalString){
	; The following only gets the number of quotation marks - it doesn't make any changes
	StrReplace(OriginalString, """", , QuotationMarkCount)
	
	NewString := OriginalString
	LoopCount := 0
	
	Loop {
		LoopCount++
		
		; The following, alternating method is better for things like '"I'm afraid", he said, "I must say goodbye"'
		/*
		If (QuotationMarksOpened == False){
			NewString := StrReplace(NewString, """", "“", , 1)
			
			QuotationMarksOpened := True
		} Else If (QuotationMarksOpened == True){
			NewString := StrReplace(NewString, """", "”", , 1)
			
			QuotationMarksOpened := False
		}
		*/
		
		; The following, half-and-half method is better for things like '"Quoted file name contains "something" in quotes"'
		If (LoopCount <= (QuotationMarkCount / 2)){
			NewString := StrReplace(NewString, """", "“", , 1)
		} Else {
			NewString := StrReplace(NewString, """", "”", , 1)
		}
		
		If (LoopCount == 4){
			Break
		}
	}
	
	NewString := StrReplace(NewString, "\", "⧵")
	NewString := StrReplace(NewString, "/", " ∕ ")
	NewString := StrReplace(NewString, ":", "꞉")
	NewString := StrReplace(NewString, "*", "⁎")
	NewString := StrReplace(NewString, "?", "?")
	NewString := StrReplace(NewString, "<", "<")
	NewString := StrReplace(NewString, ">", ">")
	NewString := StrReplace(NewString, "|", "⏐")
	
	Return %NewString%
}

GetTextCursorPosition(){
	If (WinActive("ahk_group FileExplorer")){
		ControlGetFocus, FocusedControl
		If (FocusedControl == "Edit1" Or FocusedControl == "Edit2"){
			SendInput, {U+200E} ; This isn't synchronous
			Loop {
				ControlGetText, LeafName, %FocusedControl%
				If (TextCursorPosition := InStr(LeafName, "‎")){
					; "‎" actually contains a U+200E unprintable / invisible Left-to-Right Mark (LRM) character
					Break
				}
			}
			
			SendInput, {Backspace}
			
			Return TextCursorPosition
		}
	}
		
}

#If WinActive("ahk_group FileExplorer")		
	; Alternate Unicode open and close quotation marks
	Global QuotationMarksOpened := False
	"::
		; Close quotation mark to restore syntax formatting for the rest of the script: "
		
		ControlGetFocus, FocusedControl
		If (FocusedControl == "Edit1" Or FocusedControl == "Edit2"){
			TextCursorPosition := GetTextCursorPosition()
			
			ControlGetText, LeafName, %FocusedControl%
			LeafNameLength := StrLen(LeafName)
			TextToLeft := SubStr(LeafName, 1, TextCursorPosition)
			TextToRight := SubStr(LeafName, (TextCursorPosition - LeafNameLength))
			
			; The following matches if no opening and closing Unicode quotation marks are found
			If (RegExMatch(TextToLeft, "^[^“”]+$")){
				QuotationMarksOpened := False
			}
			; The following matches if opening AND closing Unicode quotation marks are found
			If (RegExMatch(TextToLeft, "“.*”")){
				QuotationMarksOpened := False
			}
			; The following matches if opening but NOT closing Unicode quotation marks are found
			If (RegExMatch(TextToLeft, "“(?!.*”)")){
				QuotationMarksOpened := True
			}
			
			If (QuotationMarksOpened == False){
				SendInput, {U+201C} ; “
			}
			If (QuotationMarksOpened == True){
				SendInput, {U+201D} ; ”
			}
		}
	Return
	
	\::
		ControlGetFocus, FocusedControl
		; Only send Unicode backslash when editing a file name
		If (WinActive("ahk_group FileExplorer") And (FocusedControl == "Edit2")){
			; Unicode back slash (⧵)
			SendInput, {U+29F5}
		} Else {
			SendInput, \
		}
	Return
	
	; Unicode forward slash (∕)
	/::SendInput, {U+2215}
	
	; Unicode colon (꞉)
	:::SendInput, {U+A789}
	
	; Unicode asterisk (⁎)
	*::SendInput, {U+204E}
	
	; Unicode question mark (?)
	?::SendInput, {U+FF1F}
	
	; Unicode less than sign (<)
	<::SendInput, {U+FF1C}
	
	; Unicode greater than sign (>)
	>::SendInput, {U+FF1E}
	
	; Unicode pipe (⏐)
	|::SendInput, {U+23D0}
	
	; Ctrl and D → Date with Unicode thin spaces and forward slashes (yyyy ∕ MM ∕ dd)
	^d::
		FormatTime, Date, , yyyy{U+2009}{U+2215}{U+2009}MM{U+2009}{U+2215}{U+2009}dd
		SendInput, %Date%
	Return
	
	; Ctrl and T → Time with Unicode colons (HH꞉mm꞉ss)
	^t::
		FormatTime, Time, , HH{U+A789}mm{U+A789}ss
		SendInput, %Time%
	Return
	
	; Ctrl and V → Paste filename with alternative Unicode characters
	^v::
		ControlGetFocus, FocusedControl
		If (WinActive("ahk_group FileExplorer") And (FocusedControl == "Edit2")){
			ClipboardContents := Clipboard		
			Clipboard := Replace_InvalidCharacters(ClipboardContents)
			SendInput, ^v
		} Else {
			SendInput, ^v
		}
	Return
#If

; Alt or Alt Gr and forward slash → Unicode forward slash wrapped in thin spaces ( ∕ ), good for usage with numbers such as a date
!/::
<^>!/::
	SendInput, {U+2009}{U+2215}{U+2009}
Return

File Explorer: Copy paths or fragments of selected files / folders

There’s a well-known AutoHotkey function that uses the Windows shell COM API to get the full paths of all selected items in File Explorer which works well enough but I found it limited in that it doesn’t handle #32770 (Save As, Open, etc) windows and only returned full paths, not fragments. So, I adapted it to do so and bound it to hotkeys.

; The following is from a third party with my adaptations to handle #32770 windows and extract elements of paths
FileExplorer_GetSelection(Mode){
	WinGetClass, WindowClass, % "ahk_id" . hWnd := WinExist("A")
	If (RegExMatch(WindowClass, "Progman|WorkerW|(Cabinet|Explore)WClass")){
		ShellWindows := ComObjCreate("Shell.Application").Windows
		
		If (WindowClass ~= "Progman|WorkerW"){
			ShellFolderView := ShellWindows.FindWindowSW(0, 0, SWC_DESKTOP := 8, 0, SWFO_NEEDDISPATCH := 1).Document
		} Else {
			For Window in ShellWindows {
				If (hWnd = Window.HWND) && (ShellFolderView := Window.Document){
					Break
				}
			}
		}
		
		For Item in ShellFolderView.SelectedItems {
			FullPath .= (FullPath = "" ? "" : "`n") . Item.Path
		}
		
		If (!FullPath){
			FullPath := ShellFolderView.Folder.Self.Path
		}
		
		; For some reason, using a variable named "Path" causes a blank line to be prefixed to the output in this block
	} Else If (WindowClass == "#32770"){
		ControlGetFocus, FocusedControl
		If (FocusedControl == "Edit2"){
			ControlGetText, Name, Edit2 ; In this context, Edit2 is the rename field
			
			ControlGetText, Directory, ToolbarWindow324 ; Folder / directory address bar
			Directory := StrReplace(Directory, "Address: ")
			
			FullPath := Directory . "\" . Name
		} Else {
			ClipboardBackup := ClipboardAll
			Clipboard :=
			SendInput, ^c
			ClipWait, 1 ; second
			FullPath := Clipboard
			Clipboard := ClipboardBackup
		}
	} Else {
		Return
	}
	
	If (Mode == "Path"){
		Result := FullPath
	} Else {
		Loop, Parse, FullPath, `n, `r
		{
			If (Mode == "Dir"){
				SplitPath, A_LoopField, , Value
				Value .= "\"
			} Else If (Mode == "NameExt"){
				SplitPath, A_LoopField, Value
			} Else If (Mode == "NameNoExt"){
				SplitPath, A_LoopField, , , , Value
			} Else {
				Break
			}
			
			If (A_Index = 1){
				Result := Value
			} Else {
				Result .= "`r`n" . Value
			}
		}
	}
	
	Return Result
}

GroupAdd, FileExplorer, ahk_class CabinetWClass ; Normal window
GroupAdd, FileExplorer, ahk_class #32770, ShellView ; Save as / open window
GroupAdd, FileExplorer, ahk_class WorkerW ; Desktop view

#If WinActive("ahk_group FileExplorer")
	; Ctrl, Shift, and C → Copy path(s) without quotation marks of selected file(s) in Explorer
	^+c::
		Clipboard := FileExplorer_GetSelection("Path")
	Return
	
	; Ctrl, Alt, and C → Copy name(s) with extension(s) of selected file(s) in Explorer
	^!c::
		Clipboard := FileExplorer_GetSelection("NameExt")
	Return
	
	; Ctrl, Shift, Alt, and C → Copy name(s) without extension(s) of selected file(s) in Explorer
	^+!c::
		Clipboard := FileExplorer_GetSelection("NameNoExt")
	Return
#If 

File Explorer: Automatically browse Save As / Open window to last folder open

An annoyance that I’ve had for as long as I can remember is having a folder open in File Explorer then switching to another application and having to re-browse to exactly the same folder when downloading or opening a file.

Originally, I tried to resolve this using Python and WebDriver but it seemed to be technically impossible.

Eventually, I realised that it may be possible with AutoHotkey and I successfully accomplished it!

SetTitleMatchMode, RegEx
  
GroupAdd, FileExplorer, ahk_class CabinetWClass ; Normal window
GroupAdd, FileExplorer, ahk_class #32770, ShellView ; Save as / open window
GroupAdd, FileExplorer, ahk_class WorkerW ; Desktop view

; The following is from a third party with my adaptations to handle #32770 windows and extract elements of paths
FileExplorer_GetSelection(Mode){
	WinGetClass, WindowClass, % "ahk_id" . hWnd := WinExist("A")
	If (RegExMatch(WindowClass, "Progman|WorkerW|(Cabinet|Explore)WClass")){
		ShellWindows := ComObjCreate("Shell.Application").Windows
		
		If (WindowClass ~= "Progman|WorkerW"){
			ShellFolderView := ShellWindows.FindWindowSW(0, 0, SWC_DESKTOP := 8, 0, SWFO_NEEDDISPATCH := 1).Document
		} Else {
			For Window in ShellWindows {
				If (hWnd = Window.HWND) && (ShellFolderView := Window.Document){
					Break
				}
			}
		}
		
		For Item in ShellFolderView.SelectedItems {
			FullPath .= (FullPath = "" ? "" : "`n") . Item.Path
		}
		
		If (!FullPath){
			FullPath := ShellFolderView.Folder.Self.Path
		}
		
		; For some reason, using a variable named "Path" causes a blank line to be prefixed to the output in this block
	} Else If (WindowClass == "#32770"){
		ControlGetFocus, FocusedControl
		If (FocusedControl == "Edit2"){
			ControlGetText, Name, Edit2 ; In this context, Edit2 is the rename field
			
			ControlGetText, Directory, ToolbarWindow324 ; Folder / directory address bar
			Directory := StrReplace(Directory, "Address: ")
			
			FullPath := Directory . "\" . Name
		} Else {
			ClipboardBackup := ClipboardAll
			Clipboard :=
			SendInput, ^c
			ClipWait, 1 ; second
			FullPath := Clipboard
			Clipboard := ClipboardBackup
		}
	} Else {
		Return
	}
	
	If (Mode == "Path"){
		Result := FullPath
	} Else {
		Loop, Parse, FullPath, `n, `r
		{
			If (Mode == "Dir"){
				SplitPath, A_LoopField, , Value
				Value .= "\"
			} Else If (Mode == "NameExt"){
				SplitPath, A_LoopField, Value
			} Else If (Mode == "NameNoExt"){
				SplitPath, A_LoopField, , , , Value
			} Else {
				Break
			}
			
			If (A_Index = 1){
				Result := Value
			} Else {
				Result .= "`r`n" . Value
			}
		}
	}
	
	Return Result
}

FileExplorer_Navigate32770ToLastFolder(Hotkey){		
	; Some apps use class #32770 for things like Find in Notepad++ so the following only matches these windows when the title starts with Save or Open such as Save As, Save To, Save Print Output As, Open Outlook Data Files", etc
	If (WinActive("ahk_class #32770") And (WinActive("Save") Or WinActive("Open") Or WinActive(".*folder.*") Or WinActive(".*directory.*") Or WinActive("Insert"))){
		; If File Explorer hasn't been opened since the AHK script has been reloaded then FileExplorer_LastFolderPath won't have been set so the rest of the code can't run
		If (FileExplorer_LastFolderPath != ""){
			ControlGetText, CurrentFolderPath, ToolbarWindow324
			CurrentFolderPath := StrReplace(CurrentFolderPath, "Address: ")
			
			If (CurrentFolderPath != FileExplorer_LastFolderPath){
				ControlFocus, Edit1
				ControlSetText, Edit1, %FileExplorer_LastFolderPath%
				ControlSend, Edit1, {Enter}
				
				; The previous commands take a few milliseconds to complete the following is the quickest way of checking because Windows will automatically re-set the generated filename when the directory change completes
				Loop {
					ControlGetText, File_Name, Edit1
					
					If (FileExplorer_LastFolderPath != File_Name){
						Break
					}
				}

				FileExplorer_MoveTextCursorToEndOfName()
			}
		}
	} Else {
		; The following preserves the hotkey's functionality if the above conditions aren't met
		SendInput, {%Hotkey%}
	}
}

FileExplorer_MoveTextCursorToEndOfName(){
	LeafType := ""
	
	WinGetClass, WindowClass, A
	ControlGetFocus, FocusedControl
	
	If ((WindowClass == "#32770") And (FocusedControl == "Edit1")){
		LeafType := "File"
		
		ControlGetText, Name, Edit1
	} Else {
		Path := FileExplorer_GetSelection("Path")
		Name := FileExplorer_GetSelection("NameExt")
		
		If (InStr(FileExist(Path), "D")){
			LeafType := "Folder"
		} Else {
			LeafType := "File"
		}
	}
	
	SendInput, {F2}
	SendInput, {End}
	
	If (LeafType == "File"){
		DotPosition := InStr(Name, ".", False, 0) ; 0 makes it search from the right-hand side
		
		If (DotPosition > 0){
			MoveAmount := StrLen(Name) - DotPosition + 1 ; + 1 ensures that the text cursor is moved to the left-hand side of the file extension
			SendInput, {Left %MoveAmount%}
		}
	}
}

Global FileExplorer_LastFolderPath := ""
SetTimer, FastLoop, 100
FastLoop(){
	WinGetClass, WindowClass, A
	
	; Log File Explorer most recently-accessed folder for use with Save As windows
	If (WindowClass == "CabinetWClass"){
		ControlGetText, FileExplorer_LastFolderPath, ToolbarWindow323, A
		FileExplorer_LastFolderPath := StrReplace(FileExplorer_LastFolderPath, "Address: ")
	}
}

#If WinActive("ahk_class #32770")
	Insert::
		FileExplorer_Navigate32770ToLastFolder("Insert")
	Return
#If 

File Explorer: Invoke rename and automatically move text cursor to end of the file name

If you have file extensions displayed in File Explorer, renaming a file is always a little bit fiddly because you have to get the cursor just before the dot. Not anymore!

(If desired, this can be massively simplified by removing the code related to toggling the end position.)

GroupAdd, FileExplorer, ahk_class CabinetWClass ; Normal window
GroupAdd, FileExplorer, ahk_class #32770, ShellView ; Save as / open window
GroupAdd, FileExplorer, ahk_class WorkerW ; Desktop view

Global NameEndPosition := ""

; The following is from a third party with my adaptations to handle #32770 windows and extract elements of paths
FileExplorer_GetSelection(Mode){
	WinGetClass, WindowClass, % "ahk_id" . hWnd := WinExist("A")
	If (RegExMatch(WindowClass, "Progman|WorkerW|(Cabinet|Explore)WClass")){
		ShellWindows := ComObjCreate("Shell.Application").Windows
		
		If (WindowClass ~= "Progman|WorkerW"){
			ShellFolderView := ShellWindows.FindWindowSW(0, 0, SWC_DESKTOP := 8, 0, SWFO_NEEDDISPATCH := 1).Document
		} Else {
			For Window in ShellWindows {
				If (hWnd = Window.HWND) && (ShellFolderView := Window.Document){
					Break
				}
			}
		}
		
		For Item in ShellFolderView.SelectedItems {
			FullPath .= (FullPath = "" ? "" : "`n") . Item.Path
		}
		
		If (!FullPath){
			FullPath := ShellFolderView.Folder.Self.Path
		}
		
		; For some reason, using a variable named "Path" causes a blank line to be prefixed to the output in this block
	} Else If (WindowClass == "#32770"){
		ControlGetFocus, FocusedControl
		If (FocusedControl == "Edit2"){
			ControlGetText, Name, Edit2 ; In this context, Edit2 is the rename field
			
			ControlGetText, Directory, ToolbarWindow324 ; Folder / directory address bar
			Directory := StrReplace(Directory, "Address: ")
			
			FullPath := Directory . "\" . Name
		} Else {
			ClipboardBackup := ClipboardAll
			Clipboard :=
			SendInput, ^c
			ClipWait, 1 ; second
			FullPath := Clipboard
			Clipboard := ClipboardBackup
		}
	} Else {
		Return
	}
	
	If (Mode == "Path"){
		Result := FullPath
	} Else {
		Loop, Parse, FullPath, `n, `r
		{
			If (Mode == "Dir"){
				SplitPath, A_LoopField, , Value
				Value .= "\"
			} Else If (Mode == "NameExt"){
				SplitPath, A_LoopField, Value
			} Else If (Mode == "NameNoExt"){
				SplitPath, A_LoopField, , , , Value
			} Else {
				Break
			}
			
			If (A_Index = 1){
				Result := Value
			} Else {
				Result .= "`r`n" . Value
			}
		}
	}
	
	Return Result
}

FileExplorer_MoveTextCursorToEndOfName(){
	LeafType := ""
	
	WinGetClass, WindowClass, A
	ControlGetFocus, FocusedControl
	
	If ((WindowClass == "#32770") And (FocusedControl == "Edit1")){
		LeafType := "File"
		
		ControlGetText, Name, Edit1
	} Else {
		Path := FileExplorer_GetSelection("Path")
		Name := FileExplorer_GetSelection("NameExt")
		
		If (InStr(FileExist(Path), "D")){
			LeafType := "Folder"
		} Else {
			LeafType := "File"
		}
	}
	
	SendInput, {F2}
	SendInput, {End}
	
	If ((LeafType == "File") And ((!NameEndPosition) Or (NameEndPosition == "End"))){
		DotPosition := InStr(Name, ".", False, 0) ; 0 makes it search from the right-hand side
		
		If (DotPosition > 0){
			MoveAmount := StrLen(Name) - DotPosition + 1 ; + 1 ensures that the text cursor is moved to the left-hand side of the file extension
			SendInput, {Left %MoveAmount%}
	
			NameEndPosition := "Extension"
		}
	} Else {
		NameEndPosition := "End"
	}
}

FileExplorer_ResetNameEndPosition(){
	; If this isn't done then the last position will be remembered in FileExplorer_MoveTextCursorToEndOfName, even though it would no longer be relevant
	NameEndPosition := ""
}

AppChanged(){
	FileExplorer_ResetNameEndPosition()
}

SetTimer, FastLoop, 100
FastLoop(){
	If (WinActive("ahk_group FileExplorer")){
		ControlGetFocus, FocusedControl
		If (FocusedControl != "Edit1" And FocusedControl != "Edit2"){
			; Stopped editing file or folder name
			FileExplorer_ResetNameEndPosition()
		}
	}
}

Global WindowEXE := ""
SetTimer, SlowLoop, 1000
SlowLoop(){
	WinGet, ahk_exe, ProcessName, A
	
	If ((WindowEXE == "") Or (WindowEXE != ahk_exe)){
		WindowEXE := ahk_exe
		
		AppChanged()
	}
}

#If WinActive("ahk_group FileExplorer")
	; Ctrl and R → Invoke file rename, deselect whole file name, and move text cursor to (1) the end of the name if a folder or extensionless file or (2) the end of the file name but before the file extension.
	^r::
		FileExplorer_MoveTextCursorToEndOfName()
	Return
	
	End::
		FileExplorer_MoveTextCursorToEndOfName()
	Return
	$Home::
		SendInput, {Home}
		FileExplorer_ResetNameEndPosition()
	Return
#If 

Any app: Automatically expand abbreviations for IDs

Sick of manually typing in your username, email address, phone number, etc? Hotstrings to the rescue!

(A nice simple one this time.)

:*:b@::email@example.com

:c*:moe::mythofechelon ; (Case-sensitive)

:c*:ddi::01234 567 890 ; (Case-sensitive)

Excel: Automatically expand columns for CSV files

I’d recently started regularly working with CSV files that contain long values and I got sick of having to manually expand all of the columns every single time and was surprised to find out that there’s no way to do so automatically natively.

#Persistent

Global LastExcelFile := ""
SetTimer, SlowLoop, 1000
SlowLoop(){
	WinGetClass, WindowClass, A
	WinGetTitle, WindowTitle, A
	
	; Auto-expand Excel CSV column headers
	If ((WinActive("ahk_exe EXCEL.EXE")) And (InStr(WindowTitle, ".csv"))){
		Excel := ComObjActive("Excel.Application")
		
		; Tends to prompt with errors a lot, hence the use of Try which suppresses errors
		Try {
			; Checking WindowTitle for ".csv" above isn't sufficient because the commands get sent to .xlsx workbooks when .csv files are closed otherwise
			If (InStr(Excel.ActiveWorkbook.Name, ".csv")){
				If ((LastExcelFile == "") Or (LastExcelFile != WindowTitle)){
					For Worksheet In Excel.Worksheets {
						Excel.Worksheets(A_Index).Cells.Columns.AutoFit
					}
					
					LastExcelFile := WindowTitle
				}
			}
		}
	} Else {
		; The following allows LastExcelFile to be reset when changing between Excel files and non-Excel windows
		LastExcelFile := ""
	}
}

Any app: Paste into systems that don’t support it

Every now and again, I need to paste into a system that doesn’t support it such as a web console of a server, a VM that doesn’t have host integration enabled, a web site who thinks they’re being secure, etc. This is very useful in those scenarios.

; Alt and V → Send clipboard contents as keystrokes, good for systems that don't / can't allow normal pasting and usage of SendRaw, rather than SendInput, allows ALL characters to be sent and in a more reliable way
!v::SendRaw, %Clipboard%

Any app: Resize window to specific resolution

Mainly due to my work with the Chrome Web Store which requires that screenshots are exactly 1280 x 800, I sometimes need to resize a window to a specific resolution which this allows me to do.

; Windows (⊞) and equals → Resize window
#=::
	EndWidth := 0
	EndHeight := 0
	
	WinGet, Window, ID, A
	If (WinActive("ahk_exe chrome.exe")){
		InputBox, ChromeMode, Chrome Mode, (w)indow or (v)iewport:, , 140, 140
		
		If (ChromeMode == "w"){
			EndWidth += 16
			EndHeight += 8
		} Else If (ChromeMode == "v"){
			EndWidth += 16
			EndHeight += 120
		}
	}
	InputBox, InputWidth, Resize, Width:, , 140, 130
	InputBox, InputHeight, Resize, Height:, , 140, 130
	
	If (InputWidth And InputHeight){
		EndWidth += InputWidth
		EndHeight += InputHeight
		
		WinRestore, A
		WinMove, ahk_id %Window%, , , , EndWidth, EndHeight
	}
Return

Any app: Move cursor by one pixel

I’m quite a perfectionist so, when using screenshotting tools, I don’t like to include bits outside of the section I’m capturing and this kind of pixel-level precision is difficult without things like this.

; Right-hand Shift and arrow key → Move cursor 1 pixel in that direction
>+Left::MouseMove, -1, 0, 0 ,R
>+Right::MouseMove, 1, 0, 0, R
>+Up::MouseMove, 0, -1, 0, R
>+Down::MouseMove, 0, 1, 0, R

PuTTY: CTRL V to paste into console

Normally, you can only paste into a PuTTY console by right-clicking on it which is a bit annoying when you have both of your hands on the keyboard.

; #If WinActive("ahk_class ^PuTTY$") ; Regex title matching mode
  #If WinActive("ahk_class PuTTY") ; Defaut title matching mode
	; Ctrl and V → Right-click (to paste)
	^v::SendInput, {Click, Right}
#If 

Excel: Tidier pasting of copied cells to other apps

Pasting copied cells from Excel tends to include all sorts of ugly trailing empty lines and unnecessary quotation marks, especially when the cell is multi-line. This corrects that.

Global ClipboardSource := ""
OnClipboardChange("ClipoardChanged")
ClipoardChanged(Type){
	; #1 means that the clipboard contains something that can be expressed as text, rather than images for example
	If (Type = 1){
		If (WinActive("ahk_class XLMAIN")){				
			ClipboardSource := "Excel"
		} Else {
			ClipboardSource := ""
		}
	}
}

#If !WinActive("ahk_class XLMAIN")
	; Ctrl and V → Tidy up clipboard contents when copied from Excel and pasting in non-Excel
	$^v::
		If (ClipboardSource == "Excel"){
			ClipboardContents := Clipboard
			
			; Excel-copied cells always contain at least one trailing line break so the following removes those
			ClipboardContents := RegExReplace(ClipboardContents, "[\r\n]+$")
			
			; Excel-copied multi-line cells tend to get wrapped in quotation marks so the following detects whether there is a line break (and, therefore, is a multi-line cell)...
			If (RegExMatch(ClipboardContents, "(\r\n|\r|\n)+")){
				; ... and the following detects if the content is wrapped in quotation marks and, if so, generates a match object ("O") so the group of what's between them can be extracted
				If (RegExMatch(ClipboardContents, "O)^""(.+)""$", CellContent)){
					ClipboardContents := CellContent.Value(1)
					
					; Replace 2 x double quotation marks with 1 x double quotation marks
					ClipboardContents := StrReplace(ClipboardContents, """""", """")
				}
			}
			
			Clipboard := ClipboardContents
		}
		
		SendInput, ^v
	Return
#If 

Excel: Make Return / Enter send a new line

These days, I’m having to work with multi-line cells in Microsoft Excel a lot and, for some reason, it has a non-standard hotkey of Alt + Enter / Return to send a new line so this overrides that.

#If WinActive("ahk_class XLMAIN")
	; Make Enter / Return send newline (Alt and Enter) instead of committing changes to cell
	NumpadEnter::
	Enter::
		SendInput, !{Enter}
	Return
#If 

Excel: CTRL A to select all text in Find and Replace fields

Why this isn’t supported natively I have no clue.

#If WinActive("ahk_class bosa_sdm_XL9")
	; Ctrl and A → Select all text in "Find what" or "Replace with" text fields
	^a::
		ControlGetFocus, FocusedControl
		
		If (FocusedControl == "EDTBX1" Or "EDTBX2"){
			SendInput, {Home}{Shift Down}{End}{Shift Up}
		}
	Return
#If 

Any app: Automatically complete function curly braces

If you work with programming / scripting such as C++, PowerShell, JavaScript, and CSS a lot, this needs no explanation.

; Auto-complete function curly braces
:*:{`n::
	SendRaw, {
	SendInput, `n`n
	SendRaw, }
	SendInput, {Up 1}
	SendInput, {Tab}
Return

Any app: Clear clipboard

Being ultra cybersecurity-minded and working a lot with systems that synchronise clipboards, this is quite useful.

; Alt and C → Clear clipboard
!c::Clipboard := 

File Explorer: Automatically close file extension rename warning prompt

Any even remotely advanced Windows user has to semi-regularly change file extensions and contend with the annoying warning window for which there’s still no option to suppress. Not anymore (kinda)!

#Persistent
  
SetTimer, FastLoop, 100
FastLoop(){
	WinGetClass, WindowClass, A
	WinGetTitle, WindowTitle, A
	
	; Auto-close File Explorer warning prompt when changing a file's extension
	If ((WindowClass == "#32770") And (WindowTitle = "Rename")){
		SendInput, y
	}
}

Outlook: Automatically remove mailto: from copied email addresses

Again, why this is default behaviour in this day and age is beyond comprehension but there you go.

#Persistent
OnClipboardChange("ClipoardChanged")
ClipoardChanged(Type){
	; #1 means that the clipboard contains something that can be expressed as text, rather than images for example
	If (Type == 1){
		If (WinActive("ahk_exe OUTLOOK.EXE")){
			ClipboardContents := Clipboard
			
			; Auto-remove "mailto:" from email links which Outlook is notorious for
			If (RegExMatch(ClipboardContents, "^mailto:.+")){
				EmailAddress := SubStr(ClipboardContents, 8)
				
				Clipboard := EmailAddress
			}
		}
	}
}

Any app: Disable rotation of screen

Ever accidentally pressed Alt Gr and an arrow key only to have your whole virtual world turned upside-down or on its side? This blocks that.

; Disable Alt Gr rotation of screen
Hotkey, <^>!Up, Off
Hotkey, <^>!Down, Off
Hotkey, <^>!Left, Off
Hotkey, <^>!Right, Off

Outlook: Inject email’s full timestamp into preview pane

Another stupid surprise with Microsoft software: Outlook doesn’t (and can’t) allow you to see the full timestamp for an email that you’re previewing - it will only “intelligently” show the time or date. Fixed.

#Persistent
Global LastEmailIndex := ""
SetTimer, SlowLoop, 1000
SlowLoop(){
	WinGetTitle, WindowTitle, A
	
	If ((WinActive("ahk_exe OUTLOOK.EXE")) And (InStr(WindowTitle, " - Outlook"))){
		Try {
			Outlook := ComObjActive("Outlook.Application")
			
			For Email in Outlook.ActiveExplorer().Selection {
				CurrentEmailIndex := Email.ConversationIndex
				
				If ((LastEmailIndex == "") Or (LastEmailIndex != CurrentEmailIndex)){
					; Current selection hasn't been processed yet
					
					CurrentEmailSubject := Email.Subject
					CurrentEmailTimestamp := Email.CreationTime
					
					ControlGet, RichEdit20WPT8Exists, Visible, , RichEdit20WPT8, A
					ControlGet, RichEdit20WPT9Exists, Visible, , RichEdit20WPT9, A
					If (RichEdit20WPT8Exists){
						TimestampControlName := "RichEdit20WPT8"
					} Else If (RichEdit20WPT9Exists){
						TimestampControlName := "RichEdit20WPT9"
					}
					
					WinGetPos, , , OutlookWindowWidth, , A
					
					ControlGetPos, TimestampControlXBefore, , TimestampControlWidthBefore, , %TimestampControlName%, A
					TimestampControlWidthAfter := 105
					TimestampControlXAfter := TimestampControlXBefore - (TimestampControlWidthAfter - TimestampControlWidthBefore)
					
					ControlMove, %TimestampControlName%, %TimestampControlXAfter%, , %TimestampControlWidthAfter%
					
					ControlSetText, %TimestampControlName%, %CurrentEmailTimestamp%, A
				}
				
				LastEmailIndex := CurrentEmailIndex
			}
		}
	}
}

Any app: Enter difficult-to-type characters

Some characters are very useful but aren’t on the keyboard such as bullet points and arrows so I have hotkeys to them:

; Alt and o → Bullet point
!o::SendInput, •{Space}

; Alt and arrow key → Unicode arrow in that direction (→, ←, ↑, ↓)
!Right::SendInput, {U+2192}
!Left::SendInput, {U+2190}
!Up::SendInput, {U+2191}

; Alt Gr and hyphen → Dividing line
<^>!-::SendInput, ------------------------------------------------------------------------------------------

; Alt Gr and space → Unicode empty space (‎), useful for keeping a text field blank when it needs to contain something
!Space::SendInput, {U+200E}

; Alt Gr and Tab → Send tab character, useful for entering tabs in text fields on web sites
<^>!Tab::SendInput, {U+0009}

Any app: Enter current date and time

Very frequently, I need to type the current date and/or time. This allows me to do so, even in file names, thanks to my discovery I talked about in my blog post “How to work around Windows' restricted characters“!

GroupAdd, FileExplorer, ahk_class CabinetWClass ; Normal window
GroupAdd, FileExplorer, ahk_class #32770, ShellView ; Save as / open window
GroupAdd, FileExplorer, ahk_class WorkerW ; Desktop view

#If !WinActive("ahk_group FileExplorer")
	; Ctrl and D → Date with normal forward slashes
	^d::
		FormatTime, Date, , yyyy/MM/dd
		SendInput, %Date%
	Return
	
	; Ctrl and T → Time with normal colons
	^t::
		FormatTime, Time, , HH:mm:ss
		SendInput, %Time%
	Return
#If	
#If WinActive("ahk_group FileExplorer")
	; Ctrl and D → Date with Unicode thin spaces and forward slashes (yyyy ∕ MM ∕ dd)
	^d::
		FormatTime, Date, , yyyy{U+2009}{U+2215}{U+2009}MM{U+2009}{U+2215}{U+2009}dd
		SendInput, %Date%
	Return
	
	; Ctrl and T → Time with Unicode colons (HH꞉mm꞉ss)
	^t::
		FormatTime, Time, , HH{U+A789}mm{U+A789}ss
		SendInput, %Time%
	Return
#If 

Any app: Enter current time to nearest 5 minutes

Anyone who works in professional services is probably used to timesheets. I have my own Excel spreadsheet to automate this process but it uses start and stop times and real-life just isn’t that precise so I wrote an alternate version of the previous script to enter the current time to the nearest 5 minutes.

SetTitleMatchMode, RegEx

#If WinActive("i).*timesheet.* - Excel")
	; Ctrl and T → Rounded time with normal colons
	^t::
		EpochTimeOriginal := A_NowUTC - 
		EpochTimeOriginal -= 1970010100, Seconds
		RoundToNearest := 300 ; seconds / 5 minutes
		Offset := Mod(EpochTimeOriginal, RoundToNearest)
		EpochTimeRoundedDown := EpochTimeOriginal - Offset
		If (Offset > (RoundToNearest / 2)){
			EpochTimeRoundedUp := EpochTimeRoundedDown + RoundToNearest
			EpochTimeRoundedPhase1 := EpochTimeRoundedUp
		} Else{
			EpochTimeRoundedPhase1 := EpochTimeRoundedDown
		}
		EpochTimeRoundedPhase2 := 1970010100
		EpochTimeRoundedPhase2 += EpochTimeRoundedPhase1, Seconds
		FormatTime, TimeRounded, %EpochTimeRoundedPhase2%, HH:mm
		SendInput, %TimeRounded%
	Return
#If 

Outlook: Copy selected email contents with reply-like metadata

#Include WinClipAPI.ahk
#Include WinClip.ahk

#If WinActive("ahk_exe OUTLOOK.EXE")
	; Ctrl and C → Copy email
	$^c::
		ControlGetFocus, FocusedControl
		If (FocusedControl == "OutlookGrid2" Or FocusedControl == "OutlookGrid3"){			
			Outlook := ComObjActive("Outlook.Application")
			
			For Email in Outlook.ActiveExplorer().Selection {
				
				
				
				EmailFrom := Email.SenderName
				If (Email.SenderEmailType == "EX"){
					EmailSenderAddress := Email.Sender.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x39FE001E")
				} Else {
					EmailSenderAddress := Email.SenderEmailAddress
				}
				If (EmailSenderAddress) {
					EmailFrom .= " <" . EmailSenderAddress . ">"
				}
				LabelFrom := "From: "
				PTFrom := LabelFrom . EmailFrom . "`n"
				EmailFrom := StrReplace(EmailFrom, "<", "<")
				EmailFrom := StrReplace(EmailFrom, ">", ">")
				HTMLFrom := "" . LabelFrom . "" . EmailFrom . "
" ; The following regex will need to be customised as per system date format If (RegExMatch(Email.ReceivedTime, "O)(\d) (\d).*", GroupContent)){ EmailYear := GroupContent.Value(1) EmailMonth := GroupContent.Value(2) EmailDay := GroupContent.Value(3) EmailTime := GroupContent.Value(4) If (EmailMonth == 01){ EmailMonth := "January" } Else If (EmailMonth == 02){ EmailMonth := "February" } Else If (EmailMonth == 03){ EmailMonth := "March" } Else If (EmailMonth == 04){ EmailMonth := "April" } Else If (EmailMonth == 05){ EmailMonth := "May" } Else If (EmailMonth == 06){ EmailMonth := "June" } Else If (EmailMonth == 07){ EmailMonth := "July" } Else If (EmailMonth == 08){ EmailMonth := "August" } Else If (EmailMonth == 09){ EmailMonth := "September" } Else If (EmailMonth == 10){ EmailMonth := "October" } Else If (EmailMonth == 11){ EmailMonth := "November" } Else If (EmailMonth == 12){ EmailMonth := "December" } EmailTimestamp := EmailDay . " " . EmailMonth . " " . EmailYear . " " . EmailTime } Else { EmailTimestamp := Email.ReceivedTime } LabelSent := "Sent: " PTSent := LabelSent . EmailTimestamp . "`n" HTMLSent := "" . LabelSent . "" . EmailTimestamp . "
" EmailTo := "" EmailCC := "" EmailBCC := "" For Recipient in Email.Recipients { EmailRecipientName := Recipient.Name If (InStr(Recipient.Address, "/o=")){ ; EX type EmailRecipientAddress := Recipient.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x39FE001E") } Else { ; SMTP type EmailRecipientAddress := Recipient.Address } If (EmailRecipientName != EmailRecipientAddress){ EmailRecipientBoth := EmailRecipientName . " <" . EmailRecipientAddress . ">" } Else { EmailRecipientBoth := EmailRecipientAddress } If (Recipient.Type == 1){ If (EmailTo != ""){ EmailRecipientBoth := "; " . EmailRecipientBoth } EmailTo .= EmailRecipientBoth } Else If (Recipient.Type == 2){ If (EmailCC != ""){ EmailRecipientBoth := "; " . EmailRecipientBoth } EmailCC .= EmailRecipientBoth } Else If (Recipient.Type == 3){ If (EmailBCC != ""){ EmailRecipientBoth := "; " . EmailRecipientBoth } EmailBCC .= EmailRecipientBoth } } LabelTo := "To: " PTTo := LabelTo . EmailTo . "`n" EmailTo := StrReplace(EmailTo, "<", "<") EmailTo := StrReplace(EmailTo, ">", ">") HTMLTo := "" . LabelTo . "" . EmailTo . "
" LabelCC := "Cc: " PTCC := LabelCC . EmailCC . "`n" EmailCC := StrReplace(EmailCC, "<", "<") EmailCC := StrReplace(EmailCC, ">", ">") HTMLCC := "" . LabelCC . "" . EmailCC . "
" LabelBCC := "Bcc: " PTBCC := LabelBCC . EmailBCC . "`n" EmailBCC := StrReplace(EmailBCC, "<", "<") EmailBCC := StrReplace(EmailBCC, ">", ">") HTMLBCC := "" . LabelBCC . "" . EmailBCC . "
" EmailSubject := Email.Subject LabelSubject := "Subject: " PTSubject := LabelSubject . EmailSubject . "`n" HTMLSubject := "" . LabelSubject . "" . EmailSubject . "
" EmailImportance := Email.Importance If (EmailImportance == 2){ EmailImportance := "High" } Else If (EmailImportance == 1){ ; Normal EmailImportance := "" } Else If (EmailImportance == 0){ ; Normal EmailImportance := "Low" } LabelImportance := "Importance: " PTImportance := LabelImportance . EmailImportance . "`n" HTMLImportance := "" . LabelImportance . "" . EmailImportance . "
" EmailAttachments := "" For Attachment in Email.Attachments { If (!RegexMatch(Attachment.FileName, "i)(?:(?:image[\d\w]{3,})|RSImage).(?:png|jpg)|AttachedImage")){ String := """" . Attachment.FileName . """" If (EmailAttachments != ""){ String := "; " . String } EmailAttachments .= String } } LabelAttachments := "Attachments: " PTAttachments := LabelAttachments . EmailAttachments . "`n" HTMLAttachments := "" . LabelAttachments . "" . EmailAttachments . "
" PTEmailBodyFull := Email.Body If (PTEmailBodyFull){ PTEmailBodyCleaned := RemoveOldEmails(PTEmailBodyFull) PTEmailBodyCleaned := RegExReplace(PTEmailBodyCleaned, "________________________________(?:\r\n|\r|\n)+$") PTEmailBodyCleaned := RegExReplace(PTEmailBodyCleaned, "s)(\r\n|\r|\n)(?: | )\1") ; Get rid of all spaces or tabs wrapped in newlines PTEmailBodyCleaned := RegExReplace(PTEmailBodyCleaned, "s)(?:\r\n|\r|\n)", "`n`n") ; Replace 3 or more newlines with 2 PTEmailBodyCleaned := RegExReplace(PTEmailBodyCleaned, "s)(?:\r\n|\r|\n)+$") ; Get rid of trailing newlines } Else { PTEmailBodyCleaned := "[No content]" } PTBody := "`n" . PTEmailBodyCleaned HTMLEmailBodyFull := Email.HTMLBody HTMLEmailBodyCleaned := RemoveOldEmails(HTMLEmailBodyFull) ; Remove empty and trailing lines HTMLEmailBodyCleaned := RegExReplace(HTMLEmailBodyCleaned, "s)(\r|\n|\r\n)", "`n") HTMLEmailBodyCleaned := RegExReplace(HTMLEmailBodyCleaned, "(

(?:]*>)? <\/o:p>(?:<\/span>)?<\/p>(?:\r|\n|\r\n))") HTMLEmailBodyCleaned := RegExReplace(HTMLEmailBodyCleaned, "(?:
(?:\r|\n|\r\n))$") HTMLEmailBodyCleaned := RegExReplace(HTMLEmailBodyCleaned, "
(\r|\n|\r\n)<\/div>\1$") HTMLEmailBodyCleaned := RegExReplace(HTMLEmailBodyCleaned, "

]*>(?:\r|\n|\r\n)?(?:
)?(?:\r|\n|\r\n)?<\/p>") HTMLEmailBodyCleaned := StrReplace(HTMLEmailBodyCleaned, "

") HTMLBody := HTMLEmailBodyCleaned PTMetadata := "" PTMetadata .= PTFrom PTMetadata .= PTSent PTMetadata .= PTTo If (EmailCC != ""){ PTMetadata .= PTCC } If (EmailBCC != ""){ PTMetadata .= PTBCC } PTMetadata .= PTSubject If (EmailImportance != ""){ PTMetadata .= PTImportance } If (EmailAttachments != ""){ PTMetadata .= PTAttachments } PTToCopy := PTMetadata . PTBody ; Clipboard := PTToCopy ; Seems that Clipboard can't be mixed with WinClip WinClip.SetText( PTToCopy ) HTMLMetadata := "" HTMLMetadata .= HTMLFrom HTMLMetadata .= HTMLSent HTMLMetadata .= HTMLTo If (EmailCC != ""){ HTMLMetadata .= HTMLCC } If (EmailBCC != ""){ HTMLMetadata .= HTMLBCC } HTMLMetadata .= HTMLSubject If (EmailImportance != ""){ HTMLMetadata .= HTMLImportance } If (EmailAttachments != ""){ HTMLMetadata .= HTMLAttachments } HTMLMetadata := "" . HTMLMetadata . "" ; Insert metadata after body tag If (RegExMatch(HTMLBody, "isO)(.*]*>)(.*)", GroupContent)){ HTMLToCopy := GroupContent.Value(1) . HTMLMetadata . "
" . GroupContent.Value(2) } Else { HTMLToCopy := HTMLMetadata . "
" . HTMLBody } /* HTMLToCopy := StrReplace(HTMLToCopy, "ben@mythofechelon.co.uk", "[REDACTED]") HTMLToCopy := StrReplace(HTMLToCopy, "ana@pereira.wales", "[REDACTED]") HTMLToCopy := StrReplace(HTMLToCopy, "myth.of.echelon@live.com", "[REDACTED]@live.com") HTMLToCopy := StrReplace(HTMLToCopy, "mythofechelon@gmail.com", "[REDACTED]@gmail.com") */ MsgBox, Pre-WinClip.SetHTML WinClip.SetHTML( HTMLToCopy, source = "" ) ; WinClip.SetText( HTMLToCopy ) RemoveOldEmails(EmailContentBefore){ EmailContentAfter := EmailContentBefore EmailContentAfter := StrSplit(EmailContentAfter, "From:")[1] EmailContentAfter := StrSplit(EmailContentAfter, "Begin forwarded message:")[1] EmailContentAfter := StrSplit(EmailContentAfter, "-----Original Message-----")[1] ; Regex groups don't work when there's more than one instance because they're too greedy If (OutlookLinePosition := RegExMatch(EmailContentAfter, "
")){ EmailContentAfter := SubStr(EmailContentAfter, 1, OutlookLinePosition - 1) } If (iOSStringPosition := RegExMatch(EmailContentAfter, "On [\w\s,/:]+(?:[\w\s&;,<>"":@.=/-]+)? wrote:")){ EmailContentAfter := SubStr(EmailContentAfter, 1, iOSStringPosition - 1) } EmailContentAfter := StrSplit(EmailContentAfter, "
")[1] EmailContentAfter := StrSplit(EmailContentAfter, "
")[1] Return EmailContentAfter } } } Else { SendInput, ^c } Return #If

Outlook: Copy selected email headers

#If WinActive("ahk_exe OUTLOOK.EXE")

; Ctrl, Shift, and C → Copy email headers
	^+c::
		ControlGetFocus, FocusedControl
		If (FocusedControl == "OutlookGrid2" Or FocusedControl == "OutlookGrid3"){			
			Outlook := ComObjActive("Outlook.Application")
			
			For Email in Outlook.ActiveExplorer().Selection {
				EmailHeaders := Email.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x007D001E")
				
				Clipboard := EmailHeaders
			}
		}
	Return
#If

Keypirinha

Keypirinha can be summarised as “macOS’ Spotlight for Windows”: an extremely fast, useful, and quick-to-learn tool to launch anything from anywhere. Used correctly, this app can virtually eliminate searching for anything - files, bookmarks, apps, etc.

The only downside is that it doesn’t have an installer but I’ve created one myself, written about it in my blog post “My installer for Keypirinha”, and made them available to download at https://github.com/mythofechelon/Keypirinha/releases.

You can definitely just install this and immediately get value out of it but there are some capabilities that aren’t obvious and some tweaks that can further improve it, all of which I have detailed below.

Note: When editing configurations, you’ll probably have to refresh the configuration afterwards for it to take affect. You can do this by right-clicking on the System Tray icon and selecting Refresh Catalog… and/or Reload Configuration.

Keypirinha configuration

The Keypirinha app itself is extensively customisable. To see the possibilities and/or edit the config, right-click on the System Tray icon, select Configure Keypirinha, and see the left-hand window.

Personally, I only customise a few things here:

  • Change the default monitor that the interface opens on from the primary to the active one.

  • Change the default the hotkey to open the interface from Ctrl + Win + K (which is a bit awkward to use) to Ctrl + Space.

  • Set the hotkey to open the interface with history (useful if you regularly perform the same actions) to Ctrl + Shift + Space.

To implement this, simply copy the below and paste into the right-hand window:

[gui]
geometry = active_monitor

[app]
hotkey_run = Ctrl+Space
hotkey_history = Ctrl+Shift+Space

Native package: Apps

The Apps package is basically responsible for indexing file system content. By default, it indexes the following locations:

  • Start Menu

  • Desktop

  • PATH environment variable

This can be expanded to index entire folders and subfolders, specific files, specific file types in folders, etc.

To customise this, right-click on the System Tray icon, hover over Configure Package, and select Apps. Below is an example custom configuration for this:

[main]
extra_paths=
    D:\Users\${env:userName}\Documents\RDP\**\*.rdp
    ${env:Dropbox}\IT\Knowledge Base\**\*.txt

Native package: Bookmarks

The Bookmarks package indexes bookmarks / favourites in the following web browsers (in roughly decreasing order of popularity):

  • Google Chrome

  • Internet Explorer

  • Firefox

  • Chromium (generic version)

  • Google Chrome Canary (experimental version)

  • Misc (Falkon / QupZilla, Iridium, Vivaldi, Buku)

In my experience, Chromium browsers such as Opera or the new Microsoft Edge aren’t supported by default so this can be achieved by right-clicking on the System Tray icon, hovering over Configure Package, selecting Bookmarks, pasting the following into the right-hand window, and saving the changes.

[provider/Chromium]
bookmarks_files = 
    ${env:userProfile}\AppData\Roaming\Opera Software\Opera Stable\Bookmarks
    ${env:userProfile}\AppData\Local\Microsoft\Edge\User Data\Default\Bookmarks

Native package: Calc

This package is capable of some pretty advanced calculations (see https://keypirinha.com/packages/calc.html) but even for pretty basic stuff it’s useful to see the history, the calculation update as you go, and be able to press enter to copy the result.

Native package: GoogleTranslate

As a learner of Portuguese (European, not Brazilian), it’s very useful to immediately translate a word or phrase in either language from anywhere.

By default, it auto-detects the source language and translates to English (which can be customised, as with the other packages) but a specific source and destination language can be specified. For example, en:pt <to translate>.

Third-party packages

Third-party packages are available from anywhere as .keypirinha-package files and should be copied to the following folders:

  • If Keypirinha has been installed: <Keypirinha folder>\default\Packages\.
    Usually C:\Program Files (x86)\Keypirinha\default\Packages or %appData%\Keypirinha\InstalledPackages\.

  • If Keypirinha is running in portable mode: <Keypirinha folder>\portable\Profile\InstalledPackages\

Alternatively, you can use the PackageControl third-party package available at https://github.com/ueffel/Keypirinha-PackageControl to automate the rest.

A full list of officially-recognised packages are available at https://keypirinha.com/contributions.html. The following are my favourites.

Third-party package: Currency

Available at https://github.com/AvatarHurden/keypirinha-currency/releases, by default, this app updates daily and converts between currencies.

To make the syntax easier to use, right-click on the System Tray icon, hover over Configure Package, select Currency, enter the below configuration in the right-hand window, and save the changes.

[aliases]
GBP = £
USD = $$
EUR = €

Third-party package: Cvt

Available at https://github.com/DrorHarari/keypirinha-cvt/releases, this package provides unit conversions for the following measures:

  • Data Size

  • Distance

  • Area

  • Volume

  • Mass

  • Speed

  • Time

  • Force

  • Pressure

  • Energy

  • Power

  • Fuel Consumption

  • Temperature

The syntax is <number> <unit from> <unit to>.

Third-party package: Epoch

Available at https://github.com/prayzzz/keypirinha-epoch, if you work with Unix epoch timestamps a lot as I do, this will make your life that bit easier. By default, the package displays the current time in various formats but you can also paste an epoch timestamp to convert it into something human-readable.

Third-party package: MyIP

Available at https://github.com/Fuhrmann/keypirinha-myip, this package provides a super fast, super simple way to quickly get your PC’s private or public IP address.

Third-party package: WindowsApps

Available at https://github.com/ueffel/Keypirinha-WindowsApps, this app indexes:

  • Metro / Universal Windows Platform (UWP) apps such as Camera, Cortana, Mail, Microsoft Store, and Xbox Game Bar.

  • Windows Settings such as Apps & features, Clipboard, Default apps, Network status, and Windows Update.

Hashing and Base64 too

↑ Back to Index


ShareX

ShareX is basically a Swiss Army knife of great tools, from recording the screen to identifying colours. Its own interface summarises its capabilities better than I can:

And the settings only get deeper! In fact, this tool so extensive that I’ve simply recreated my own configuration and made the file available to download here: https://mythofechelon.co.uk/s/mythofechelons-ShareX-1330-settings.sxb

If, like me, you need to regularly take screenshots (sometimes in rapid succession such as during a live event), annotate screenshots, and take screen recordings, I don’t think there’s a more efficient way to do so.

Be sure to set up screen recording properly:

  1. Open the main interface

  2. Select Task settings…Screen recorderScreen recording options…

  3. Under FFmpeg path, select Download

  4. Under Sources, select Install recorder devices and proceed through the installer

↑ Back to Index


Notepad++

Notepad++ is like Notepad on steroids. Tabs, macros, highlighting, plugins, customisable UI, spell-checking, etc etc.

Personally, I find that Notepad++ is so extensive that it can actually be counter productive so I’ve stripped back its keyboard shortcuts, context menu, language options, etc to what I actually use.

Similar to ShareX, I’ve made my configuration available to download here: https://mythofechelon.co.uk/s/mythofechelons-Notepad-settings.zip

Simply close the app (this generates the files that need to be overwritten) and then paste the .XML files into the folder %appData%\Notepad++\. The 3 files are as follows:

  • config.xml: Includes settings for the application in general.

  • contextMenu.xml: Includes settings for the in-app right-click menu.

  • shortcuts.xml: Includes settings for the keyboard shortcuts and macros.

Macros

Included in my config are my custom macros which I’ve created over my years working as an IT, network, and cybersecurity engineer.

Below, I have listed them all, with demonstrations for the particularly useful ones:

  • Convert American date format to ISO 8601 date format

  • Convert British date format to ISO 8601 date format

  • Convert certificate thumbprint to hash: Useful when working with Windows certificate manager

  • Convert IP addresses to sortable format. Demonstrated below:

  • Convert IP addresses from sortable format. Demonstrated below:

  • Convert MAC address from Windows ARP to standard

  • Convert MAC address from Windows ARP to Windows DHCP

  • Convert MAC address from standard to Windows ARP

  • Convert MAC address from standard to Windows DHCP

  • Convert to Exchange alias format

  • Convert URLs to hosts. Demonstrated below:

  • Count non-blank lines (open find window before running)

  • Encode Splunk search. Useful when embedding a search as a URL. Not quite the same as URL encoding due to Splunk special characters such as $. Demonstrated below:

  • Find all IPv4 addresses

  • Find all IPv4 subnets

  • Remove Event Viewer XML

  • Remove regex group names

  • Replace 3 or more blank lines with 2

  • Replace commas with newlines

  • Replace commas with pipes

  • Replace newlines with commas

  • Replace newlines with pipes

  • Replace newlines with tabs

  • Replace pipes with newlines

  • Replace tabs with commas

Third-party plugins

Plugins can either be installed via the built-in plugin manager at PluginsPlugins Admin… or by pasting the folder or DLL file into the folder C:\Program Files (x86)\Notepad++\plugins\.

Personally, my favourites are as follows:

  • Compare: Useful for comparing two text fields for differences.

  • DSpellCheck. Tip: This doesn’t do anything until you download a dictionary / language which you can do via PluginsDSpellCheckChange Current LanguageDownload Languages...

  • Location Navigate: Useful when regularly jumping back and forth between viewed or edited positions within the same file or between different files.

  • jN → Smart Highlighter: When double-clicking on a string or searching via Find, partial and whole matches found in any view are highlighted in the editing pane and on the scrollbar.


Tips

Here are some miscellaneous useful things that I’ve learned over time:

  • Holding down Alt will allow you to select text vertically. This is useful if, for example, you want to copy a block of text without the indentations or delete the start of multiple lines that begin the same way.

  • In the subwindow Find result that’s brought up by the Find All in Current Document and Find All in All Opened Documents options, Ctrl + C will retain the line numbers but right-click Copy will not.

  • If you want to use a macro that simply searches then you need to open the Find window before you run it.

↑ Back to Index


QTTabBar

QTTabBar is a very useful extension for File Explorer.

As with most of these tools, this app can do a lot more than what I use it for.

Installation

One you have run the EXE file and completed the installation wizard, you will need to:

  1. Restart explorer.exe. Be aware that instances of File Explorer will not be re-opened. You can do this by:

    1. Opening File Explorer.

    2. Right-clicking on an empty space of the Taskbar.

    3. Selecting Task Manager.

    4. Under the tab Processes and the section Apps, select Windows Explorer then the button Restart. If you can’t do this for whatever reason then it’s easier to just sign out and in or restart the PC.

  2. Enable the toolbar(s). You can do this by:

    1. Opening File Explorer.

    2. Selecting the tab View.

    3. In the group Show/hide, underneath the button Options, select the downwards-pointing arrow.

    4. Select QTTabBar and any others you wish to show.

Navigation

QTTabBar accelerates File Explorer navigation with the following web browser-like UI improvements:

  • Multiple tabs per window.

  • Open folder in new tab via middle click.

  • Close tab via middle click.

  • Re-open closed tab via Ctrl + Shift + Z (keyboard shortcuts can be changed).

  • Browse to parent folder via double click on empty space.


Preview

QTTabBar allows you to quickly preview the contents of folders by clicking on an overlayed button and even the contents of common file types such as text, image, and video by simply hovering over them.


Save as Shortcut

Sometimes, you can’t find a tool to do what you need so you just have to create one yourself!

Back in November 2012, I realised that it was more useful to have some shortcuts alongside the other files, rather solely in the web browser on one PC, so I started work on this Google Chrome extension to automate the process.

Nowadays, it can do a lot more:

  • Save the current tab as one file or all opened tabs as a ZIP file.

  • Supported shortcut file types:

    • Cross-platform .HTML

    • Windows-specific .URL

    • macOS-specific .WEBLOC

    • Linux-specific .DESKTOP

  • Save via GUI, context menu, or keyboard shortcut.

  • Option for custom naming formats including various variables.

  • Option to automatically replace Windows invalid characters.

  • Option to automatically strip out the site name.

  • Option to automatically remove notification indicators.

  • Option to preserve the tab order in archive / ZIP files.

  • Guidance on how to change browser settings and diagnose extension issues.



Sign-off

I hope that this has been useful.

Feel free to subscribe to my newsletter to be automatically notified of new blog posts in the future.

😊

Converting Blurb BookSmart to Microsoft Word

How to make custom Squarespace Tudor social links