-
-
Save charlie89/5670d46c90abf3351ed012b2afd926ca to your computer and use it in GitHub Desktop.
#Requires AutoHotkey v2.0 | |
; This script creates a dark mode for specific windows by inverting their colors. | |
; It uses the Windows Magnifier in the background for the color inverting, but it only inverts just the rectangle in place | |
; of the specific windows, NOT the whole screen like the Magnifier applicaion does. This is possible through DLL calls. | |
; Big thanks to the original source at https://www.autohotkey.com/boards/viewtopic.php?p=563023#p563023. | |
; Requirements: | |
; - AutoHotkey needs to be installed in C:\Program Files (or use the workaround from https://www.reddit.com/r/AutoHotkey/comments/16ux144/ui_access_without_reinstalling_in_program_files_v2/). | |
; - In the 'Launch Settings' of 'AutoHotkey Dash' you need to enable 'UI Access'. | |
; Usage: | |
; Use Win+i to invert a window manually or add it to the auto_invert_gp group to invert it automatically. | |
#SingleInstance Force | |
if (!A_IsCompiled && !InStr(A_AhkPath, "_UIA")) { | |
Run "*uiAccess " A_ScriptFullPath | |
ExitApp | |
} | |
SetTitleMatchMode "RegEx" | |
; Add Programs for automatic dark mode here: | |
GroupAdd "auto_invert_gp", "ahk_class #32770", , "Find|Öffnen|Speichern unter" ; Windows Explorer properties window and many others (but not the file open/save dialog that is already dark) | |
GroupAdd "auto_invert_gp", "ahk_class OperationStatusWindow" ; Windows Explorer dialogs | |
GroupAdd "auto_invert_gp", "ahk_exe AutoHotkeyUX.exe" | |
GroupAdd "auto_invert_gp", "ahk_exe hh.exe" ; Windows Help | |
GroupAdd "auto_invert_gp", "ahk_exe mmc.exe" | |
GroupAdd "auto_invert_gp", "ahk_exe procexp64.exe" ; Process Explorer | |
GroupAdd "auto_invert_gp", "ahk_exe regedit.exe" | |
GroupAdd "auto_invert_gp", "ahk_exe Taskmgr.exe" | |
GroupAdd "auto_invert_gp", "ahk_exe WinRAR.exe" | |
; Add Programs where automatic dark mode should never be applied here: | |
; This is mostly because they have dialogs that match ahk_class #32770 but are already in dark mode | |
AutoInvIgnProcName:= ["notepad++.exe"] | |
#MaxThreadsPerHotkey 2 | |
DetectHiddenWindows true | |
; SetBatchLines -1 | |
SetWinDelay -1 | |
OnExit Uninitialize | |
global Inverters := [] | |
global WINDOWINFO | |
global pTarget := 0 | |
; Color Matrix used to transform the colors | |
; Special thanks to https://github.com/mlaily/NegativeScreen/blob/master/NegativeScreen/Configuration.cs, they have many more color matrixes. | |
; I'm not too keen on the maths, so maybe I'm missing some things, | |
; but here is my basic understanding of what does what in a color matrix, when applied to a color vector: | |
; r*=x g+=x*r b+=x*r a+=x*r 0 | |
; r+=x*g g*=x b+=x*g a+=x*g 0 | |
; r+=x*b g+=x*b b*=x a+=x*b 0 | |
; r+=x*a g+=x*a b+=x*a a*=x 0 | |
; r+=x g+=x b+=x a+=x 1 | |
; Simple Inversion | |
MatrixInv := "-1|0|0|0|0|" | |
. "0|-1|0|0|0|" | |
. "0|0|-1|0|0|" | |
. "0|0|0|1|0|" | |
. "1|1|1|0|1" | |
; Smart Inversion | |
MatrixSmart := "0.333|-0.667|-0.667|0|0|" | |
. "-0.667| 0.333|-0.667|0|0|" | |
. "-0.667|-0.667|0.333|0|0|" | |
. "0|0|0|1|0|" | |
. "1|1|1|0|1" | |
; High saturation, good pure colors | |
MatrixHigh := "1|-1|-1|0|0|" | |
. "-1|1|-1|0|0|" | |
. "-1|-1|1|0|0|" | |
. "0|0|0|1|0|" | |
. "1|1|1|0|1" | |
; set the wanted color matix | |
global Matrix := MatrixHigh | |
inGroup(GroupName, WinTitle := "A", WinText := "", ExcludeTitle := "", ExcludeText := "") | |
{ | |
GroupIDs := WinGetList("ahk_group " GroupName) | |
Window := WinExist(WinTitle, WinText, ExcludeTitle, ExcludeText) | |
Loop GroupIDs.Length | |
{ | |
if (GroupIDs[A_Index] = Window) | |
{ | |
;found match | |
return Window | |
} | |
} | |
return false | |
} | |
class Inverter | |
{ | |
hTarget := "" | |
hGui := "" | |
hGui1 := "" | |
hGui2 := "" | |
hChildMagnifier := "" | |
hChildMagnifier1 := "" | |
hChildMagnifier2 := "" | |
xPrev := "" | |
yPrev := "" | |
wPrev := "" | |
hPrev := "" | |
stopped := 0 | |
__New(hTarget) | |
{ | |
DetectHiddenWindows true | |
this.hTarget := hTarget | |
DllCall("LoadLibrary", "str", "magnification.dll") | |
DllCall("magnification.dll\MagInitialize") | |
MAGCOLOREFFECT := Buffer(100, 0) | |
Loop Parse Matrix, "|" | |
NumPut("Float", A_LoopField, MAGCOLOREFFECT, (A_Index - 1) * 4) | |
Loop 2 | |
{ | |
gid := hTarget "_" A_Index | |
MyGui := Gui(, gid,) | |
if (A_Index = 2) | |
MyGui.Opt("+AlwaysOnTop") ; needed for ZBID_UIACCESS | |
; +HWNDhGui%A_Index% | |
MyGui.Opt("-DPIScale +toolwindow -Caption +E0x02000000 +E0x00080000 +E0x20") ; WS_EX_COMPOSITED := E0x02000000 WS_EX_LAYERED := E0x00080000 WS_EX_CLICKTHROUGH := E0x20 | |
MyGui.Show("NA") | |
this.hGui%A_Index%:= MyGui.Hwnd | |
this.hChildMagnifier%A_Index% := DllCall("CreateWindowEx", "uint", 0, "str", "Magnifier", "str", "MagnifierWindow", "uint", WS_CHILD := 0x40000000, "int", 0, "int", 0, "int", 0, "int", 0, "ptr", this.hGui%A_Index%, "uint", 0, "ptr", DllCall("GetWindowLong" (A_PtrSize=8 ? "Ptr" : ""), "ptr", this.hGui%A_Index%, "int", GWL_HINSTANCE := -6 , "ptr"), "uint", 0, "ptr") | |
DllCall("magnification.dll\MagSetColorEffect", "ptr", this.hChildMagnifier%A_Index%, "ptr", MAGCOLOREFFECT) | |
} | |
gid := hTarget "_" 2 | |
this.hGui := this.hGui1 | |
this.hChildMagnifier := this.hChildMagnifier1 | |
return this | |
} | |
stop() { | |
DetectHiddenWindows true | |
if(!this.stopped) { | |
this.stopped := 1 | |
hGui := this.hGui | |
WinHide "ahk_id " hGui | |
hGui := this.hGui1 | |
WinHide "ahk_id " hGui | |
hGui := this.hGui2 | |
WinHide "ahk_id " hGui | |
hChildMagnifier := this.hChildMagnifier | |
WinHide "ahk_id " hChildMagnifier | |
hChildMagnifier := this.hChildMagnifier1 | |
WinHide "ahk_id " hChildMagnifier | |
hChildMagnifier := this.hChildMagnifier2 | |
WinHide "ahk_id " hChildMagnifier | |
} | |
} | |
start() { | |
DetectHiddenWindows true | |
if(this.stopped) { | |
this.stopped := 0 | |
hChildMagnifier := this.hChildMagnifier | |
WinShow "ahk_id " hChildMagnifier | |
hGui := this.hGui | |
WinShow "ahk_id " hGui | |
} | |
} | |
doit() | |
{ | |
DetectHiddenWindows true | |
hTarget := this.hTarget | |
hGui := this.hGui | |
hGui1 := this.hGui1 | |
hGui2 := this.hGui2 | |
hChildMagnifier := this.hChildMagnifier | |
hChildMagnifier1 := this.hChildMagnifier1 | |
hChildMagnifier2 := this.hChildMagnifier2 | |
hideGui := "" | |
WINDOWINFO := Buffer(60, 0) | |
if (this.stopped or DllCall("GetWindowInfo", "ptr", hTarget, "ptr", WINDOWINFO) = 0) and (A_LastError = 1400) | |
{ | |
; xx("destroyed") | |
return | |
} | |
if (NumGet(WINDOWINFO, 36, "uint") & 0x20000000) or !(NumGet(WINDOWINFO, 36, "uint") & 0x10000000) | |
{ | |
; minimized or not visible | |
if (this.wPrev != 0) | |
{ | |
WinHide "ahk_id " hGui | |
this.wPrev := 0 | |
} | |
sleep 10 | |
return 1 | |
} | |
x := NumGet(WINDOWINFO, 20, "int") | |
y := NumGet(WINDOWINFO, 8, "int") | |
w := NumGet(WINDOWINFO, 28, "int") - x | |
h := NumGet(WINDOWINFO, 32, "int") - y | |
move := 0 | |
if (hGui = hGui1) and ((NumGet(WINDOWINFO, 44, "uint") = 1) or (DllCall("GetAncestor", "ptr", WinExist("A"), "uint", GA_ROOTOWNER := 3, "ptr") = hTarget)) | |
{ | |
; xx("activated") | |
hGui := hGui2 | |
hChildMagnifier := hChildMagnifier2 | |
move := 1 | |
hideGui := hGui1 | |
} | |
else if (hGui = hGui2) and (NumGet(WINDOWINFO, 44, "uint") != 1) and ((hr := DllCall("GetAncestor", "ptr", WinExist("A"), "uint", GA_ROOTOWNER := 3, "ptr")) != hTarget) and hr | |
{ | |
; deactivated | |
hGui := hGui1 | |
hChildMagnifier := hChildMagnifier1 | |
WinMove x, y, w, h, "ahk_id " hGui | |
WinMove 0, 0, w, h, "ahk_id " hChildMagnifier | |
WinShow "ahk_id " hChildMagnifier | |
DllCall("SetWindowPos", "ptr", hGui, "ptr", hTarget, "int", 0, "int", 0, "int", 0, "int", 0, "uint", 0x0040|0x0010|0x001|0x002) | |
DllCall("SetWindowPos", "ptr", hTarget, "ptr", 1, "int", 0, "int", 0, "int", 0, "int", 0, "uint", 0x0040|0x0010|0x001|0x002) ; some windows can not be z-positioned before setting them to bottom | |
DllCall("SetWindowPos", "ptr", hTarget, "ptr", hGui, "int", 0, "int", 0, "int", 0, "int", 0, "uint", 0x0040|0x0010|0x001|0x002) | |
hideGui := hGui2 | |
} | |
else if (x != this.xPrev) or (y != this.yPrev) or (w != this.wPrev) or (h != this.hPrev) | |
{ | |
; location changed | |
move := 1 | |
} | |
if(move) { | |
WinGetPos ,,&_w_, &_h_, "ahk_class Shell_TrayWnd" | |
hs := A_ScreenHeight-_h_ | |
if(y+h>hs) { | |
h := hs-y ; escape taskbar | |
} | |
WinMove x, y, w, h, "ahk_id " hGui | |
WinMove 0, 0, w, h, "ahk_id " hChildMagnifier | |
WinShow "ahk_id " hChildMagnifier | |
WinShow "ahk_id " hGui | |
} | |
if (A_PtrSize = 8) | |
{ | |
RECT := Buffer(16, 0) | |
NumPut("int", x, RECT, 0) | |
NumPut("int", y, RECT, 4) | |
NumPut("int", w, RECT, 8) | |
NumPut("int", h, RECT, 12) | |
DllCall("magnification.dll\MagSetWindowSource", "ptr", hChildMagnifier, "ptr", RECT) | |
} | |
else | |
DllCall("magnification.dll\MagSetWindowSource", "ptr", hChildMagnifier, "int", x, "int", y, "int", w, "int", h) | |
this.xPrev := x, this.yPrev := y, this.wPrev := w, this.hPrev := h | |
this.hChildMagnifier := hChildMagnifier | |
this.hGui := hGui | |
if hideGui | |
{ | |
WinHide "ahk_id " hideGui | |
hideGui := "" | |
} | |
return 1 | |
} | |
} | |
; Automatically turn on inversion filter | |
loop | |
{ | |
DetectHiddenWindows false ;would otherwise find many other windows | |
aTarget := WinExist("A") | |
if (aTarget != pTarget) { ; other window focused, check if it should be automatically inverted | |
pTarget:= aTarget | |
GroupIDs := WinGetList("ahk_group auto_invert_gp") | |
;Concat := "" | |
;For Each, Element In GroupIDs { | |
; If (Concat != "") { | |
; Concat .= "`n" | |
; } | |
; Concat .= Element ": " WinGetProcessName(Element) ;WinGetTitle(Element) | |
;} | |
;MsgBox Concat | |
Loop GroupIDs.Length | |
{ | |
hTarget := GroupIDs[A_Index] | |
if (hTarget = aTarget) { ;currently focused window in auto_invert_gp group | |
found:= 0 | |
For index, tmp in Inverters { | |
if(tmp.hTarget = hTarget) { | |
found:= 1 | |
Break | |
} | |
} | |
if (found = 0) { | |
;MsgBox "Invert " hTarget " " WinGetProcessName(hTarget) | |
hTargetName:= WinGetProcessName(hTarget) | |
ignore:= 0 | |
Loop AutoInvIgnProcName.Length | |
{ | |
if AutoInvIgnProcName[A_Index] = hTargetName { | |
ignore:= 1 | |
Break | |
} | |
} | |
if (ignore = 0) { | |
ToggleInversion(hTarget) | |
} | |
} | |
Break | |
} | |
} | |
} | |
; Refresh all inverted windows | |
For index, tmp in Inverters { | |
if(!tmp.stopped) { | |
ret := tmp.doit() | |
if(!ret) { | |
tmp.stop() | |
Inverters.removeAt(index) | |
Break | |
} | |
} | |
} | |
} | |
ToggleInversion(hTarget) { | |
found:= 0 | |
For index, tmp in Inverters { | |
if(tmp.hTarget=hTarget) { | |
found:= 1 | |
if(tmp.stopped) { | |
tmp.start() | |
} | |
else { | |
tmp.stop() | |
} | |
Break | |
} | |
} | |
if (found = 0) { | |
tmp := Inverter(hTarget) | |
Inverters.push(tmp) | |
} | |
} | |
#i:: ;Win+i | |
{ | |
ToggleInversion(WinExist("A")) | |
} | |
Uninitialize(ExitReason, ExitCode) | |
{ | |
DllCall("magnification.dll\MagUninitialize") | |
ExitApp | |
} |
Hi,
yeah AI is currently still very bad with autohotkey scripts, i also made that experience. There is a v1 and a v2 syntax, and there are a few similar programs (i.e. there is a version which runs on linux) which aim to be "syntax-compatible" but have partially different syntax, and i guess the AI can't tell the difference of all that.
Do you have performance problems? Because for me it runs just fine on a i5-9500M mobile cpu and uses maybe 2-3% cpu when inverting a few windows. I still don't fully understand the code in the Inverter class (i just took it from the mentioned link and changed it from v1 to v2 syntax), so it's difficult for me to improve anything here.
What i want to improve when i have time for it:
- Add a feature which looks at the color of the program's title bar to decide if the window should be inverted.
- Try to fix a bug when there is a dialog which opens another dialog which opens then another dialog (happens in Visual Studio in the Project Options and some other programs) then the 2nd dialog suddenly stops inverting it's color.
- Disable font font smoothing / anti-aliasing (or however it is called) on inverted windows, because a partial inversion (like with the
Smart Inversion
filter) doesn't invert the smoothed part of the fonts and that makes text very hard to read. I use a windows VM at home which seems to disable that smoothing so i only noticed that at work.
To your suggestions (which sound like they come from an AI..?):
use Widget instead of WinExist
: i will look it up.cache the results of calling DllCall("GetWindowInfo", ...)
: it's only called once and writes in the WINDOWINFO buffer, so i don't see how i could cache something there. Do you have a code example?more efficient algorithm to search for an inverter
: Do you have a code example? Because i don't know a faster syntax. Also usually there are only a few inverted windows at max, so looping over those few should still be plenty fast. Or do you have a use-case to invert thousands of windows at once?use DllCall("magnification.dll\MagUninitialize") only if the inverters are actually used
: Do you restart that script every few seconds? I mean i see your point, but the usual use-case would probably be to start the script through Windows autostart and it gets closed when you shutdown the PC, that single Dll call shouldn't matter at all. And if one doesn't remove the 'ahk_class #32770' from the script then to 99% there was a inverter used because very many programs (not only windows, alsonotepad++
or the modernvisual studio code
) still have dialogs with class #32770.
Do you have performance problems? Because for me it runs just fine on a i5-9500M mobile cpu and uses maybe 2-3% cpu when inverting a few windows. I still don't fully understand the code in the Inverter class (i just took it from the mentioned link and changed it from v1 to v2 syntax), so it's difficult for me to improve anything here.
I noticed a slight delay of 1-2 seconds when the Windows Scheduler and other heavy windows open, so I thought to optimize somehow.
I have Windows 11 - AMD 7900X, no performance problems.
- Try to fix a bug when there is a dialog which opens another dialog which opens then another dialog (happens in Visual Studio in the Project Options and some other programs) then the 2nd dialog suddenly stops inverting it's color.
I came across the same thing, there are several other programs that create their own sub-windows, but this is not critical.
cache the results of calling DllCall("GetWindowInfo", ...)
: it's only called once and writes in the WINDOWINFO buffer, so i don't see how i could cache something there. Do you have a code example?
more efficient algorithm to search for an inverter
: Do you have a code example? Because i don't know a faster syntax. Also usually there are only a few inverted windows at max, so looping over those few should still be plenty fast. Or do you have a use-case to invert thousands of windows at once?
I don't have an example or similar codes, it was suggested to me by Blackbox AI, and for several days I tried to implement these proposals in various ways :D
I want to thank you again! Thank you for sharing the code. Without you, I don't know how much time and effort I would have spent creating a similar script. ❤️❤️
Hi, have you thought about improving and speeding up the code?
I tried to optimize the code using the neural networks Opus, ChatGPTo, Gemini Pro, but unfortunately the HYIP AI turned out to be powerless in AutoHotkey, because there are errors and hallucinations on every 3-4 lines.
I haven't tried ChatGPT-4, the neural network recommends this:
Reducing the number of DLL calls: There are many DLL function calls in the code, which can be slow. Try to reduce the number of these calls by combining them into larger blocks of code.
Using caching: If some values do not change frequently, you can cache them to speed up access to them.
Memory optimization: There are many memory operations in the code that can be slow. Try to optimize these operations using more efficient algorithms and data structures.
Using multithreading: If the script performs several tasks at the same time, you can use multithreading to speed up work.
In a loop loop, you can use Widget instead of WinExist to get a list of windows in the auto_invert_gp group.
In the function Inverter.dot(), you can cache the results of calling DllCall("GetWindowInfo", ...) to speed up access to information about the window.
In the ToggleInversion function, you can use a more efficient algorithm to search for an inverter in the Inverters array.
In the Uninitialize function, you can use DllCall("magnification.dll\MagUninitialize") only if the inverters are actually used.
....
Thank you!